Skip to content

feat(chat): hide the Chat module when NEXT_PUBLIC_CHAT_DISABLED is set - #6137

Merged
TheodoreSpeaks merged 11 commits into
stagingfrom
feat/remove-chat-ui
Jul 31, 2026
Merged

feat(chat): hide the Chat module when NEXT_PUBLIC_CHAT_DISABLED is set#6137
TheodoreSpeaks merged 11 commits into
stagingfrom
feat/remove-chat-ui

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Gate the Chat module behind NEXT_PUBLIC_CHAT_DISABLED. It's an opt-out, so existing deployments are unaffected — nothing to set to keep Chat.
  • One variable, no server/client twin: it isn't a secret, so getEnv resolves the same value from process.env on the server and window.__ENV in the browser.
  • Presentation and capability are separate. The flag decides whether surfaces render; COPILOT_API_KEY decides whether the work can run, and gates the Sim Chat block, prompt-job claims, and Inbox — each failing on its own terms.
  • With Chat off the workspace lands on your first workflow, and the chats list, scheduled tasks, editor Chat panel, and chat CTAs are gone. Routes are gated, not deleted: /home redirects because it's baked into delivered invite emails and the invitation-accept contract.
  • The setup wizard writes the opt-out when you skip the chat key, in all three modes. k8s now prompts for the key too — it previously did neither.
  • Compose forwards the flag to the app container; it was writing it to .env where nothing passed it through.

Fixes three bugs the gate exposed:

  • A persisted activeTab: 'copilot' left the workflow panel blank from first paint, including pre-hydration via the blocking script.
  • The panel's handoff listener claimed MOTHERSHIP_SEND_MESSAGE events outside its own gate, silently swallowing "Fix in Chat".
  • Its auto-select effect read a deliberately-skipped query as "chat deleted elsewhere" and cleared the user's selection permanently.

Type of Change

  • New feature

Testing

