Skip to content

Hide Agent Builder features when disabled#2314

Merged
andresgutgon merged 1 commit intomainfrom
feature/hide-agent-builder-features
Feb 17, 2026
Merged

Hide Agent Builder features when disabled#2314
andresgutgon merged 1 commit intomainfrom
feature/hide-agent-builder-features

Conversation

@andresgutgon
Copy link
Contributor

@andresgutgon andresgutgon commented Feb 16, 2026

Summary

  • Conditionally hide Agent Builder UI components when agentBuilder access is disabled for a workspace
  • Redirect users to /dashboard when trying to access Agent Builder routes directly

Features Hidden

When productAccess.agentBuilder is false:

Feature Location Implementation
Home link Project sidebar Filtered from PROJECT_ROUTES array
Integrations Settings page Conditionally rendered in layout
Latte sidebar All project pages LatteLayout wrapper conditionally rendered
Triggers section Document editor sidebar Hidden via agentBuilder check
Tools section Document editor sidebar Hidden via agentBuilder check
SubAgents section Document editor sidebar Hidden via agentBuilder check

Routes Protected

Routes that redirect to /dashboard when agentBuilder is disabled:

  • /projects/[projectId]/versions/[commitUuid]/home
  • /settings/integrations/new
  • /settings/integrations/[integrationId]/destroy

Implementation Details

  • Added getProductAccess() server-side helper at apps/web/src/services/productAccess/
  • Client components use useProductAccess() hook from SessionProvider
  • Server components use computeProductAccess() or getProductAccess()

Test plan

  • Verify Home link is hidden in project sidebar when agentBuilder is disabled
  • Verify Integrations section is hidden in settings when agentBuilder is disabled
  • Verify Latte sidebar doesn't appear when agentBuilder is disabled
  • Verify Triggers/Tools/SubAgents sections are hidden in document editor sidebar
  • Verify direct navigation to /home redirects to /dashboard
  • Verify direct navigation to /settings/integrations/new redirects to /dashboard

Made with Cursor

@andresgutgon andresgutgon force-pushed the feature/hide-agent-builder-features branch 2 times, most recently from 2ea3e07 to 312ce20 Compare February 17, 2026 09:20
Conditionally hide Agent Builder UI components and redirect from protected
routes when agentBuilder access is disabled for a workspace.

Features hidden when agentBuilder is false:
- Home link in project sidebar navigation
- Integrations section in settings page
- Latte AI copilot sidebar
- Triggers section in document editor sidebar
- SubAgents section in document editor sidebar

Tools section behavior when agentBuilder is false:
- Shows configured integrations (read-only)
- Hides add button
- Hides remove option from dropdown menu

Routes that redirect to /dashboard when agentBuilder is false:
- /projects/[projectId]/versions/[commitUuid]/home
- /settings/integrations/new
- /settings/integrations/[integrationId]/destroy

Implementation uses the productAccess flags from the workspace model,
accessed via useProductAccess() hook for client components and
computeProductAccess()/getProductAccess() for server components.

Also extracts Latte fetching logic into a separate LatteWrapper component
for cleaner layout code.

Co-authored-by: Cursor <cursoragent@cursor.com>
@andresgutgon andresgutgon force-pushed the feature/hide-agent-builder-features branch from 312ce20 to 2b6bb6a Compare February 17, 2026 09:45
}

// TODO: Replace with /traces project level when ready
return url.issues.root
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no agent builder if users go to /projects/:id we redirect to
First document or issues

In the future will redirect to traces project level. What do you think?

@@ -31,7 +31,9 @@ export const workspaces = latitudeSchema.table('workspaces', {
}),
stripeCustomerId: varchar('stripe_customer_id', { length: 256 }),
isBigAccount: boolean('is_big_account').notNull().default(false),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this please, not needed anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is used in the weekly email. Needed until we fully migrate to CH

promptManagerEnabled: boolean('prompt_manager_enabled')
.notNull()
.default(true),
agentBuilderEnabled: boolean('agent_builder_enabled').notNull().default(true),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't think it makes sense to have this in the data model, we have feature flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example why I think these kind of cases where a lot of things are affected is better in custom flags

#2314 (comment)

Also not super important but this way is 0 extra queries. Again not important but these 2 flags are for everyone and are here to stay for a long time.

