Skip to content

feat: mark provider switches in the timeline and stack thread provider icons - #2275

Merged
chuks-qua merged 15 commits into
mainfrom
feat/multi-provider-thread-icons
Oct 21, 2026
Merged

chuks-qua merged 15 commits into
mainfrom
feat/multi-provider-thread-icons

Conversation

@chuks-qua

@chuks-qua chuks-qua commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

Closes #2234.

Before this change, a provider switch left no trace in the timeline, and the thread kept one provider icon.

Now a switched thread shows the following:

  • A marker. "Claude → Codex" appears between the switching prompt and the first reply from the new provider, with both provider icons. It names the switch and nothing else.
  • A stacked sidebar icon. The thread row in the project tree stacks the current provider in front of the previous one, inside the same 16px slot, so titles stay aligned.

A thread that never switched renders exactly as before.

commitProviderSwitch (one transaction)
  threads.providers_used   [claude] -> [claude, codex]   least recently used first
  messages (role: system)  "Switched from Claude to Codex" + provider_switch {from, to, handoffId}
broadcast
  thread.providerSwitched  patches the row, appends the marker once
  thread.modelUpdated      unchanged, for older clients

The marker's sequence is reserved when the switch is armed, so it stays before the reply even when the save lands after the reply was accepted. The handoff audit is still written to disk under the id the marker carries, for debugging.

Verified live in Electron with a real Claude Haiku turn followed by a GPT-6 Astra turn in .dev/fixture-repo. The marker reads "Claude→Codex" and the thread row stacks both icons. A thread switched on the base build, which has no providers_used, still shows no marker and one icon. Targeted tests pass: the server handoff and switch suites at 104 of 104, the web conversation and store suites at 1405 of 1405, and shared at 162 of 162, with bun run typecheck at 10 of 10 tasks.

Why

When a user moves a running thread from one provider to another, they need to see where the switch happened. Without that, a reply from Codex reads as if Claude wrote it. The sidebar stack lets users spot mixed-provider threads without opening them.

UI Changes

Before: a Claude to Codex switch on main. There is no marker, replies carry no provider, and the sidebar shows one icon.

Before: switched thread with no marker and one sidebar icon

After: the same flow on this branch, with the marker and the stacked sidebar icon.

After: Claude to Codex marker and stacked sidebar icon

Review Notes

  • Database migration. 0096 adds two nullable columns: messages.provider_switch and threads.providers_used. Stable and nightly builds share the database. The marker is a plain role: "system" message rather than a new systemNotice kind because older builds parse systemNotice strictly. Older builds show the marker as plain text and ignore both columns.
  • Security. Handoff paths are hardened. A thread id now goes through validateThreadId, which rejects any .., and a handoff directory name must be a ULID. Tests cover traversal attempts.
  • No backfill. Switches made before this change have no marker and no providers_used.
  • Known gaps for follow-up.

Moves the append-once-loaded helper into the thread store so the switch marker, like a revert receipt, shows once whether the push or the load wins. Adds readHandoff for handoff.read.
The sidebar stack draws the provider before the current one from the
newest other entry. First-use order picked the wrong provider after a
return trip such as Claude to Codex to Cursor and back to Codex.
@chuks-qua
chuks-qua merged commit 4b4bfdc into main Oct 21, 2026
14 checks passed
@chuks-qua
chuks-qua deleted the feat/multi-provider-thread-icons branch October 21, 2026 15:48
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.

feat(PSW-04): timeline marker for a provider switch

1 participant