Cloud start: the clone row shows the seed's live progress

TE
4d ago
+464-22

Piece 4 of the Cloud project seed (leo-workspace design/cloud.md, "Getting the project onto the machine"), the clone row's live progress. Leo, on the start screen's line:

Your project folder is copied up.
we'll want to deal with the edge cases for this too

#30634 added cmux link seed push, #30812 made Cloud start and move use it, and #30517 gave the startup checklist its clone row with seed timing. This PR feeds the seed's own progress lines into that row while the copy runs. It is rebuilt on feat-cmux-next at #30517's merge. The seed_* clone timing and the failed answer forwarded before the guard are unchanged.

What the row shows

The page owns the words (CloudStartView.tsx, new cloudStartup.* keys in every locale; non-English copies marked needs_review):

  • upload: "Sending your changes (12 MB)";
  • the machine's job: "Cloning from GitHub · 41%", "Fetching from GitHub", "Applying your changes", "Checking out files", "Getting submodules", "Getting large files";
  • a copy: "Copying the folder instead · 1,204 of 8,310 files · 312 MB of 2.1 GB" (sizes and counts in the pane's language);
  • a finished copy shows only the check mark, plus "Copying the folder instead" if a seed fell back;
  • seed.needs_github: an "Install the cmux GitHub App to clone it next time" link under the row (https only). The real card is #30637.

The shared model (cloudStartProgress.ts):

  • clone.progress gains bytesTotal, filesDone and filesTotal;
  • clone gains fallback and installUrl;
  • the final answer keeps the live lines' fallback.

Swift stays thin, and CmuxNextApp does not grow

check-app-size --base: 117608 source lines before and after.

  • CmuxNextDaemon/Launch/StderrLines.swift: ProcessRunner.run(stderrLine:) hands the child's stderr over line by line. The child already writes to an unlinked temp file, not a pipe. A DispatchSource vnode watch on a dup'd descriptor reads appended bytes with pread.
    • Stop condition: at child exit the runner calls finish(). It drains to EOF on the reader's queue, delivers the last partial line, then cancels the source.
    • Cleanup: defer { cancel() } covers every throw or timeout, and the cancel handler closes the dup'd descriptor.
    • Lines are capped at 64 KiB. No timer, no polling.
  • CmuxNextCloud/Move/SeedProgress.swift:
    • parses {"progress":{...}} lines;
    • throttles them to one per 250 ms, with a phase change always going through;
    • carries a fallback's code and link onto the copy's lines;
    • feed runs the copy and forwards each value on the main actor through a newest-only stream, which ends when the copy returns or throws.
  • CmuxNextAgentPane: Step.cloneProgress and AgentPaneCloudStart.fields map a progress value to the page's clone fields.
  • CmuxNextApp: CloudStartChat wraps its existing copy in SeedProgress.feed, and CloudMoveCLI.startChatCopy passes stderrLine through. There is no as!.

The CSS is unchanged: the link reuses the row's detail and link classes, so there are no new design-token rows.

Tests

  • Web (cmux-ci test):
    • CloudStartView.test.tsx: the row's words for upload, clone, checkout, copy and complete, and the install link;
    • cloudStartProgress.test.ts: the new fields, the fallback kept into the final answer, a non-https link dropped;
    • check:design-tokens, check:catalog-keys, format, typecheck.
  • Swift: SeedProgressTests (parse, throttle, feed, field names) and cmux-ci compile-check --app --package CmuxNext.

Risk: this adds progress to a step that already runs. If no progress line arrives, the row shows #30517's state and timing as before.

Follow-ups: #30637 (the install card), #30741 (setup hint and status not wired), #30799 (resolved here: "complete" no longer shows raw).

Agent session: agent session

🤖 Generated with Claude Code