initialThreadUuid={lastThreadUuid}
initialMessages={initialMessages}
>
{productAccess.agentBuilder ? (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only people with promptManagerEnabled will have latte because of this validation: https://github.com/latitude-dev/latitude-llm/blob/main/packages/core/src/services/workspaces/updateProductAccess.ts#L31-L36

It's an impossible combination, a workspace with agentBuilder and not with promptManagerEnabled

Copy link
Collaborator

@geclos geclos Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could have built exactly the same logic but storing the state in feature's data model rather than workspace's, the only advantage i see is to have index-enforced conditions but that is not a good tradeoff to polute the main workspace model imo

@andresgutgon
Copy link
Contributor Author

@claude review please

@claude
Copy link

claude bot commented Feb 17, 2026

Claude finished @andresgutgon's task in 1m 44s —— View job


PR Review: Hide Agent Builder features when disabled

Todo:

  • Read changed files and understand the implementation
  • Review for bugs, logic issues, and best practices
  • Check existing review comments for context
  • Post comprehensive feedback

Summary

Good approach overall. The implementation is clean and the pattern of propagating agentBuilder from useProductAccess() / computeProductAccess() is consistent. The test coverage for getRedirectUrl is thorough. Below are issues I found, including some that overlap with the existing review thread comments.


Issues

1. destroy/page.tsx — Client-side redirect is not a true route guard

File: apps/web/src/app/(private)/settings/integrations/[integrationId]/destroy/page.tsx:105-109

useEffect(() => {
  if (!agentBuilder) {
    navigate.push(ROUTES.dashboard.root)
  }
}, [agentBuilder, navigate])

This is a client component ('use client') doing its redirect via useEffect. The page renders first, then redirects — meaning there's a flash of content and the page is technically accessible momentarily. The /new page correctly uses a server-side redirect(). The destroy page should follow the same pattern with a server wrapper (like new/page.tsx) to do the guard server-side, keeping destroy/page.tsx a pure client component.

Fix this →


2. home/page.tsx — Redirects to project root instead of dashboard

File: apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/home/page.tsx:18-20

if (!productAccess.agentBuilder) {
  redirect(ROUTES.projects.detail({ id }).root)
}

The PR description says routes redirect to /dashboard, and the /settings/integrations/new page does redirect(ROUTES.dashboard.root). But /home redirects to the project root, which in turn calls getRedirectUrl() — this could potentially loop or just return to the same document. The PR summary should clarify this is intentional behavior or it should consistently redirect to /dashboard.


3. Tools/index.tsxisLoading state not respected when agentBuilder is false

File: apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/SidebarArea/index.tsx:73-89

<div className={cn('flex flex-col gap-y-6 min-w-0', {
  'custom-scrollbar scrollable-indicator': agentBuilder,
})}>
  {data.isLoading ? (
    <SidebarLoader />
  ) : (
    <>
      {agentBuilder && <TriggersSidebarSection ... />}
      <ToolsSidebarSection agentBuilder={agentBuilder} />
      {agentBuilder && <SubAgentsSidebarSection />}
    </>
  )}
</div>

When agentBuilder is false, data.isLoading still waits for triggersData (via useDocumentTriggersData()) even though triggers won't be rendered. This means the sidebar shows a loading state while fetching data that will be discarded. The isLoading logic should factor in agentBuilder.


4. Tools/index.tsx — PR author's own review comment is unresolved

File: apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/SidebarArea/index.tsx

The PR author (andresgutgon) left a comment: "This should be shown but not external integrations. Only read only custom tools." — referring to the Tools section. The current implementation already passes agentBuilder to ToolsSidebarSection and filters to only CLIENT_TOOLS_INTEGRATION_NAME when disabled, which seems correct. But it would be good to confirm if the sidebar area comment refers to the same concern that was already addressed in Tools/index.tsx, or if there's something still missing.


5. workspaces.ts — Schema comment from geclos about feature flags vs data model

File: packages/core/src/schema/models/workspaces.ts:37

Reviewer geclos suggested this should use feature flags rather than the data model. andresgutgon replied with reasoning about 0 extra queries and long-term stability. This is a design decision that needs resolution before merging, since it touches the database schema. If keeping it in the schema is the final decision, a migration will need to be generated for agentBuilderEnabled — it's unclear from the PR whether one exists.


6. getProductAccess.ts — Duplicate workspace fetch

File: apps/web/src/services/productAccess/getProductAccess.ts

export async function getProductAccess() {
  const { workspace } = await getCurrentUserOrRedirect()
  return computeProductAccess(workspace)
}

This is a thin wrapper around computeProductAccess that also incurs the cost of fetching the current user session again. Callers like home/page.tsx already fetch the session independently:

// home/page.tsx
const productAccess = await getProductAccess()  // fetches session
// ...
const commit = await findCommitCached(...)       // uses separate params

While getCurrentUserOrRedirect() likely uses Next.js request-scoped caching, it's worth verifying this is deduplicated and not causing double session lookups in pages that already call getCurrentUserOrRedirect() themselves.


7. utils.ts — Missing test case: agentBuilder=false with lastSeenDocumentUuid

File: apps/web/src/app/(private)/projects/[projectId]/utils.ts:39-45

When agentBuilder is false, lastSeenDocumentUuid is ignored entirely and always uses documents[0]. There's a test for this (ignores lastSeenDocumentUuid and returns first document URL), which is good. However the behavior change (ignoring user's last-seen document when agent builder is disabled) may be surprising. At minimum, a comment in getRedirectUrl explaining why lastSeenDocumentUuid is ignored in non-agentBuilder mode would help future maintainers.


Minor Notes

  • SidebarArea/index.tsx: The scrollable-indicator CSS class is only applied when agentBuilder is true. If scrolling is needed for the tools-only view (when agentBuilder=false but custom tools are present), the scroll behavior may break.
  • destroy/page.tsx: The IntegrationReferences component hardcodes .home.root as the link destination (line 64), which would break for workspaces where agentBuilder is disabled. This is a pre-existing issue but worth noting.

@andresgutgon andresgutgon merged commit 22ac036 into main Feb 17, 2026
25 checks passed
@andresgutgon andresgutgon deleted the feature/hide-agent-builder-features branch February 17, 2026 10:20
@github-project-automation github-project-automation bot moved this from Inbox to Done in Latitude LLM Roadmap Feb 17, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Feb 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants