fix: proposal threads disable the dead reply input (decide-only, INV-12) #16
@@ -22,6 +22,8 @@ export interface RenderedProposal {
|
|||||||
id: string;
|
id: string;
|
||||||
/** true → resolves exactly, decidable; false → stale/orphaned (INV-11). */
|
/** true → resolves exactly, decidable; false → stale/orphaned (INV-11). */
|
||||||
pending: boolean;
|
pending: boolean;
|
||||||
|
/** always false — proposals are decide-only, no reply input (INV-12). */
|
||||||
|
canReply: boolean;
|
||||||
turnId?: string;
|
turnId?: string;
|
||||||
range: { start: number; end: number };
|
range: { start: number; end: number };
|
||||||
}
|
}
|
||||||
@@ -252,6 +254,11 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
author: { name: proposal.author.id },
|
author: { name: proposal.author.id },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
// Proposals are decide-only (INV-12): ✓ accept / ✗ reject in the title
|
||||||
|
// bar. Without this, VS Code renders its default "Reply…" input with no
|
||||||
|
// submit command wired — a dead end. Discussion belongs in a regular
|
||||||
|
// coauthoring thread; proposal discussion trails are deferred (spec §1.7).
|
||||||
|
vsThread.canReply = false;
|
||||||
vsThread.label = pending
|
vsThread.label = pending
|
||||||
? "Pending proposal"
|
? "Pending proposal"
|
||||||
: "⚠ Stale proposal (target text changed or missing) — accept disabled";
|
: "⚠ Stale proposal (target text changed or missing) — accept disabled";
|
||||||
@@ -315,12 +322,13 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
const state = this.docs.get(docPath);
|
const state = this.docs.get(docPath);
|
||||||
if (!state) return [];
|
if (!state) return [];
|
||||||
const out: RenderedProposal[] = [];
|
const out: RenderedProposal[] = [];
|
||||||
for (const [id] of state.vsThreads) {
|
for (const [id, vsThread] of state.vsThreads) {
|
||||||
const p = state.artifact.proposals.find((x) => x.id === id)!;
|
const p = state.artifact.proposals.find((x) => x.id === id)!;
|
||||||
const off = state.live.get(id)!;
|
const off = state.live.get(id)!;
|
||||||
out.push({
|
out.push({
|
||||||
id,
|
id,
|
||||||
pending: !state.unresolved.has(id),
|
pending: !state.unresolved.has(id),
|
||||||
|
canReply: vsThread.canReply !== false,
|
||||||
turnId: p.turnId,
|
turnId: p.turnId,
|
||||||
range: { start: off.start, end: off.end },
|
range: { start: off.start, end: off.end },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ suite("F4 propose/accept (host E2E — programmatic ingress, no LLM)", () => {
|
|||||||
assert.strictEqual(rendered.length, 1);
|
assert.strictEqual(rendered.length, 1);
|
||||||
assert.strictEqual(rendered[0].pending, true);
|
assert.strictEqual(rendered[0].pending, true);
|
||||||
assert.strictEqual(rendered[0].turnId, "turn-p1");
|
assert.strictEqual(rendered[0].turnId, "turn-p1");
|
||||||
|
assert.strictEqual(rendered[0].canReply, false, "proposals are decide-only — no dead reply input (INV-12)");
|
||||||
const art = readSidecar();
|
const art = readSidecar();
|
||||||
assert.strictEqual(art.proposals.length, 1, "proposal persisted at propose time");
|
assert.strictEqual(art.proposals.length, 1, "proposal persisted at propose time");
|
||||||
assert.strictEqual(art.anchors[art.proposals[0].anchorId].fingerprint.text, TARGET);
|
assert.strictEqual(art.anchors[art.proposals[0].anchorId].fingerprint.text, TARGET);
|
||||||
|
|||||||
Reference in New Issue
Block a user