docs(web): the composer bar is one session-maybe slot, not a swapped pair
fix(web): keep one composer bar DOM across the no-workspace transition The composer.bar slot moves from session to session-maybe scope: with no current session the entry still mounts, the machine faces (keyboard, stop, command) arrive undefined, and the bar renders its normal DOM inert via the disabled owner prop. DisabledInputBar and its parallel tree are gone, so the textarea node survives the cold-start workspace pick instead of flashing through a remount. A blank session whose workspace was deleted takes the same inert path through owner props. test(web): add the composer DOM-continuity acceptance probe Drives a real dsh web server with headless chromium through the cold-start -> pick-workspace -> type flow and asserts the composer textarea is the same DOM node throughout (a marker property must survive). Rerun prerequisites are in the header comment. docs(web): session-maybe identity is adoption, not hold-forever fix(web): session-maybe entries adopt the first session, then remount like strict entries A session-maybe entry used to keep one React instance across every transition, so component-local state leaked between sessions once the composer bar moved to that scope (PermissionSelect's optimistic pick, the IME composition guard). Identity is now adoption: an incarnation born session-less holds through the arrival of the first session (the blank shell's DOM survives the workspace pick), and afterwards behaves exactly like a strict session entry — a switch or a drop to no-session remounts, clearing local state by construction. The child key is an incarnation counter kept in the stable outlet wrapper via render-phase setState. chore: knip knows the root acceptance probe's playwright dependency scripts/hero-composer-dom-continuity.mjs resolves playwright through apps/web's devDependency tree (createRequire), which knip cannot follow; ignore it at the root workspace.
This commit is contained in:
+1
-1
@@ -70,7 +70,7 @@ A trigger/menu/pick pipeline with zero knowledge of "commands":
|
||||
|
||||
- The hub (trigger/decoration registries + send orchestration) takes the slash/command services as optional `ctx.get()` dependencies: without ui-slash or the command surfaces, input still sends and receives normally — graceful degradation.
|
||||
- Each materialized Session has exactly one `SessionInputShell` (the facade), created and torn down with the session scope; with no session, no input machine is built. `ConversationRoot` is itself the `session-maybe` resident shell, holding HeroShell, the Workspace picker, the composer stack, and the chain-fallback frame.
|
||||
- With no session the shell renders the presentation-only `DisabledInputBar`; once `connectWorkspace` returns a blank session, only the input body is swapped for the strict-session InputBar. The textarea may be rebuilt here, while `ConversationRoot`, the Hero, and the layout skeleton hold; blank → engaging/active stays the same session-bound InputBar, with the textarea never rebuilt on a phase flip.
|
||||
- The composer bar is one `session-maybe` slot entry rendered unconditionally: with no session the same InputBar renders inert (machine faces absent, `disabled` owner prop), and once `connectWorkspace` returns a blank session the same instance goes live — the textarea DOM survives the no-session → blank transition and every later phase flip; `ConversationRoot`, the Hero, and the layout skeleton hold throughout.
|
||||
- ConversationRoot's Hero criterion is `sessionId === undefined || (composerPhase === 'blank' && (openState === 'open' || openState === 'loading'))`. The first submit enters engaging synchronously, and a failure keeps the composer and the error context rather than falling back to the blank Hero; the sidebar's blank bit flips false only after a prompt is successfully accepted.
|
||||
- Sending unifies in the hub defaultSink: after an optimistic draft clear it goes only through `session.prompt {mode:'queue'|'steer'}`; backfill happens only when it fails and the live draft is still empty — a user who has kept typing is never overwritten. No Draft materialize or attach transaction exists.
|
||||
- When the blank Hero re-picks the Workspace, the shell calls `connectWorkspace`; if the target session differs, the non-empty draft moves from the current shell to the target shell before the new id is opened, and the old blank session survives but is no longer current.
|
||||
|
||||
Reference in New Issue
Block a user