Tested manually. bun run lint:check, type-check, and the full audit suite (boundaries, api-validation:strict, utils, zustand-v5, react-query, client-boundary, bare-icons, icon-paths, realtime-prune, skills, agent-stream-docs) all pass. Full vitest suite green apart from two failures inherited from staging (executor/handlers/pi/* — one needs rg on PATH). No migrations authored here.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

TheodoreSpeaks and others added 8 commits July 30, 2026 18:36
A self-hosted deployment that skipped the chat key still rendered the full
mothership Chat UI, landing on the composer and 401ing on every message.

Gate it behind a CHAT_ENABLED / NEXT_PUBLIC_CHAT_ENABLED twin, written by the
setup wizard alongside COPILOT_API_KEY and validated by the existing FLAG_TWINS
doctor check. The flag resolves at module scope on both render passes, so no
chat surface renders then disappears.

With Chat off the workspace lands on its first workflow (resolved server-side,
behind the cached host-context check so no workflow id leaks to non-members),
and the chats list, scheduled tasks, editor Chat panel, and chat CTAs are
absent. Routes are gated rather than deleted: /home redirects because it is
baked into delivered invitation emails and the accept contract.

Also fixes two bugs the gate exposed: a persisted activeTab of 'copilot' left
the workflow panel blank from first paint, and the panel's handoff listener
claimed MOTHERSHIP_SEND_MESSAGE events outside its own gate, silently
swallowing "Fix in Chat" messages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
# Conflicts:
#	apps/sim/app/layout.tsx
#	apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx
#	apps/sim/app/workspace/page.tsx
#	apps/sim/lib/core/config/env-flags.ts
#	apps/sim/lib/core/config/env.ts
#	apps/sim/lib/invitations/core.ts
#	packages/testing/src/mocks/env-flags.mock.ts
…in flag

CHAT_ENABLED made Chat opt-in, so every existing deployment that already had
COPILOT_API_KEY would have lost the module until it set a new variable. Invert
to an opt-out so nothing changes for them.

That also collapses the twin. The only reason the flag needed a server/client
pair was that it projected a secret; NEXT_PUBLIC_CHAT_DISABLED is not one, so
getEnv resolves the same value from process.env on the server and window.__ENV
in the browser. Gone with it: the FLAG_TWINS entry and its doctor sync check,
the two-variable wizard write, and the boot-time throw, whose contradiction
(flag on, key absent) can no longer be expressed.

Presentation and capability are now separate concerns. NEXT_PUBLIC_CHAT_DISABLED
decides whether the surfaces render; COPILOT_API_KEY decides whether the work
can run, and gates the paths that need it — the Sim Chat block, prompt-job
claims, and inbox access — each failing on its own terms.

The wizard writes the opt-out when you skip the chat key, which is the case this
started from: a fresh self-host that never configured Chat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
The dev and compose flows minted a chat key and wrote the Chat opt-out
alongside it; k8s did neither, so a cluster install with no COPILOT_API_KEY in
its Helm values rendered a Chat module that rejects every message.

Prompt with the same flow and feed both values into `app.env`, which the chart
already renders as arbitrary container env. Reading the previous release's key
matters here in a way it does not for the file-based modes: `helm upgrade`
without `--reuse-values` keeps only what this document carries, so a key the
user elects to keep has to be re-supplied or it is silently dropped.

Splits the release-values read from the secret-reuse check so both the key and
the secrets come from one `helm get values` call, and carries the mothership
override across for the same mint-here-validate-there reason the other modes
document.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
…art from

The wizard wrote the Chat opt-out only to the env file its own mode owns, so
choosing compose put it in the root `.env` while `bun run dev` reads
`apps/sim/.env` and never saw it. Skipping the chat key appeared to do nothing.

Mirror values that change how the app behaves — as opposed to where it connects
— across both targets. Connection settings deliberately do not go through this:
DATABASE_URL and friends differ between the compose stack and a local dev run,
which is why this takes an explicit set of values rather than the whole batch.

The mirrored file is written even when absent, since missing is exactly the case
that stranded the flag, but with seeding suppressed so a compose run leaves a
one-line apps/sim/.env instead of a full .env.example for a stack the user is
not running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
The wizard wrote the flag into the root .env, but compose only passes through
variables the service's `environment` block names — and that block listed
COPILOT_API_KEY without its companion. Skipping the chat key on a Docker install
therefore did nothing: the value sat in .env and never reached the container.

Add the passthrough to all four compose files. Reverts the previous commit's
mirroring into apps/sim/.env, which treated the symptom — each mode writes only
the env file it owns, and that file is now wired correctly.

k8s needs no equivalent: its values flow into `app.env`, which the chart renders
key by key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
Server-resolving the first workflow meant a session lookup, an access check and
a query had to finish before anything rendered. A slow or unreachable database
left the user on a blank page under a populated sidebar — worse than the
instant redirect it replaced, and with no signal that anything was wrong.

Redirect straight to `/w` instead and let it pick from the workflow list the
layout already prefetches, so the choice costs no round trip and cannot hang.

Repoints the sidebar's primary action rather than hiding it: the slot that
offered "New chat" now offers "New workflow" and creates one, since with Chat
off there is no composer to open but the intent is the same.

Sends the CLI key handoff to signup rather than login. It is reached from a
terminal — usually the setup wizard standing up a fresh self-host — where the
visitor has no account yet. Both auth pages cross-link carrying the callback,
so a returning user is one click from login with their destination intact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
Effects: the panel's auto-select effect read the copilot chat list while the
list query was deliberately skipped, took "empty" for "deleted in another tab",
and cleared the user's selection — latching a ref that stopped it ever being
restored. Guarded on the same condition as the handoff listener.

Memo: `/w` filtered workflows through a useMemo whose array dependency was a
fresh `[]` on every render while the query had no data — the exact window the
page exists for — so it memoized nothing and re-fired the redirect effect. Keyed
on the workflow id instead. Same unstable-default problem on the sidebar's chat
list, where it invalidated five downstream memos; given a stable empty constant.

Callback: `handleCreateWorkflow` listed the whole mutation object in its deps,
which TanStack recreates every render. Harmless until this branch wired it into
the top nav, where it defeated `memo(SidebarNavItem)`.

React Query: Recently Deleted still fetched archived chats unconditionally and
offered restores into routes that now 404.

Also surfaces an error state on `/w` — it is the landing route now, so a failed
list fetch would otherwise spin forever behind a log line — fixes a spinner
using a token undefined in dark mode, and trims comments that restated code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
@TheodoreSpeaks
TheodoreSpeaks requested a review from a team as a code owner July 31, 2026 22:49
@gitguardian

gitguardian Bot commented Jul 31, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35187658 Triggered Username Password bc14b58 apps/desktop/src/main/browser-credentials/vault.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Jul 31, 2026 11:13pm

Request Review

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Wide routing and navigation changes (default landing, invites, sidebar) plus schedule and executor gating; behavior is env-driven and mostly UI, but misconfiguration could strand users or skip prompt jobs unexpectedly.

Overview
Adds an opt-out for the Chat module via NEXT_PUBLIC_CHAT_DISABLED and isChatEnabled (Chat stays on when unset). UI visibility is separate from mothership capability: the public flag hides surfaces; COPILOT_API_KEY still gates the Sim Chat block, prompt-job schedule claims, Inbox, and mothership SSE.

With Chat off, workspaces land on the first workflow (/w instead of /home), the sidebar drops chats and scheduled tasks, and chat CTAs and the editor Chat tab are hidden. /home and chat routes redirect or 404 so old invite links keep working.

The setup wizard writes the opt-out when the chat key is skipped (compose, dev, and k8s); Docker compose files pass the flag into the app container. Docs and .env.example document COPILOT_API_KEY and the new variable.

Fixes regressions from hiding Chat: persisted copilot panel tab no longer blanks the workflow panel (including pre-hydration script), the panel handoff listener only runs when Chat is available, and copilot auto-select no longer treats a skipped query as a deleted chat.

Reviewed by Cursor Bugbot for commit 0809195. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an opt-out deployment flag for the Chat module.

  • Hides Chat routes, navigation, queries, editor surfaces, scheduled tasks, and related calls to action when NEXT_PUBLIC_CHAT_DISABLED=true.
  • Redirects workspace landing routes to the workflow list and provides permission-aware handling for workspaces with no workflows.
  • Gates Chat-dependent execution paths on COPILOT_API_KEY and propagates setup configuration through supported deployment modes.
  • Corrects persisted panel state and Chat handoff behavior when the Chat tab is unavailable.

Confidence Score: 5/5

The PR appears safe to merge.

The previously reported read-only empty-workspace failure is fixed: workflow creation remains hidden until permissions resolve and is unavailable to read-only members, while editors retain the creation path.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/page.tsx Adds a terminal empty-workspace state and correctly limits workflow creation to users with resolved edit permission.
apps/sim/lib/core/config/env-flags.ts Defines the deployment-wide Chat feature gate used by server and client surfaces.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx Prevents unavailable Chat state from blanking the panel, clearing selections, or consuming handoff events.
apps/sim/app/api/schedules/execute/route.ts Avoids claiming Chat-dependent prompt jobs when the required Copilot credential is absent.
scripts/setup/steps.ts Integrates Chat configuration into setup so skipped credentials produce the corresponding opt-out setting.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Flag[NEXT_PUBLIC_CHAT_DISABLED] --> Enabled{Chat enabled?}
  Enabled -->|Yes| Chat[Render Chat surfaces]
  Enabled -->|No| Workflows[Land on workflow list]
  Workflows --> Existing{Workflow exists?}
  Existing -->|Yes| Editor[Open first workflow]
  Existing -->|No| Permission{User can edit?}
  Permission -->|Yes| Create[Offer Create workflow]
  Permission -->|No| Admin[Ask workspace admin]
  Key[COPILOT_API_KEY] --> Capability{Chat capability available?}
  Capability -->|Yes| Jobs[Run prompt jobs and Inbox]
  Capability -->|No| Skip[Skip Chat-dependent work]
Loading

Reviews (3): Last reviewed commit: "fix(setup): name both variables in the c..." | Re-trigger Greptile

Comment thread apps/sim/app/workspace/[workspaceId]/w/page.tsx Outdated
Comment thread scripts/setup/modes/compose.ts
# Conflicts:
#	apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx
#	apps/sim/app/workspace/page.tsx
…edule tests

The zero-workflow landing offered "Create workflow" to every member. Creation
navigates optimistically, so a read-only member was sent to a workflow the
server had already refused to create, with the failure never surfaced. Gate both
entry points — the empty state and the sidebar's "New workflow" row — on the
same `canEdit` check the rest of the sidebar uses, and tell read-only members
who can make one instead of offering an action that cannot succeed.

The schedule-execution tests only passed locally because vitest loads the
developer's own `.env`, which supplied COPILOT_API_KEY; CI has none, so the
prompt-job claim guard skipped the claims those cases assert on. Pin the key
through the env mock so the suite states its own preconditions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread scripts/setup/steps.ts
The caller writes the Chat opt-out whenever the prompt returns no key, so the
hint's "or set COPILOT_API_KEY yourself" restored capability while leaving the
module hidden — the one path where following setup's own advice does not work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0809195. Configure here.

@TheodoreSpeaks
TheodoreSpeaks merged commit c5cc6ce into staging Jul 31, 2026
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant