#46 (SLICE-3, accept): Accept all pending proposals in one gesture
Document-edit flow SLICE-3 — completes reach→review→accept
(specs/coauthoring-document-edit-flow.md §7.2, INV-42). A single "Accept all"
gesture applies every pending proposal on the current document through the
existing F4 acceptById seam (block proposals take the INV-40 word-precise path
automatically), in descending anchor order so an earlier accept never invalidates
a later one, skipping (never force-applying) proposals that can't anchor and
reporting applied-vs-skipped. Batched application of the existing accept path — no
new mechanism; the webview posts intent only (INV-35). No confirmation dialog
(undo restores).
- proposalController.ts: acceptAllProposals(document) → {applied, skipped}
(descending order, orphan-skip); accept/acceptById gain a silent opt so the
batch suppresses N per-proposal orphan warnings in favour of one report.
- trackChangesPreview.ts: ToolbarMsg += {type:"acceptAll"}; handleWebviewMessage
routes it to a public acceptAll(document) that batches + reports.
- extension.ts + package.json: cowriting.acceptAllProposals command (active doc,
markdown-gated palette entry) for the non-webview path.
- media/preview.ts + shellHtml: "✓✓ Accept all" toolbar button, posting the
intent, shown only with ≥2 pending proposals (authorable, on-state).
- trackChangesModel.ts: diffToBlockHunks now emits one block-aligned hunk per
CHANGED block even when changed blocks are ADJACENT (treats changed blocks as
1:1 anchors alongside unchanged ones; gap-spans only cover add/remove runs
between anchors) — fixes adjacent changed blocks collapsing into one proposal.
- f12Accept host E2E (apply-all reconstructs; orphan skip + report; single
proposal; command registered/gated); MANUAL-SMOKE-F12 §3.
214 unit + 73/5 host E2E green. Completes the document-edit-flow cluster
(#42 reach + #47 review + #46 accept).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -138,9 +138,40 @@ export class ProposalController implements vscode.Disposable {
|
||||
// ---- PUC-2/PUC-3: accept / reject (INV-11/INV-12) ----------------------------------
|
||||
|
||||
/** Accept by proposal id (test-facing twin of the thread-menu gesture). */
|
||||
async acceptById(docPath: string, proposalId: string): Promise<boolean> {
|
||||
async acceptById(docPath: string, proposalId: string, opts?: { silent?: boolean }): Promise<boolean> {
|
||||
const hit = this.byId(docPath, proposalId);
|
||||
return hit ? this.accept(hit.state, hit.proposal) : false;
|
||||
return hit ? this.accept(hit.state, hit.proposal, opts) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* #46 (INV-42): accept EVERY pending proposal on a document in one gesture — a
|
||||
* batched application of the existing `acceptById` seam, not a new mechanism.
|
||||
* Block proposals take the INV-40 word-precise path automatically. Applied in
|
||||
* DESCENDING anchor order so an earlier accept never invalidates a later one's
|
||||
* offsets; proposals whose anchor can't resolve are SKIPPED (never force-applied)
|
||||
* and counted. Returns the applied-vs-skipped tally for the caller to report.
|
||||
*/
|
||||
async acceptAllProposals(document: vscode.TextDocument): Promise<{ applied: number; skipped: number }> {
|
||||
if (!this.isTracked(document)) return { applied: 0, skipped: 0 };
|
||||
const state = this.ensureState(document);
|
||||
state.artifact = this.store.load(state.docPath) ?? emptyArtifact(state.docPath);
|
||||
const text = document.getText();
|
||||
const items = state.artifact.proposals.map((p) => {
|
||||
const fp = state.artifact.anchors[p.anchorId]?.fingerprint;
|
||||
const resolved = fp ? resolve(text, fp) : "orphaned";
|
||||
return { id: p.id, start: resolved === "orphaned" ? null : resolved.start };
|
||||
});
|
||||
const resolvable = items
|
||||
.filter((i): i is { id: string; start: number } => i.start !== null)
|
||||
.sort((a, b) => b.start - a.start);
|
||||
let applied = 0;
|
||||
let skipped = items.length - resolvable.length; // orphans, skipped up front
|
||||
for (const it of resolvable) {
|
||||
// silent: one batch report stands in for N per-proposal warnings.
|
||||
if (await this.acceptById(state.docPath, it.id, { silent: true })) applied++;
|
||||
else skipped++;
|
||||
}
|
||||
return { applied, skipped };
|
||||
}
|
||||
/** Reject by proposal id (test-facing twin of the thread-menu gesture). */
|
||||
rejectById(docPath: string, proposalId: string): boolean {
|
||||
@@ -150,7 +181,7 @@ export class ProposalController implements vscode.Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async accept(state: DocState, proposal: Proposal): Promise<boolean> {
|
||||
private async accept(state: DocState, proposal: Proposal, opts?: { silent?: boolean }): Promise<boolean> {
|
||||
if (this.guard.isReadOnly(state.docPath)) return false;
|
||||
const document = this.openDoc(state);
|
||||
if (!document) return false;
|
||||
@@ -158,9 +189,11 @@ export class ProposalController implements vscode.Disposable {
|
||||
const fp = state.artifact.anchors[proposal.anchorId]?.fingerprint;
|
||||
const resolved = fp ? resolve(document.getText(), fp) : "orphaned";
|
||||
if (resolved === "orphaned") {
|
||||
void vscode.window.showWarningMessage(
|
||||
"Cowriting: this proposal's target text changed or is missing — undo to restore it, or reject to discard (it is never applied by guess).",
|
||||
);
|
||||
// #46: accept-all suppresses per-proposal warnings (one batch report instead).
|
||||
if (!opts?.silent)
|
||||
void vscode.window.showWarningMessage(
|
||||
"Cowriting: this proposal's target text changed or is missing — undo to restore it, or reject to discard (it is never applied by guess).",
|
||||
);
|
||||
this.renderAll(document);
|
||||
return false;
|
||||
}
|
||||
@@ -178,9 +211,10 @@ export class ProposalController implements vscode.Disposable {
|
||||
{ expectedVersion: document.version, turnId: proposal.turnId },
|
||||
);
|
||||
if (!ok) {
|
||||
void vscode.window.showWarningMessage(
|
||||
"Cowriting: the editor rejected the accept — the proposal is still pending.",
|
||||
);
|
||||
if (!opts?.silent)
|
||||
void vscode.window.showWarningMessage(
|
||||
"Cowriting: the editor rejected the accept — the proposal is still pending.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
this.store.update(state.docPath, (a) => removeProposal(a, proposal.id));
|
||||
|
||||
Reference in New Issue
Block a user