Repository navigation
feat(first-session): the hosted path drops 100 words of reading, addresses the composer, and stops offering a slot that's taken - #2266
Conversation
…esses the composer, and stops offering a slot that's taken QA walk 4 (person-paced, production ccd4046): page visible to a live agent took 1:27, and the first real answer ~2:31. The product's own waits were 7.7s; reading (64s) and typing (60s) were the rest. Three levers: 1. Bring your own via a "Run it here" link (?mode=hosted) read 130 words (31s) before its button. That person already chose hosting, so the page is now: "Run an agent here", a 20-word lead, one line saying where it lands ("It joins My Workspace as <name>." + Change), the button, and the limits line. "Other ways to connect" opens the full page with hosting still chosen. It's shown from first paint with the button disabled until availability says the instance can host; a non-hosting instance gets the full page. Each line waits for its data (no "joins as" or "0 turns" on first paint). Measured in Chromium against the production API: ~33 words before the button (from 130), button at y=278 at 1440x900 and y=377 at 390x844, no horizontal scroll. 2. Typing the agent's 32-character handle cost 9.6s. "Go to your pod" now passes "@<agent> " in navigation state, and the thread fills and focuses the composer once per navigation, then drops it from history so a reload doesn't refill it. The effect sits above V2Thread's `!pod` early return. The first version sat below it and the browser check crashed it ("Rendered more hooks than during the previous render"); jsdom couldn't see that, so there's now a load-order test. 3. Scout's "Run an agent here" start (and Scout's hosted-offer button, the same id) stayed under the welcome after the agent was live. The agent-states roster now says which agents are hosted, and the thread hides the run-hosted start once the viewer owns one in the pod. The other starts stay. Copy in en + zh-CN (zh-cn-ui-localization glossary: 智能体, 托管, 你; the limits line matches the existing zh format). One text-link style (v2-byo__link), the same declarations as v2-team__link: no new tokens. Tests: the compact page (title, joins line, limits, no full-page copy, one click to live), Other ways to connect, the disabled-until-available act, Go to your pod's draft, the draft applied once, the draft surviving a late pod load, the start hidden only when the viewer holds a hosted agent, and the hosted roster flag. Mutations: no compact view, no draft on Go to your pod, start not hidden, no prefill, roster never hosted, and the effect below the early return each fail an arm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157AG2wo8mJYqKezXNs48Ya
lilyshen0722
left a comment
There was a problem hiding this comment.
Written by sprint-review, a Commonly agent — code gate at f9a8e3904. pod thread
The hooks fix is correctly placed and genuinely pinned
Verified by position, not by the comment: the effect is at :1257, the if (!pod) early return at :1276. And the arm is a real pin — making the hook conditional (if (pod) useEffect(...)) reds "the draft arrives when the pod loads after the first render, without a hooks crash" with the exact error your comment names, Rendered more hooks than during the previous render.
Worth saying that the browser check earning its place here is the point: jsdom can catch this, but only if a test renders loading-then-pod, and nothing would have prompted that arm without the crash being seen first.
The effect's other properties check out by reading: navigate(..., { replace: true, state: null }) cannot loop, because the re-run finds no composeDraft and returns at the first guard; slice(0, 200) bounds the prefill; and composeDraft arrives only through router state, never the URL, so there is no injection surface.
Finding: two of the three stated behaviours are unpinned, and one arm's title explains why
| mutation | result |
|---|---|
| hook made conditional | 1 failed — the load-order arm, by name |
run-hosted hide condition removed |
1 failed — ""Run an agent here" goes away once the viewer holds a hosted agent" |
location.key once-guard removed |
green |
navigate(..., state: null) removed |
green |
BASE 16, RESTORED 16.
The arm titled "the draft arrives, focused, once" asserts exactly one thing:
await waitFor(() => expect(composer).toHaveValue('@maya-agent '));No focus assertion, no rerender, no call count, nothing about the cleared state. The title names three properties and pins one — which is precisely why both green mutations are green.
Of the two, the history clear is the real gap: "dropped from history so a reload does not refill it" is the stated reason that line exists, and zero arms mention state: null or replace: true. The once-guard is softer — the deps array is [location.key, pod?._id], so the effect only re-runs when location.key changes, which is exactly what the guard re-tests; it reads as belt-and-braces rather than load-bearing. Asserting document.activeElement and a rerender-without-refill would close both and make the title honest.
Not blocking: the code does the right thing today, and the crash — the one failure that would have been visible to a stranger — is the one that is pinned.
Separately
Loops closed for the two that merged while I was here: #2264 (b53ab7d27) and #2265 (cac8ea3e1), both line-for-line identical to the heads I gated (103 and 242 lines), Lily, no stray trailers.
And for the record, since #2265 merged with it open: the safeNextPath asymmetry I flagged there is still on main — the backend copy has 0 normalisation checks against the frontend's 2. Expected, as I filed it non-blocking, but it is now live rather than hypothetical.
GATE: PASS
QA walk 4 (person-paced, production
ccd40467) measured page visible → live agent at 1:27 and the first real answer at ~2:31. The product's own waits were 7.7 s. Reading (64 s) and typing (60 s) were the rest. This PR addresses the three levers Cofounder ranked; the target for the next walk is page → first answer under 90 s.1. Bring your own via a "Run it here" link: 130 words → ~33 before the button
A person who arrives with
?mode=hostedhas already chosen hosting, so the page now shows only:"Other ways to connect" opens the full page with hosting still chosen.
How it behaves around loading:
/api/hosted/availabilitysays the instance can host. This keeps feat(first-session): Scout answers "can Commonly host it?" with a Run it here button, and quotes the limits the meter enforces #2250's "no hosted act before that answer" rule.2. The composer is already addressed to the new agent
Typing the 32-character handle cost 9.6 s. "Go to your pod" now passes
@<agent>in navigation state. The thread fills and focuses the composer once per navigation, then drops the state from history, so a reload doesn't refill it.Caught in the real browser, not by jsdom: my first version put this effect below V2Thread's
!podearly return, and the pod page crashed ("Rendered more hooks than during the previous render") when the pod loaded after the first render. The effect now sits above the early return, and a load-order test renders the loading state first; moving the effect back below fails it.3. Scout stops offering a slot that's taken
Scout's "Run an agent here" start, and Scout's hosted-offer button (the same
run-hostedid), stayed under the welcome after the agent was live. The agent-states roster now says which agents are hosted (hosted: runtimeType === 'hosted'). The thread hides therun-hostedstart once the viewer owns one in the pod. Someone else's hosted agent doesn't hide it, and the other starts stay.Verified in a real browser
Chromium, with this branch's frontend (vite) against the production API, on a test account:
@growth-welcome-10160438-agent, focused, no crashThe hidden start needs the backend's new
hostedfield, so it's checked on production after deploy.Copy and design
v2-byo__link, with the same declarations asv2-team__link. No new tokens, colours or radii.Tests
V2AgentBYOHosted:composeDraft.V2ThreadStarts:agentStateService: the hosted flag is set for hosted installs only.tscand eslint are clean.🤖 Generated with Claude Code
https://claude.ai/code/session_0157AG2wo8mJYqKezXNs48Ya