Motivation
Writers often want the model to propose several alternatives for the same passage/file (e.g. "give me 3 versions of this intro"), then flip through them and pick one. Today this is impossible by design.
Current behavior (the blocker)
PendingChangesStore.addChanges enforces at most one pending update per (path, thread): staging a second update to the same file in the same chat auto-rejects the first (src/stores/pendingChangesStore.svelte.ts:198, dedup loop). That's the correct "the model revised its edit, keep the latest" model — but it's the exact opposite of "keep N alternatives and let me choose".
Both diff surfaces also assume a single active update: inline editor and reading view both take getPendingUpdatesForPath(path).at(-1) (src/editor/inlineDiffExtension.ts, src/editor/readingViewDiffProcessor.ts).
Proposed design (decided)
Trigger: the model marks variants explicitly — a normal update still dedups (common case stays clean); only updates tagged as part of an alternative-set coexist. Likely a new tool arg or a dedicated propose_variants tool; the tool description must invite it or the model won't use it.
Data model: introduce a variantGroupId (or reuse toolCallId when a set is proposed in one call) so the store can group alternatives for one path. Variant-tagged updates skip the auto-reject dedup and coexist.
Mutual exclusivity on accept: accepting variant B auto-rejects its siblings (A, C, …) for that path. The rejection mechanism already exists (dedup uses it); it just fires on accept-of-sibling instead of on stage-of-newer.
UI — loop controls:
PendingChangesBar row for a variant set becomes "1 of N ◂ ▸" with prev/next; the DiffView re-renders against the selected variant's newContent.
- In-note inline diff renders the selected variant (instead of
.at(-1)), ideally with the same prev/next affordance (can be a follow-up — bar-only looping ships the core value first).
Scope notes / follow-ups
- Keep normal iterative edits deduping — variants must be opt-in and explicitly marked.
- Reading-view processor renders per-section; a looping control there is awkward — inline editor + bar are the primary surfaces.
- Interacts with the just-added cross-thread pending-update banner/badge (
countOtherThreadsPendingUpdate); make sure variant grouping and cross-thread counting stay distinct concepts.
Suggested phasing
- Store:
variantGroupId, coexistence for tagged updates, mutual-exclusion accept.
- Tool: variant-tagging arg + description.
- UI: prev/next in
PendingChangesBar.
- (Follow-up) prev/next in the in-note inline diff.
Motivation
Writers often want the model to propose several alternatives for the same passage/file (e.g. "give me 3 versions of this intro"), then flip through them and pick one. Today this is impossible by design.
Current behavior (the blocker)
PendingChangesStore.addChangesenforces at most one pendingupdateper (path, thread): staging a second update to the same file in the same chat auto-rejects the first (src/stores/pendingChangesStore.svelte.ts:198, dedup loop). That's the correct "the model revised its edit, keep the latest" model — but it's the exact opposite of "keep N alternatives and let me choose".Both diff surfaces also assume a single active update: inline editor and reading view both take
getPendingUpdatesForPath(path).at(-1)(src/editor/inlineDiffExtension.ts,src/editor/readingViewDiffProcessor.ts).Proposed design (decided)
Trigger: the model marks variants explicitly — a normal
updatestill dedups (common case stays clean); only updates tagged as part of an alternative-set coexist. Likely a new tool arg or a dedicatedpropose_variantstool; the tool description must invite it or the model won't use it.Data model: introduce a
variantGroupId(or reusetoolCallIdwhen a set is proposed in one call) so the store can group alternatives for one path. Variant-tagged updates skip the auto-reject dedup and coexist.Mutual exclusivity on accept: accepting variant B auto-rejects its siblings (A, C, …) for that path. The rejection mechanism already exists (dedup uses it); it just fires on accept-of-sibling instead of on stage-of-newer.
UI — loop controls:
PendingChangesBarrow for a variant set becomes "1 of N ◂ ▸" with prev/next; theDiffViewre-renders against the selected variant'snewContent..at(-1)), ideally with the same prev/next affordance (can be a follow-up — bar-only looping ships the core value first).Scope notes / follow-ups
countOtherThreadsPendingUpdate); make sure variant grouping and cross-thread counting stay distinct concepts.Suggested phasing
variantGroupId, coexistence for tagged updates, mutual-exclusion accept.PendingChangesBar.