Skip to content

Commit 9945b97

Browse files
committed
fix(issue-6): invalidate stale scan cache on resource edits in editor
The Resource Editor webview cached Scan Code results in an in-memory JS variable that was never cleared while the editor stayed open. Adding a previously-missing key (or Refresh) left the stale snapshot in place, so Scan Code kept reporting the key missing until the tab was closed/reopened. loadResources() now clears scanResultsCache so the next Scan Code re-queries the backend (whose cache is already invalidated on mutations). Extracted the cache-lifecycle rules into scanCacheState.ts (mirrored by the inline webview JS) with regression tests.
1 parent 4e97e97 commit 9945b97

3 files changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect } from 'chai';
2+
import {
3+
shouldInvalidateScanCache,
4+
resolveScanAction,
5+
ResourceEditorEvent
6+
} from '../../views/scanCacheState';
7+
8+
/**
9+
* Regression tests for the "Scan Code results become stale after editing keys"
10+
* bug (issue #6 follow-up).
11+
*
12+
* Repro from the report: scan reports a missing key -> add it via the editor ->
13+
* scan again still reports it missing; Refresh doesn't help; only closing and
14+
* reopening the editor does. Root cause: the webview's in-memory scanResultsCache
15+
* was never cleared while the editor stayed open, so "Scan Code" kept short-
16+
* circuiting to the snapshot taken when the editor first opened.
17+
*/
18+
describe('scan results cache staleness (issue #6 follow-up)', () => {
19+
it('invalidates the scan cache after adding a key', () => {
20+
expect(shouldInvalidateScanCache('addSuccess')).to.equal(true);
21+
});
22+
23+
it('invalidates the scan cache after updating a key', () => {
24+
expect(shouldInvalidateScanCache('updateSuccess')).to.equal(true);
25+
});
26+
27+
it('invalidates the scan cache after deleting a key', () => {
28+
expect(shouldInvalidateScanCache('deleteSuccess')).to.equal(true);
29+
});
30+
31+
it('invalidates the scan cache on an explicit Refresh', () => {
32+
// The reporter noted Refresh alone did not resolve the stale scan.
33+
expect(shouldInvalidateScanCache('refresh')).to.equal(true);
34+
});
35+
36+
it('re-queries the backend once the cache is cleared', () => {
37+
expect(resolveScanAction(null)).to.equal('requery');
38+
});
39+
40+
it('still serves the cache while it is populated (fast re-open)', () => {
41+
expect(resolveScanAction({ missing: [], unused: [], references: [] })).to.equal('use-cache');
42+
});
43+
44+
it('end-to-end: add-then-rescan re-queries instead of reusing the stale snapshot', () => {
45+
// Simulate the webview state machine.
46+
let cache: unknown | null = { missing: ['Processing_Description_Label'], references: [] };
47+
48+
// First scan uses the snapshot.
49+
expect(resolveScanAction(cache)).to.equal('use-cache');
50+
51+
// User adds the missing key -> loadResources() runs -> cache cleared.
52+
const event: ResourceEditorEvent = 'addSuccess';
53+
if (shouldInvalidateScanCache(event)) {
54+
cache = null;
55+
}
56+
57+
// Next "Scan Code" must hit the backend, which no longer reports it missing.
58+
expect(resolveScanAction(cache)).to.equal('requery');
59+
});
60+
});

vscode-extension/src/views/resourceEditor.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,16 @@ export class ResourceEditorPanel {
11471147
11481148
function loadResources() {
11491149
setStatus('Loading...');
1150+
// Drop the cached scan results so the next "Scan Code" re-queries the
1151+
// backend. loadResources() runs on every add/update/delete and on the
1152+
// Refresh button, so without this the modal kept showing the scan
1153+
// snapshot taken when the editor first opened — a key added in the
1154+
// editor was still reported missing until the tab was closed/reopened.
1155+
// The backend cache is already invalidated on mutations, so a fresh
1156+
// re-query returns correct results. If the extension re-sends valid
1157+
// cached scan results right after (handleLoadResources), that simply
1158+
// repopulates with the backend's current state, which is what we want.
1159+
scanResultsCache = null;
11501160
vscode.postMessage({ command: 'loadResources' });
11511161
}
11521162
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Scan-results cache lifecycle for the Resource Editor webview.
3+
*
4+
* The webview keeps the last "Scan Code" result in a JS variable so re-opening the
5+
* modal is instant. The bug (issue #6 follow-up) was that this cache was never
6+
* cleared while the editor stayed open: adding a previously-missing key in the
7+
* editor left the stale snapshot in place, so "Scan Code" (and even the Refresh
8+
* button) kept reporting the key as missing until the tab was closed and reopened.
9+
*
10+
* The webview's HTML/JS is built as a string and cannot import this module, so these
11+
* are the canonical, unit-tested rules the inline logic MUST mirror:
12+
*
13+
* - Any resource mutation (add/update/delete) and the explicit Refresh both funnel
14+
* through loadResources(), which clears the cache. The backend cache is already
15+
* invalidated on mutations, so the next scan re-queries fresh.
16+
* - "Scan Code" only short-circuits to the in-memory cache when it is still present;
17+
* once cleared it posts a real `scanCode` message to the extension.
18+
*/
19+
20+
export type ScanResultsCache = unknown | null;
21+
22+
/**
23+
* Events that occur while the editor is open and that affect whether the cached scan
24+
* snapshot is still trustworthy.
25+
*/
26+
export type ResourceEditorEvent =
27+
| 'refresh' // user clicked the Refresh button
28+
| 'addSuccess' // a key was added
29+
| 'updateSuccess' // a key value was updated
30+
| 'deleteSuccess'; // a key was deleted
31+
32+
/**
33+
* Whether the cached scan snapshot must be invalidated in response to an event.
34+
* Every event that reloads the resource grid also invalidates the scan cache, because
35+
* the set of keys (and therefore which keys are missing/unused) may have changed.
36+
*/
37+
export function shouldInvalidateScanCache(event: ResourceEditorEvent): boolean {
38+
switch (event) {
39+
case 'refresh':
40+
case 'addSuccess':
41+
case 'updateSuccess':
42+
case 'deleteSuccess':
43+
return true;
44+
default:
45+
return false;
46+
}
47+
}
48+
49+
/**
50+
* Decide what "Scan Code" should do given the current cache. When the cache has been
51+
* cleared (null), it must re-query the backend rather than re-displaying stale data.
52+
*/
53+
export function resolveScanAction(cache: ScanResultsCache): 'use-cache' | 'requery' {
54+
return cache ? 'use-cache' : 'requery';
55+
}

0 commit comments

Comments
 (0)