Skip to content

fix(web): stamp focused-app context on main-composer messages#382

Open
mgoldsborough wants to merge 1 commit into
mainfrom
fix/chat-focused-app-context
Open

fix(web): stamp focused-app context on main-composer messages#382
mgoldsborough wants to merge 1 commit into
mainfrom
fix/chat-focused-app-context

Conversation

@mgoldsborough
Copy link
Copy Markdown
Contributor

Problem

A user viewing an app (e.g. collateral) and typing into the main chat panel got an agent that couldn't see what they were looking at — it would say things like "I can't read your UI state directly — collateral doesn't expose a 'currently viewing' pointer."

The bundle is innocent: collateral does publish its current view via useVisibleState (activeDocument {id,name} + a summary). The break is in the web shell.

Root cause

The globally-mounted chat panel ChatChrome sent messages with no appContext:

// before
(text, files) => chat.sendMessage(text, undefined, files)

The only path that stamped app context was AppWithChat.handleChat — the in-app "ask about this" channel — which fires only when the iframe itself initiates a send, not when you type into the side panel.

The backend confirms the consequence: runtime.ts gates its entire focused-app resolution on if (request.appContext) and does not derive the focused app from X-Workspace-Id. So no appContext → no focused app → no "user is currently viewing X" section and no injected app visible state. Exactly the symptom.

Fix

A small standalone FocusedAppContext:

  • The active AppWithChat publishes its AppContext while mounted and clears it on unmount / route change.
  • ChatChrome reads it and stamps it on outgoing messages. useChat already enriches appContext with the app's latest visible state from the bridge store, so the open-document pointer now reaches the agent.

Standalone context (not folded into ShellContext) so focus changes re-render only the two participants, not every shell consumer. Non-app routes publish null → no appContext, unchanged behavior. The in-app channel and its [App Context: …] text prefix are untouched.

Test

web/test/FocusedAppContext.test.tsx pins the publish→read contract across two components (the exact link that was broken), the inert out-of-provider default, and clear-on-null.

Verification

  • bun run verify:static — pass
  • bun run test:web — 462 pass / 0 fail
  • cd web && bun run build — clean

(The unrelated test:unit dompurify failure is a missing nested dependency in src/bundles/automations/ui/, present on a fresh checkout regardless of this change.)

Messages typed into the global chat panel (ChatChrome) were sent with no
appContext. Only the in-app "ask about this" channel (AppWithChat.handleChat)
stamped it, so a user typing into the side panel while viewing an app got an
agent that couldn't see which app — or which document — they were looking at.
The backend gates its entire focused-app resolution on request.appContext
(runtime.ts), so no appContext means no "currently viewing" section and no
injected app visible state, even though the app (e.g. collateral) publishes it
via useVisibleState.

Add a small FocusedAppContext: the active AppWithChat publishes its AppContext
while mounted (and clears it on unmount / route change); ChatChrome reads it and
stamps it on outgoing messages. useChat already enriches appContext with the
app's latest visible state from the bridge store, so the open-document pointer
now reaches the agent. Standalone context (not folded into ShellContext) so
focus changes re-render only the panel and the app view, not every shell
consumer. Non-app routes publish null → no appContext, unchanged behavior.
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