Skip to content

Commit 9efbe9f

Browse files
committed
Warn when completed answers reference reassigned code
1 parent fe4aa65 commit 9efbe9f

3 files changed

Lines changed: 16 additions & 1 deletion

File tree

‎docs/implementation/read-only-review.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,5 @@ PR #11 round 6 reproduced releasing a concurrency slot while a cancelled invocat
9898
The review's polling-efficiency observation is tracked separately in issue #12: answer polling currently rebuilds the full review, and the follow-up will measure and remove that work while preserving snapshot metadata and stale-response protection.
9999

100100
PR #11 round 7 confirmed cancellation tracking and described outdated snippet retries in its summary. Direct UI reassignment of owned code is already rejected; a persisted assignment change reproduced the stale-reference case, now guarded in both manager and retry UI. The summary also mentioned in-flight composer state: browser tests reproduced lost typing during submission. Action responses now capture current drafts before rendering and clear only the unchanged submitted draft/attachment, preserving edits and drafts on other items.
101+
102+
PR #11 round 8 returned no inline findings and identified one related summary gap: completed answers only warned for an older snapshot or plan revision, not a snippet invalidated by reassignment. Completed answers now show the same historical-context warning for either condition. A browser regression preserves the historical answer while checking the warning after a persisted assignment change.

‎test/browser/review.spec.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
44
import { join } from 'node:path';
55
import { execFileSync } from 'node:child_process';
66
import { createDemo } from '../../scripts/demo.ts';
7+
import { choiceKeys } from '../../core/approvals.ts';
78
import { startServer } from '../../web/server.ts';
89
let root: string, app: Awaited<ReturnType<typeof startServer>>;
910
test.beforeEach(async () => { root=mkdtempSync(join(tmpdir(),'codeboost-browser-'));app=await startServer(createDemo(join(root,'demo')),0); });
@@ -245,3 +246,15 @@ for(const switchItem of [false,true]) test(`preserves edits made while a questio
245246
await expect(page.getByText('Submitted question',{exact:true})).toBeVisible();
246247
await expect(page.getByLabel('Question about this item')).toHaveValue('New unsent draft');
247248
});
249+
test('warns on completed answers when a snippet assignment changes',async({page})=>{
250+
const service=app.service,identity=service.config.identity,initial=service.load();
251+
const foreign=initial.segments.find(s=>s.row==='Unplanned'&&s.operation==='+')!;
252+
const assigned=service.act({action:'assign',item:'P1',key:foreign.key,token:initial.token});
253+
const asked=service.act({action:'note',item:'P1',kind:'question',text:'Explain assigned code',reference:{key:foreign.key,start:foreign.newLine,end:foreign.newLine},token:assigned.token});
254+
service.store.beginAnswer(identity,asked.createdNoteId!,'completed');service.store.finishAnswer(identity,asked.createdNoteId!,'completed',{status:'complete',text:'Historical answer'});
255+
const index=initial.segments.findIndex(s=>s.key===foreign.key);
256+
service.store.saveReview(identity,asked.expected,[],[{action:'assign',item:'P2',key:choiceKeys(initial.segments,identity)[index]!}]);
257+
await page.goto(app.url);
258+
await expect(page.locator('.agent-answer')).toContainText('Historical answer');
259+
await expect(page.locator('.agent-answer')).toContainText('Answer refers to earlier code or review context.');
260+
});

‎web/public/app.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ $("code").addEventListener("keyup",captureHighlightedLines);
564564
function answerMarkup(note) {
565565
if(note.kind!=="question") return "";
566566
const answer=note.answer;
567-
if(answer?.status==="complete") return `<section class="agent-answer"><strong>${answer.provider === "claude" ? "Claude Code" : answer.provider === "codex" ? "Codex" : "Agent"}</strong><p>${esc(answer.text)}</p>${note.answerOutdated ? '<small>! Answer refers to an earlier review snapshot.</small>' : ""}</section>`;
567+
if(answer?.status==="complete") return `<section class="agent-answer"><strong>${answer.provider === "claude" ? "Claude Code" : answer.provider === "codex" ? "Codex" : "Agent"}</strong><p>${esc(answer.text)}</p>${note.answerOutdated || note.outdated ? '<small>! Answer refers to earlier code or review context.</small>' : ""}</section>`;
568568
if(note.answerOutdated || note.outdated) return '<p class="warn">This question refers to an earlier review. Ask again against the current code.</p>';
569569
if(answer?.status==="pending" && answer.expiresAt>Date.now()) return '<p role="status">Agent · Answering…</p>';
570570
const error=answer?.status==="failed"?answer.error:answer?.status==="pending"?"Agent was interrupted or timed out.":"Answer not started. Choose an agent in Settings or retry when capacity is available.";

0 commit comments

Comments
 (0)