/chrome command output visibility in a fresh session
August 14, 2026 · View on GitHub
Problem
Running /chrome status (or doctor, authorize, …) in a brand-new session
executed correctly and appended command/run + command/done, but the page
stayed on the empty hero and showed nothing. The same commands rendered fine in
a session that already had conversation content.
Root cause
This is a deliberate harness behaviour, not a bridge bug.
ComposerPhase activates a conversation only when the Chat window holds a
visible non-command Node:
// packages/client/runtime/src/client/sessions/session.ts
function hasVisibleConversationContent(chat: ChatSnapshot): boolean {
return chat.order.some(key => chat.nodes.get(key)?.kind !== 'command')
}
Generic command rows are control-plane content and are excluded on purpose, so
a command-only session stays blank and the hero hides the conversation view.
/goal is not special-cased in that predicate — it works because ui-goal
ships a feature-owned Conversation Definition that projects the command input as
a separate non-command Chat Node. The harness Agent Note
2026-08-01-goal-command-input-projection records the decision and explicitly
rejects widening the generic rule:
Render every command input as a user bubble. Rejected because existing control commands deliberately leave a fresh session on the hero; changing them would broaden interaction semantics without a feature-owned Conversation Definition.
So the sanctioned fix for /chrome is a feature-owned projection in this
package, not a change to the harness render layer.
Fix
dsh-chrome now ships a browser half (src/client/) mirroring ui-goal:
chrome-command-input.ts— aConversationNodeDefinitionmatchingcommand/runwherename === 'chrome', building achrome-command-inputChat Node anchored atseq - 0.1(just before the run).ChromeCommandInputView.tsx— the keyedconversation.chat.noderenderer, a right-aligned bubble reusing the shared--dsw-*tokens.index.ts—applyregistering the Definition plus the renderer throughctx.slots.inject('conversation.chat.node', …).
Because the projected Node's kind is chrome-command-input (not command), it
satisfies hasVisibleConversationContent, flips the session to active, and the
generic result row that the command Definition already produced becomes visible
alongside it.
Build
The harness clientBundle tsdown preset is repository-internal, so the closure
factory format is reproduced locally in scripts/build-client.mjs with esbuild:
window.__ModuleLoader__.load({ id: "dsh-chrome", factory: (require) => { … } })
Only react and react/jsx-runtime stay external — they are resolved from the
shell's frozen module table. Every other import is type-only and erased, so the
bundle carries no cross-plugin runtime identity. src/client/ is excluded from
the node-half tsconfig.json and type-checked by tsconfig.client.json.
Operational note
ClientModuleRegistry caches negative dsh.client verdicts for the process
lifetime ("plugin-set changes take effect on restart"). Adding the client half to
an already-running dsh web therefore requires a server restart before the row
appears in window.__DSH_BOOT__; rebuilding the bundle alone is not enough.