Skip to content

cmux NEXT: a one-time sidebar note and an onboarding line for Import from Conductor - #36453

Merged
cmux-bot[bot] merged 6 commits into
feat-cmux-nextfrom
lane/conductor-import-notes
Nov 5, 2026
Merged

cmux-bot[bot] merged 6 commits into
feat-cmux-nextfrom
lane/conductor-import-notes

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-11-05, on the ranked list of next moves (Import from Conductor is #3): "i meant can you do all of htem".

The spec for this move asks for "one onboarding line, Settings → Import next to browser import, and a one-time sidebar note with the Nov 20 deadline". #36364 adds Settings › Import. This PR adds the other two. It is stacked on #36364, which is stacked on #36389, and it lands after both.

  • Sidebar note. It uses the shared notice card:
    • Text: "Bring your Conductor workspaces", then "Conductor Cloud shuts down on November 20. Settings › Import opens your workspaces here, in place."
    • Open Import opens Settings › Import, the same path as import.open.
    • It shows only when all of these hold: the import scan found Conductor on this Mac, the import cards are on, and it is before Nov 20 (midnight Pacific).
    • It stays gone once the person closes it, starts the import, or hides the Conductor card in Settings › Import. Done after an import also hides that card.
    • Closing it is kept in the import cards' own saved state, as a new optional field, so older saved state still loads.
    • In the card order, it comes after the update status and the test-feed notice and waits for the "cmux Updated!" card. It comes before announcements and the daily tip because it is the only one with a deadline.
  • Onboarding line. The first-run "Bring your history" page (Chief: a first-run Bring your history screen #33215) gets one muted line when this Mac has Conductor and it is before the deadline: "Conductor workspaces: import them from Settings › Import before Conductor Cloud shuts down on November 20." The App reads it off the main thread and passes it in with the page's detection.
  • Not touched: the New Tab page and the Chief's front page. cmux NEXT: no "Bring your stuff to cmux" import cards on the New Tab page #24352 took import cards off those on purpose.

How it is built:

  • The decision and the strings live in CmuxNextImportSuggestions, and the card and its buttons in CmuxNextImportCards, which now depends on CmuxNextSidebar for the card type.
  • The App adds wiring only. ClassicSnapshotRead moves to CmuxNextOnboarding, so the App shrinks by one line.
  • The new strings are in the ImportSuggestions table, translated into all 20 other locales.

Observed / Not run

  • Observed:
    • prepush-gate: no new violations.
    • lint-ios-package-conventions.sh is clean.
    • scripts/web-lint.sh and bun run check in webviews/ pass. model.test.ts passes 18 of 18, including the new notice test.
    • The ImportSuggestions catalog has no parity errors.
  • Observed on the fleet at 08b9c45:
    • the app compile check passed;
    • 8 of 8 Swift suites passed: ConductorNoteTests (Conductor found, not imported, before the deadline; no Conductor; at and after the deadline with an injected clock; closed, including across save and load; a running or done job; a hidden card; older saved state; the onboarding line with and without Conductor), ConductorImportWireTests, ImportScannerTests, ImportCardDeckTests, BrowserImportEnabledTests, ImportSuggestionRemovedTests, ImportSuggestionsCenterClassicTests, ClassicImportTests.
  • Not run: a gallery state for the page line, a capture, and a native speaker's check of the translations.

No capture: the note and the line show only on a Mac with Conductor data; a capture follows once the stack is in.

Changelog

Added: A one-time sidebar note and a first-run line point people with Conductor to Settings › Import before Conductor Cloud shuts down on November 20.

🤖 Generated with Claude Code

Agent session: agent session

teamleaderleo and others added 3 commits November 5, 2026 10:43
…tor on the daemon

conductor_import.plan (read) finds Conductor under the daemon's HOME and
returns {found, plan}: the cmux-conductor-import dry run against the
stored ledger and the workspaces open now (catalog_from_folder when
conductor.db is gone); plan is null when nothing is found, and error says
why when there is no plan (an unreadable conductor.db, or a ledger from a
newer cmux, which is left as it is).

conductor_import.record (mutation, idempotent) sets one workspace's
stage, pending (with an optional half-made cmux workspace) or done, and
run_completed counts a run. A done workspace is named by its key (the key
the app created it with) or its public id; the plan counts it open by either. The ledger is one JSON row in the personal
state store, committed and replayed like every state mutation.

Pages and apps never reach either op; MCP excludes them (the app's
Import from Conductor drives these). Capability conductor-import-v1.

Tests: cmux-tui-core state::conductor_import_tests (4) and cmux-tui
--test conductor_import_daemon over a real headless daemon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293
Settings › Import gets a Conductor card when this Mac has Conductor's database or
~/conductor/workspaces. Import asks the daemon for its dry-run plan
(conductor_import.plan), shows it (projects, the workspaces opened in place with their
branches, Claude Code chats found, archived workspaces left out, env var names only, the
archive-deletes-folder warning and the Nov 20 deadline), and on confirm adds each repository
as a project and opens each live workspace at its own folder, recording each one in the
import's ledger before and after (conductor_import.record), so a rerun makes nothing twice
and a cut-off run's half-made workspace is replaced.

The flow lives in CmuxNextImportCards behind ConductorImportHost; the App adds one-line
forwards only. ClassicImportPlan moves to CmuxNextOnboarding so the App does not grow.
Strings are in the ImportSuggestions table in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293
…from Conductor

Leo, 2026-11-05: "i meant can you do all of htem" (the ranked list of moves; #3 is Import
from Conductor). Conductor Cloud goes read-only on November 13 and shuts down on November 20,
and the spec asks for "one onboarding line, Settings → Import next to browser import, and a
one-time sidebar note with the Nov 20 deadline". Settings › Import came with the previous
change; this adds the other two.

The sidebar note is the shared notice card ("Bring your Conductor workspaces", the
November 20 deadline, Open Import, which opens Settings › Import). It shows only while the
import scan found Conductor on this Mac (agents.conductor), before 2026-11-20 (midnight
Pacific), and until the person closes it, starts the import, or hides the Conductor card in
Settings › Import (its Done after the import does). Its x closes it for good, kept in the
import cards' own saved state (ImportSuggestionState, a new optional field, so older saved
state still loads). In the card chooser it comes after the update status and the test-feed
notice and waits for the "cmux Updated!" card, like the rest, and comes before
announcements and the daily tip: it is the one time-limited message of the three.

The first-run Bring your history page gets one line under its sources when this Mac has
Conductor, before the deadline: "Conductor workspaces: import them from Settings › Import
before Conductor Cloud shuts down on November 20." The page's provider takes the line from
the App (read off the main thread) and returns it with its detection. Nothing goes on the
New Tab page or the Chief's front page.

The decision and the strings live in CmuxNextImportSuggestions, the card in
CmuxNextImportCards (now on CmuxNextSidebar for the notice card's type); the App adds the
wiring only, and ClassicSnapshotRead moves to CmuxNextOnboarding so the App does not grow.
Strings are in the ImportSuggestions table in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@github-actions github-actions Bot added risk:high Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Nov 5, 2026

@teamleaderleo teamleaderleo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review (gpt-6-astra): SHIP at 08b9c45

…tor-import-app

# Conflicts:
#	cmux-tui/bindings/cpp/.cmux-resource-api.json
#	cmux-tui/bindings/go/.cmux-resource-api.json
#	cmux-tui/bindings/java/.cmux-resource-api.json
#	cmux-tui/bindings/python/.cmux-resource-api.json
#	cmux-tui/bindings/rust/.cmux-resource-api.json
#	cmux-tui/bindings/typescript/.cmux-resource-api.json
#	cmux-tui/bindings/zig/.cmux-resource-api.json
@teamleaderleo
teamleaderleo marked this pull request as ready for review November 5, 2026 18:33
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review b940ac4: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr36453 -f ref=lane/conductor-import-notes -f platform=macos -f backend_mode=local

Gallery before/after for b940ac4: 10 states rendered, 0 images differ. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 42085453454 -R manaflow-ai/cmux -n gallery-advisory-36453-b940ac4c5310582375bf221565cd23b6ad888ea2 (then read gallery-review.json)

States
State Theme Status Diff Picked by
pages.import-history/list Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/list Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/cards Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/cards Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/compact Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/compact Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/nothing-detected Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/nothing-detected Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/detecting Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/detecting Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/importing Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/importing Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/done Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/done Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/error Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/error Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/paste-summary Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/paste-summary Apple System Colors unchanged 0% own ImportHistory.tsx
pages.import-history/autodetected Apple System Colors Light unchanged 0% own ImportHistory.tsx
pages.import-history/autodetected Apple System Colors unchanged 0% own ImportHistory.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at b940ac4

Reviewer: codex gpt-6.1-sol xhigh, fast tier.

Follow-ups: 3. They land as checklist items in this PR's one follow-up tracking issue in this repo (Follow-ups: #N ..., label follow-up; add to an open one before filing, never one issue per finding), linking this receipt; the lane that opened the PR fixes them forward.

SHIP at b940ac4

Visual verdict: no images; Leo tries the build

  • [P2] [follow-up] [category:correctness] [finding:F1] [outcome:pending] The sidebar note and onboarding line only reevaluate on model changes or a new detection, so they can remain visible after Nov 20 while the app/page stays open (ImportSuggestionsCenter+ConductorNote.swift:13-18, ConductorNote.swift:29-31).
  • [P2] [follow-up] [category:correctness] [finding:F2] [outcome:pending] try? discards project.add failures; workspaces are then recorded done, so a transient failure can permanently leave the imported repository absent (ConductorImport.swift:65-69).
  • [P2] [follow-up] [category:correctness] [finding:F3] [outcome:pending] Finished jobs are pruned after 14 days, allowing the “one-time” Conductor note to reappear before the deadline (ImportJob.swift:173-179, ConductorNote.swift:14-18).

Follow-ups:

  • [finding:F1] Refresh deadline-driven notices while visible.
  • [finding:F2] Preserve or retry failed project registration.
  • [finding:F3] Persist Conductor completion independently of job pruning.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026

@cmux-bot cmux-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cmux-bot approves b940ac4: an independent review passed this exact head (router-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

@cmux-bot
cmux-bot Bot merged commit 93e291b into feat-cmux-next Nov 5, 2026
269 of 274 checks passed
@cmux-bot
cmux-bot Bot deleted the lane/conductor-import-notes branch November 5, 2026 20:01
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
* Import from Conductor: the scan counts workspace folders, not files

Review follow-up on #36453 (the Conductor card): `ImportScanner.conductor()`
counted every visible entry under `~/conductor/workspaces/<repo>/` as a
workspace, so a file left there (a note, an editor's leftovers) raised the
card's count, and a file at the repo level was listed as a repo. It now
counts folders only, through `subfolders(_:)`, which asks for the directory
flag with the one directory read, so the scan stays one listing per folder.
It reads through CmuxNextCompat's checked seams (`FileManager.checked`,
`blockingResourceValues`), so CmuxNextImportSuggestions now lists
CmuxNextCompat, already in its graph through CmuxNextBrowserImport.

Leo, 2026-11-05: "i meant can you do all of htem" (Import from Conductor is

Test: ImportScannerTests.conductorCountsItsWorkspaceFolders gains a home
with a file at each level (counted 2 before, 1 now). Swift tests run on the
fleet, not here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: a ledger that breaks its invariants is unreadable

Review follow-up on #36326 / #36389: `Ledger::decode` (and so `usable` in
the daemon's router) took any ledger that parsed as usable. A row such as
`{"version":1,"runs":0,"workspaces":{"w":{"stage":"done"}}}` has a done
entry with no cmux workspace: the plan would read it as closed since and
offer it again, so a rerun could make the workspace twice. Decoding now
checks what every record keeps (a done entry names its cmux workspace; no
entry names an empty one) and returns `Load::Unreadable` with the reason,
so `conductor_import.plan` shows no plan and says why, and
`conductor_import.record` refuses (`validation.invalid`), as they already
did for a ledger that does not parse.

Tests (cargo, Linux): ledger::tests::a_done_entry_without_its_cmux_workspace_is_unreadable
and conductor_import_tests::conductor_import_refuses_a_damaged_ledger fail
with the check taken out and pass with it; cargo test -p
cmux-conductor-import, -p cmux-tui-core --lib conductor_import, -p cmux-tui
--test conductor_import_daemon and clippy -D warnings pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: a record never trades an open done workspace for another

Review follow-up on #36389: `Record::apply` replaced a workspace's ledger
entry unconditionally, so two overlapping runs (different idempotency
keys, both planning the workspace as one to adopt) could each make it and
the last done won, leaving a duplicate the ledger no longer knew about.

`conductor_import.record` now refuses (`validation.invalid`) a pending or
done record over an entry that is done while its cmux workspace is still
open, unless the record names that same workspace (by key or public id).
The check reads the open workspaces from the state `commit_state` hands
its apply step and runs inside its transaction, so two runs cannot both
pass it. Checking done as well as pending closes the interleaving where
both runs record pending before either records done: the second done is
refused. Unchanged: a done entry whose workspace was closed is offered
again and may be recorded over, pending replaces pending, and recording
the same workspace again is fine.

Tests (cargo, Linux): conductor_import_tests::conductor_import_record_keeps_an_open_done_workspace
fails with the check taken out (the second run's pending is accepted) and
passes with it; the crate, core and daemon suites and clippy -D warnings
pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: the note and the onboarding line end on time and stay gone

Two review follow-ups on #36453 (the sidebar note and the first-run
history page line):

Deadline while visible. Both were evaluated only when their inputs
changed, so with cmux open across 2026-11-20T08:00Z they stayed up. The
center now schedules one DemandTimer deadline from its injected clock
(`scheduleConductorDeadline()`, from `start`), which sets an observed
`conductorDeadlinePassed` the note reads, so a note on screen is read
again and goes; started after the deadline it is gone at once. The history
page gets the line's end with it (`noticeUntil`, from
`ImportScanner.conductorOnboardingNotice(now:)`), and the page store sets
one timer to that moment and drops the line (a far end waits again from
setTimeout's limit). No timer that wakes repeatedly.

Note came back after pruning. The note's "the import ran" test read the
job ledger, which drops finished jobs after 14 days. A finished Conductor
import is now kept in `ImportSuggestionState.conductorImportRan`, a new
optional field like `conductorNoteClosed`, so older saved state still
loads; the note checks it. A done Conductor job already in the ledger sets
it when the jobs are restored, before the prune. A declined preview keeps
nothing, as before.

The App's only change is the one line that hands the page the notice
(same line count). CmuxNextImportCards now lists CmuxNextWakeups (for
DemandTimer), already in its graph through CmuxNextBrowserImport.

Tests: ConductorNoteTests.theNoteStaysGoneAfterTheFinishedJobIsPruned and
the older-state test's nil check; ImportSuggestionsCenterConductorTests
(the note goes at the deadline by an observation change with the clock
frozen before it, gone at once past it, a finished import keeps it gone
after its job is removed, a declined one does not);
ImportHistoryPageProviderTests.detectionSendsTheNoticeWithItsEnd; and the
webview model test "the host's line about Conductor goes at its end while
the page stays open" (fake timers), which fails with the timer taken out
and passes with it (bun test, 19 pass). The Swift tests run on the fleet,
not here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: the run stops on a failed close, keeps unadded repos importable, and previews whenever there is something to say

Three review follow-ups on #36453's `ConductorImport.importing(progress:)`:

Failed cleanup. Each workspace a cut-off run half made was closed with
`try?`, so a close that failed still went on to make its replacement and
left two. A failed close now stops the run (its toast, then the throw, as
every other failure); a workspace already gone (no key in the store, or
the daemon's `selector.not_found` for it) is no failure.

Ignored project adds. `try? projects.add` dropped errors, and that repo's
workspaces were then recorded done, so the repo never reached the project
list, even after Import Again. Chosen: a repository that cannot be added
keeps its workspaces unrecorded (they are skipped, so Import Again offers
them with it), the other repositories still come over, the run is not
counted complete, and it ends failed (`ConductorImportFailure.projects`).
Why not stop the whole run up front: a refusal of a path (the daemon
refuses some folders outright) repeats on every run, so one such repo
would block every other repo's import for good; skipping its workspaces
loses nothing and the failed toast still says something went wrong.
Project adds now use a per-run idempotency key, so a project removed
since an earlier run is added again rather than replayed as done.

Recoverable-only and project-only plans. The run toasted "Every Conductor
workspace is already open here." and skipped the preview whenever nothing
was adoptable, even with archived recoverable workspaces or repositories
not listed yet. It now reads the project list once and shows the preview
whenever there is a workspace to open, a repository to add, a recoverable
workspace to explain or a cut-off workspace to replace; "all here" stays
only for nothing at all. After a run that opened nothing with recoverable
workspaces left, the toast is the preview's existing recoverable line. No
new strings.

To test this without a daemon, the run's daemon calls go through
`ConductorImportDaemon` (`LiveConductorImportDaemon` on the App's
connection) and the preview through an injected confirm; the public init
and the App are unchanged.

Tests: ConductorImportRunTests (CmuxNextAppTests) with a fake daemon and
host: a failed cleanup close stops the run with nothing opened or
recorded; a cleanup workspace already gone is no failure; a failed project
add leaves its workspaces unrecorded while the other repo's open; a
project-only and a recoverable-only plan show the preview; nothing left
shows none. Each but the already-gone case fails against the old flow.
The Swift tests run on the fleet, not here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: a done record follows its own pending record

Review of #36784 (F1): two runs that both record pending before either
records done still each made a workspace, and only the earlier done was
refused after its workspace existed. `conductor_import.record` now refuses a
done record that names another workspace than the pending entry, and the app
closes the workspace it just made when its done record is refused, so the
ledger's workspace is the only one left open.

Leo: "i meant can you do all of htem"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

* Import from Conductor: close a made workspace only on a confirmed conflict

Review of #36784 at 512ab19 (F1): the run closed the workspace it made on
any failed done record, but a timeout or dropped connection can come after
the daemon kept the record, which closed a good import. The conflict refusal
now names the field `cmux_workspace`, and only that refusal closes the
workspace (and a close that fails then stops the run with its error). Any
other failure leaves it open; the ledger's pending entry still names it, so
the next run's plan cleans it up if the record was not kept.

Leo: "i meant can you do all of htem"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7927be6a5485e9de6293

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — b940ac4c Deployed Nov 5, 2026 by teamleaderleo via wait for same-key tree publisher #117077
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant