Skip to content

feat(first-session): the hosted path drops 100 words of reading, addresses the composer, and stops offering a slot that's taken - #2266

Merged
lilyshen0722 merged 1 commit into
mainfrom
growth/hosted-first-minute-2
Oct 16, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
growth/hosted-first-minute-2

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

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=hosted has already chosen hosting, so the page now shows only:

  • "Run an agent here", with a 20-word lead;
  • one line saying where it lands ("It joins My Workspace as ." with a Change link);
  • the Run it here button;
  • the limits line.

"Other ways to connect" opens the full page with hosting still chosen.

How it behaves around loading:

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 !pod early 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-hosted id), 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 the run-hosted start 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:

check result
compact page, words before the button ~33 (head 26 + "joins" line 7), down from 130
button position y=278 at 1440x900, y=377 at 390x844, in view, no horizontal scroll
press → Go to your pod composer reads @growth-welcome-10160438-agent , focused, no crash
reload the pod composer empty (state dropped)

The hidden start needs the backend's new hosted field, so it's checked on production after deploy.

Copy and design

  • en + zh-CN, per the zh-cn-ui-localization glossary (智能体, 托管, 你); the zh limits line matches the existing format.
  • One text-link style, v2-byo__link, with the same declarations as v2-team__link. No new tokens, colours or radii.

Tests

  • V2AgentBYOHosted:
    • the compact page: title, joins line, limits, none of the full page's copy, one click to live;
    • "Other ways to connect" opens the full page with hosting still chosen;
    • the act is disabled until hosting is confirmed;
    • "Go to your pod" carries composeDraft.
  • V2ThreadStarts:
    • the start is hidden only when the viewer holds a hosted agent;
    • it stays when someone else holds one;
    • the draft is applied once;
    • the draft survives a late pod load.
  • agentStateService: the hosted flag is set for hosted installs only.
  • Mutations: each of these fails an arm:
    • no compact view;
    • no draft on "Go to your pod";
    • start not hidden;
    • no prefill;
    • roster never hosted;
    • the effect below the early return.
  • Ran locally on Node 22: 12 frontend suites (327 tests, including i18n keys and layout invariants) and the agent-state suites, all green. tsc and eslint are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_0157AG2wo8mJYqKezXNs48Ya

…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 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@lilyshen0722
lilyshen0722 added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit e0dcc1a Oct 16, 2026
17 checks passed
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