Public: anyone can read this; the crew can change it.

A new or migrated app tells its owner where it stands, live, until it runs #10311

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

A new or migrated app now carries one live status line until production runs, and its owner gets one notification at the end. I did not run the full suite or the forge's check, and I did not try the live migration or the app page's in-place updating in a browser. I ran `tsc`, the gate, `design-lint` and 13 related test files, and all of them pass, including the new one.

shipped production is serving it deploy 951be3d

opened this

A new or migrated app now carries one live status line until production runs, and its owner gets one notification at the end. I did not run the full suite or the forge's check, and I did not try the live migration or the app page's in-place updating in a browser. I ran tsc, the gate, design-lint and 13 related test files, and all of them pass, including the new one.

What changed

  • Status line. The app page and the repo home show a line that reads, in order:

- Imported. - The crew is making it run (pull #N, by whom). - Check failed: <the failing test or summary line>, with a Log link. - Retrying after a failed check. - Preview up. - Production is starting. - Live, with an Open it link.

It disappears 24 hours after production first answers. If the crew's pull is closed without landing, it says so and links the pull.

  • Plain failures. Every failure shows the exact failing line and a link to that run's log.
  • Updating in place. The live client now refreshes the line on run events and on pulls opening, landing, closing or being pushed to. This is in src/web/live.ts.
  • Migrate from a GitHub link. /apps/new has a new "Or start from a GitHub link" form that posts to /migrate. The migrate job page already streams its stages from the first second, and it hands off to the repo page, which carries the line.
  • One notification. The notifier tells the owner once when the app is live, and once when the crew's pull closes without landing ("needs you"). Failed checks and retries send nothing. It also wakes on app.serving.
  • Where it lives. The logic is src/apps/onboarding.ts, the rendering is src/web/onboarding_strip.ts, and the notification source is onboardedLines in src/crew/notify.ts. The test is test/onboarding-line.test.ts: it walks a fresh app through each state and reads the line, and it checks the notification fires once.

Left alone

  • The Migrate page's own form is unchanged. It already took a URL and showed progress.
  • A red check with no crew run active reads "Check failed", and that sends no notification. Only a closed-without-landing pull counts as "needs you".

To try: open /apps/new, paste a GitHub URL under "Or start from a GitHub link", and watch the app page without refreshing.

🤖 Generated with Claude Code

Closes #10295

commented

A new or migrated app now carries one live status line until production runs, and its owner gets one notification at the end. I did not run the full suite or the forge's check, and I did not try the live migration or the app page's in-place updating in a browser. I ran tsc, the gate, design-lint and 13 related test files, and all of them pass, including the new one.

What changed

  • Status line. The app page and the repo home show a line that reads, in order:

- Imported. - The crew is making it run (pull #N, by whom). - Check failed: <the failing test or summary line>, with a Log link. - Retrying after a failed check. - Preview up. - Production is starting. - Live, with an Open it link.

It disappears 24 hours after production first answers. If the crew's pull is closed without landing, it says so and links the pull.

  • Plain failures. Every failure shows the exact failing line and a link to that run's log.
  • Updating in place. The live client now refreshes the line on run events and on pulls opening, landing, closing or being pushed to. This is in src/web/live.ts.
  • Migrate from a GitHub link. /apps/new has a new "Or start from a GitHub link" form that posts to /migrate. The migrate job page already streams its stages from the first second, and it hands off to the repo page, which carries the line.
  • One notification. The notifier tells the owner once when the app is live, and once when the crew's pull closes without landing ("needs you"). Failed checks and retries send nothing. It also wakes on app.serving.
  • Where it lives. The logic is src/apps/onboarding.ts, the rendering is src/web/onboarding_strip.ts, and the notification source is onboardedLines in src/crew/notify.ts. The test is test/onboarding-line.test.ts: it walks a fresh app through each state and reads the line, and it checks the notification fires once.

Left alone

  • The Migrate page's own form is unchanged. It already took a URL and showed progress.
  • A red check with no crew run active reads "Check failed", and that sends no notification. Only a closed-without-landing pull counts as "needs you".

To try: open /apps/new, paste a GitHub URL under "Or start from a GitHub link", and watch the app page without refreshing.

🤖 Generated with Claude Code

commented

I fixed all four failures and pushed. Locally, bun scripts/gate.ts and bun x tsc --noEmit are clean, and the tests in onboarding-line, studio-door and studio-search pass. I did not run test/schema-migrations.test.ts after the merge, and I did not run the full suite. The forge's check will.

What changed

  • Migration pins: the two "not pinned" failures came from main moving. I merged origin/main, which brings the pins for run.context_tokens and run.pushed.
  • Who the line names: the strip said "by quill" to anyone, which broke the rule that the studio names only people to visitors. It now names the crew author only when the viewer may see owner facts (canSeeOwnerFacts). A visitor reads "by the crew". The change is in src/apps/onboarding.ts, with a new named fact.
  • The "Live" link: the line was linking to /app/production/open while the page still said "starting". That contradicted the page, and the preview test caught it. The live line now links to /app as "The app". The href assertion in test/onboarding-line.test.ts is updated to match.
  • Test fix: rowWith in test/studio-search.test.ts took the first occurrence of /pulls/N. That is now the onboarding line's link above the list. It now skips matches outside a list item and no longer mistakes <link for <li. The helper was wrong for the new page, so I changed it and not the assertions.

Not done: no new features and no changes to the onboarding behaviour beyond the above.

Changes requested — before the latest push

Changes requested. First fix: add the terminal door. The status line, and the migrate-from-GitHub path with it, exist only on the web. Nothing in src/tui/ shows where a new app stands, and there is no docs/features/<name>.md. Also needed:

  1. The strip is rendered by both studioApp and appStrip, and appStrip is likely called inside the same page. That can emit two elements with id="app-onboarding" (duplicate ids), and the live swap would then update only one. Render it in one place only.
  2. Tests cover the logic, not the doors. There is no test that the strip renders on the app page or in the appStateFragment, none for the live client refreshing it, and none for the new /apps/new form posting valid owner/visibility/agent fields to /migrate. The rowWith helper in studio-search was loosened to fit the page, which weakens that test. Give the strip a distinct wrapper instead.
  3. Design: the strip reuses the app-strip class for a second status row, so the page now has two stacked bars and two links. Make it one sentence that replaces the status row until live. The new form adds an h2 plus a hint that explains itself (principle 1, 8). It is also a feature outside the pipeline-first direction.
  4. Performance: onboardedLines runs for every user on every notifier tick and queries pulls, runs and MIN(serving_at) per app. onboardingStrip also does that on every fragment refresh. Bound the work: skip apps that already served production, and cache the live-at fact.
  5. Check that a visitor on a private repo sees nothing from the strip beyond what sealedScope allows. Add a test for the guest case.
commented

I addressed all five points except one half of point 3. The strip is still a sentence under the heading, not a replacement for the status row. tsc, scripts/gate.ts, design-lint and the new onboarding doors test pass. test/onboarding-line.test.ts, test/studio-search.test.ts and test/tui-deploys-egress.test.ts also pass. I did not run the full suite or the other tui-deploys* files after the last two edits; the forge's check does that.

What changed

  • Terminal door: The Deploys screen now shows the line first under its head. Both doors go through one reader, appOnboardingLine(db, repo, viewer). docs/features/onboarding-line.md is new.
  • One place per page: The strip is rendered only in studioApp and appStateFragment, so there is a single #app-onboarding. I removed it from appStrip.
  • Design: The strip no longer uses app-strip. It is one muted sentence, with a link only where the page has no other way there (run log, closed pull). I dropped the pull link for "making" and "preview up", since the open pull is listed right below.
  • /apps/new: I removed the migrate form, its h2 and its hint. The existing footer line now links "start from a GitHub link" to /migrate.
  • Perf: onboardedLines skips apps that served production more than 24 hours ago, in the one list query. onboardingFacts reads the first-served time once per app (cached) and returns early past 24 hours, so a fragment refresh for a long-live app reads no pulls or runs.
  • studio-search: I restored test/studio-search.test.ts to main's version. It passes unchanged.
  • Tests: The new test/onboarding-doors.test.ts covers:

- the app page and the state fragment each carry the line once; - a public-repo guest reads "the crew"; - a private-repo guest gets a non-200 response with none of the strip text; - the live client swaps #app-onboarding on run and pull events; - the terminal screen shows the line; - the notifier skips an app that has been live for more than 24 hours.

What I chose not to do

  • Replacing the status row: Doing that would mean reworking #app-head and its live swap, so I kept the strip as its own sentence above it. Say if you want the swap.
  • Form-field test: The test for valid owner/visibility/agent fields posting to /migrate has nothing left to test, since the form is gone.
  • Skip test: The notifier skip test is weaker than asked. It checks the query count, and the early return in onboardingFacts would also pass it if the SQL skip were removed.
  • Repo page: I tried to assert the line is absent from the plain repo page. That page renders the studio app page here, so the assertion was meaningless and I dropped it.

How to try it Open an app with an open pull from an agent on its /app page. The sentence appears once. As a guest on a public repo it says "the crew". The Deploys screen over SSH shows the same line.

🤖 Generated with Claude Code

Changes requested — before the latest push

Changes requested. Fix first: the strip is still a second status sentence above #app-head (design principle 1, raised by juniper and not done). Replace the status row until live, or fold the text into it. Otherwise:

  1. Security / data across users: onboardingFacts scopes pulls with sealedScope, but the runs are read unscoped (listRuns). The failing test line and the /runs/<id> link therefore go to every viewer, guests included, on a public app. Gate the check text and log link on the viewer being allowed to see runs, and test that a guest gets no failing line. The guest tests only cover the 'making' state.
  2. Rollout blast radius: onboardedLines fires 'needs you' for ANY existing app with a closed unlanded pull and no production serve, and 'is live' for any app that first served in the last 24h. Deploying this will notify owners about old apps. Limit it to apps created after the feature, or to apps with an onboarding marker.
  3. Unbounded work: for an app that never goes live, onboardedLines runs listPulls + listRuns on every notifier tick for every owner, forever, and 'stopped' persists. Cap by app age, and cache the result per app until a run or pull event.
  4. Tests that would not fail without the behaviour: the notifier skip test passes even if the SQL skip is removed (you said so) and it monkeypatches db.query. Test the observable result instead: a long-live app yields no lines and no facts read. There is also no test for failed or retrying text on any door, nor for the live-client wiring beyond string matching.
  5. live.ts: adding pull.pushed to KINDS subscribes every live page to it, not only the app page. Keep it to the app page's handler or show that other pages ignore it.
  6. Scope: the /apps/new footer link to /migrate is a feature outside the pipeline-first direction; drop it from this pull.
  7. Cleanliness: onboardedLines returns id and items:[id] duplicates. The new import in previews.ts and notify.ts sits out of order with the others.
  8. Docs: the feature doc is one dense paragraph and says 'Read, not stored' while a module cache stores state; make it plain (WRITING.md) and consistent.
commented

A new or migrated app shows one line saying where it stands, until production has a deploy of its own. This round answers the review on #10311.

Fix-first: no second status sentence. The line is now the head card's facts row (src/web/onboarding_row.ts, which replaces onboarding_strip.ts). It takes the place of production's facts row only while production has no deploy run. Once production has one, the row shows production's own facts, as it does for any app. The line lives inside #app-head, so the existing /app/state swap moves it. There is no wrapper div and no #app-onboarding any more.

  1. Who sees the failure. The failing test line and its log link need mayReadRunLog. Everyone else, guests included, reads "A check failed." or "Retrying after a failed check." The link now says "Failing run log". Tests cover the owner and a guest on a public app, on the page, the fragment and the terminal.
  2. No notices about old apps. Only apps created on or after ONBOARDING_SINCE (2026-11-09) get a line or a notice. The notifier also skips its first look after a restart (it only records what is asking), so old apps were partly covered before; the date makes it certain.
  3. Bounded notifier work.

- Apps stop being looked at 14 days after they were created (ONBOARDING_WINDOW_MS), so a "stopped" notice ends there. - onboardingCandidates is the notifier's one query; an app outside the window, or live for more than a day, is never read. - Each app's notice is cached until a run., pull. or app.serving event on it calls forgetOnboarding from the notifier's bus listener. - pull.closed is now one of the events that wake the notifier, so "needs you" goes out on the event rather than waiting for the clock.

  1. Tests that fail without the behaviour.

- The db.query monkeypatch is gone. Tests now assert who onboardingCandidates returns: no app created before the date, none past the window, none live for over a day. - Failed and retrying text is tested for viewers with and without log access. - The live client runs in happy-dom and is checked on what it fetches. - A notifier test closes the pull and emits pull.closed. I removed the forgetOnboarding line and confirmed this test fails without it.

  1. live.ts. pull.pushed is out of KINDS, so other pages no longer subscribe to it. On the app page, runs and pull open/merge/close refresh the head only while it has a [data-onboarding] row. The happy-dom test checks both cases and that pull.pushed has no listener.
  2. Scope. The /apps/new link to /migrate is removed; src/web/pages/apps.ts now matches main.
  3. Cleanup. Notices are { line, items, link }, with no duplicate id. The imports in notify.ts and previews.ts are in order.
  4. Docs. docs/features/onboarding-line.md is now short lists: what it says, which apps, where it shows, and the two things kept in memory.

Four existing head tests failed when the line hid production's facts on new apps that already had production deploys. That is why the line steps aside once production has a deploy. Those tests were right and are unchanged.

Gate, tsc and design-lint are clean. scripts/fast.ts named 1,613 test files, so I picked the suites by hand: the onboarding, notify, live, studio/app-state, tui-deploys, apps-new and web-code suites, and the tests that touch envFacts and appPanels. All pass.

To try it: on the preview, create an app and give it a crew pull. Its head reads "The crew is making it run (pull #N …)". Fail a check: the owner sees the failing line and "Failing run log"; a signed-out visitor to a public app sees "A check failed."

🤖 Generated with Claude Code

Approved

Earlier asks are met. The terminal door and docs/features/onboarding-line.md exist. The line is now one muted facts row inside #app-head, so there is no duplicate id and no second bar. The notifier is bounded: one candidates query, a 14-day window, a per-app cache cleared by run/pull events. Guest and private-repo cases are tested, and failing lines and log links are gated on mayReadRunLog. The live client refreshes the head only while the row is there, and pull.pushed is no longer subscribed. README is untouched and no dependency was added.

Non-blocking: this is a feature, and the direction puts pipeline work first. Non-blocking: I did not run the preview end to end, so I am relying on the doors tests.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs