Repository navigation
ADE M8: connect straight to chat + kill the narrative copy - #219
Merged
Merged
Conversation
This was referenced Aug 25, 2026
Closed
Opening /fleet/$botId now connects. The Start/Resume interstitial — a greeting, a paragraph of kernel setup, and a button rendered instead of the conversation — is gone. `BotChatPage` starts the session once per mount and renders the conversation shell throughout: connecting (composer stand-in + shimmer), failed (compact inline notice with Retry, remediation behind a collapsed Details disclosure), ready (live composer). The server was already safe for a mount-time start — `startPrimaryChat` holds a per-thread lock and gates on `hasSession`, so an auto-start against a live bot adopts rather than re-mints. #197's hook-count gate is untouched: neither `ChatView` nor `BubbleTimeline` mounts before the thread is in the store, and the pending shell is not an ancestor of either. Copy across components/captain/** and the fleet chat/screen surfaces no longer narrates the UI to its own user. Removed: "Firstmate already has a session open", "is standing by", "no session starts until you say so", "Opening this tab never starts one", "it will tell you what is missing", "That opens its workspace, and routines can be added after", "You are watching this bot's live desktop". Headlines state the state, details carry one next action, and technical remediation moved into disclosures and error detail halves via the new `adeCaptainErrorParts`. Closes #217
D1 session_unavailable is a server-side bucket (no-project, no-repo-path,
workspace-create-failed, kernel down, no models), so the headline may not
assert a cause. It is now "This bot isn't connected."; the remedy is the
server's own message in the disclosure. When the no-project CTA renders
below, the strip's disclosure is suppressed so exactly one next action
shows.
D2 retryConnect refreshes the detail and roster queries as well as
restarting the session. The notice is raised for a failed getBot/getRoster
too, and those never recovered from a press that only touched the session.
D3 Mount-time auto-start is gated on the shuvcode kernel pill being
healthy. startPrimaryChat dispatches thread.create before the session
attempt, so sweeping a rail with the kernel down materialised one durable
thread per bot per visit. Kernel down/unknown now renders the failed state
with no server call; Retry stays ungated because a press is intent.
D4 Read-marking requires 1.5s of continuous ready-and-at-end dwell.
M3/M4 built marking on "the captain pressed Start"; M8 removed that
button, so arrow-keying the rail was irreversibly wiping unread dots.
Minors: pinned the route's load-bearing key={botId} and added the
post-await bot guard in handleStart; toolsMissing clears on a failed
start; the notice container is role="status" with the alert scoped to the
sentence so Retry and the disclosure are not announced as alert content;
fixed two tests that could not fail (a tone sweep asserting a literal
against itself, and a details-open assertion that never matched React's
open="" serialization).
…rives A null fleet-health snapshot means 'not read yet', not 'unhealthy'. Treating it as blocked flashed the kernel-down notice on every cold load and then silently connected once the first frame landed. It still does not auto-start (canAutoConnect(null) is false), so this waits instead of guessing.
shuv1337
force-pushed
the
ade/m8-direct-chat
branch
from
August 25, 2026 19:50
124f9fa to
71a2aa1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #217
The captain's critique: "get rid of this 'resume chatting' button, why would we not connect right to the chat instead of adding this step and the weird narrative in the UI".
1. Direct connect
Opening
/fleet/$botIdconnects. There is no interstitial and no button.BotChatPageauto-starts once per mount and renders one conversation shell for all three states:connectingfailedRetry+ collapsedDetailsreadyBubbleTimeline/ChatViewNotes on the two things that made this safe:
startPrimaryChatalready takes a per-thread lock and gates onhasSession, so a mount-time start against a bot that is already live is a probe-only pass that adopts the running session. No live-session teardown, no double-start. No server changes in this PR.ChatView/BubbleTimelinestill mount only oncechatReady. The pending shell is a pure props-in component that is not an ancestor of either, so it cannot change a hook count. Only the button and the landing page were removed.useRefkeyed bybotId, written synchronously before theawait. The double-invoked mount effect sees the bot the first pass recorded and issues exactly one start. Keying bybotId(not a boolean) lets the same mount reconnect when the rail swaps conversations under it.The empty conversation is now bot avatar + name +
No messages yet— no CTA (the composer below is the CTA), no kernel lecture.The
toolsProbe: "missing"strip is kept (delegation is genuinely down) but reworded and demoted totone="muted".2. De-narration
Swept
components/captain/**plus the fleet chat/screen copy. Strings stay in the pure logic modules where they already lived;adeCaptainErrorPartsis new and splits a captain error intoheadline+detailsinstead of concatenating them — that concatenation is what put a paragraph of provider setup on screen as primary copy.Representative before → after:
Firstmate already has a session open.+Resume chattingFirstmate is standing by. Nothing is running yet — no session starts until you say so.+Start chattingThis bot runs on the shuvcode kernel. It needs \shuvcode service start` running and an opencode2 provider instance in Settings → Providers (with Binary path pointing at your shuvcode CLI). You can open the chat either way — it will tell you what is missing.`This bot isn't connected — check its provider settings.·Details▸ the provider/binary remediationNo kernel session is available right now.This bot isn't connected — check its provider settings.Start a desktop to watch this bot work. Opening this tab never starts one.No desktop running(detail empty — the Start button beside it is the action)You are watching this bot's live desktop.Desktop running(detail empty)The desktop is stopped. Its files are kept, so starting it again resumes the data.Desktop stopped·Files are kept — resuming picks up where it left off.Waiting for Screenbox to bring the container up.Starting a desktop(detail empty)Showing fullscreen./Paused while off-screen. Scroll back to watch.Open fullscreen/Desktop running — scroll back to watchRoutines run inside a project. Create one, then this bot can hold routines.Create a project for this bot.Send this bot a message once. That opens "Ledger", and routines can be added after.Send this bot a message to open "Ledger".Loading this bot's project. Routines appear once it is available.Loading routines(no detail — nothing to fix)The rail is showing only the bots waiting on you. Open one to decide it.Bots do their work inside a project. Creating one also creates its Second Mate.Code Monkey is mid-turn and this provider cannot steer it. Try again when it finishes.Code Monkey is still working. Send this when it finishes.This conversation is still synchronizing. Try again in a moment.Not connected yet. Try again in a moment.Sent straight to the fleet. Never shown in this conversation, the contact list, or logs.Never stored in this conversation.This bot changed elsewhere while you were editing. Your changes are kept; saving overwrites the other ones.Changed elsewhere — saving overwrites those changes.Add one from a template to start building your crew.Add a bot from a template.Review work routes to bots whose tag contains "Reviewer".Tags containing "Reviewer" receive review work.Scope
M6's attach/observer logic is untouched — only the poster copy in those panels.
CaptainShell/ContactRailstructure and the workspace sidebar are untouched (M7 owns those);contactRail.logic.tschanges are copy-only.Verification
vp test run apps/web/src/components/fleet apps/web/src/components/captain apps/web/src/state/ade.test.ts— 26 files, 414 tests passingvp check --fix— 0 errors; no new warnings in changed filestsc --noEmit -p apps/web/tsconfig.json— cleannode scripts/check-brand-identity.ts— passed across 3458 filesNew tests: mount-time auto-start incl. a StrictMode-style double-invoke simulation (
shouldAutoStartChat), the three-state connect machine + composer gating (resolveBotChatConnectState/isBotChatComposerDisabled), failure-state rendering with the disclosure closed and the technical text kept out of primary copy (BotChatConnect.test.tsx,renderToStaticMarkup— the web suite has no DOM), the error headline/detail split, and copy-tone assertions pinning the banned phrasings out of each rewritten module.