Step 10j (host + extension): cache contract three-state UX
Completes the two-tier cache contract from step 9 / docs/CACHE_CONTRACT.md
on the extension side. The Python side shipped in the Python repo at
33c495a.
Host (rcjav-host.py):
- fetch_rules_info() memoizes per-script-path calls to
`rc-jav.py --print-rules-info` so handle_cache_status doesn't pay
the Python startup cost on every poll.
- _cache_freshness_fields(data, rules_info) computes the new
cache_schema / id_rules / id_rules_signature trio + their three
*_match booleans + cache_state ('fresh' / 'stale_by_rules' /
'schema_mismatch' / 'missing'). Legacy version:3 caches still on
disk report as stale_by_rules with cache_schema_match=True (we'll
migrate them at next load_cache).
- New handle_reextract_ids() action forwards to
`rc-jav.py --reextract --format json` with a 5-minute timeout.
background.js:
- New `reextract-ids` message forwards to host with a 300s timeout.
options-cache.js + options-library-issues.js:
- renderCacheContractBanner() paints a three-state banner above the
per-remote list: green ✓ fresh / amber ! stale-by-rules (with
"Re-extract IDs (fast, no rescan)" chip button) / red ✗ schema
mismatch. Includes a snippet of the cache signature for diagnostics.
- Delegated click handler in options-library-issues.js catches
.cache-reextract, sends the message, shows transient
"Re-extracting…" state, and replaces the button with a per-summary
line ("Re-extracted N IDs · X changed · Y unchanged · Z dropped").
- rules_info_error from the host surfaces as its own amber line above
the banner.
node --check passes on background.js, options-cache.js,
options-library-issues.js individually and on the concatenation of all
four script files. python -m py_compile passes on rcjav-host.py.
Behavioral verification requires reloading the unpacked extension and
running through:
- Check Cache → banner shows "stale by rules" amber (legacy v3 cache)
- Click "Re-extract IDs" → fast path runs, summary appears
- Check Cache again → banner now shows "Cache up to date" green
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,35 @@ document.getElementById("setup-health-run").addEventListener("click", (event) =>
|
||||
})
|
||||
);
|
||||
|
||||
// Three-state UX (docs/CACHE_CONTRACT.md): fresh / stale_by_rules / schema_mismatch.
|
||||
// Renders an inline banner above the per-remote list. Stale_by_rules adds a
|
||||
// "Re-extract IDs" button that triggers the fast rebuild without rclone.
|
||||
function renderCacheContractBanner(r) {
|
||||
const state = r.cache_state;
|
||||
if (r.rules_info_error) {
|
||||
return `<div style="margin-top:10px;padding:6px 8px;background:rgba(255,200,50,.08);border:1px solid rgba(255,200,50,.25);border-radius:4px;color:#ffa;">⚠ rules lookup failed: ${escapeHtml(r.rules_info_error)}</div>`;
|
||||
}
|
||||
if (state === "fresh") {
|
||||
return `<div style="margin-top:10px;padding:6px 8px;background:rgba(120,200,120,.08);border:1px solid rgba(120,200,120,.25);border-radius:4px;color:#afa;">✓ Cache up to date with current ID rules.</div>`;
|
||||
}
|
||||
if (state === "stale_by_rules") {
|
||||
const sigLine = r.id_rules_signature && r.id_rules_signature !== "legacy"
|
||||
? `<div style="color:#999;font-size:11px;margin-top:3px;">Cache signature: <code>${escapeHtml(String(r.id_rules_signature).slice(0, 22))}…</code></div>`
|
||||
: `<div style="color:#999;font-size:11px;margin-top:3px;">Cache predates the two-tier contract (legacy header).</div>`;
|
||||
return `<div style="margin-top:10px;padding:8px 10px;background:rgba(255,200,50,.08);border:1px solid rgba(255,200,50,.3);border-radius:4px;color:#ffa;">
|
||||
! <strong>Cache is stale by rules.</strong> ID extraction rules have changed since this cache was built. Some <code>jav_id</code> values may be out of date.
|
||||
${sigLine}
|
||||
<div style="margin-top:8px;"><button class="chip-btn cache-reextract" type="button" style="color:#ffd97a;background:rgba(255,200,50,.12);border-color:rgba(255,200,50,.35);font-weight:600;">Re-extract IDs (fast, no rescan)</button></div>
|
||||
</div>`;
|
||||
}
|
||||
if (state === "schema_mismatch") {
|
||||
return `<div style="margin-top:10px;padding:8px 10px;background:rgba(255,120,120,.08);border:1px solid rgba(255,120,120,.3);border-radius:4px;color:#faa;">
|
||||
✗ <strong>Cache schema mismatch.</strong> The on-disk cache shape is incompatible (schema ${escapeHtml(r.cache_schema ?? "?")} vs expected ${escapeHtml(r.expected_cache_schema ?? "?")}). A full re-scan is required.
|
||||
</div>`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
document.getElementById("cache-status-run").addEventListener("click", async () => {
|
||||
const out = document.getElementById("cache-status-results");
|
||||
out.textContent = "checking cache...";
|
||||
@@ -136,6 +165,7 @@ document.getElementById("cache-status-run").addEventListener("click", async () =
|
||||
`<div><span style="color:#777;">Configured target:</span> ${escapeHtml((r.configured?.default_target || []).join(", ") || "(none)")}</div>`,
|
||||
`<div><span style="color:#777;">Configured source:</span> ${escapeHtml((r.configured?.default_source || []).join(", ") || "(none)")}</div>`,
|
||||
];
|
||||
rows.push(renderCacheContractBanner(r));
|
||||
for (const m of r.remotes || []) {
|
||||
const color = m.status === "never_scanned" || m.stale ? "#ffa" : "#afa";
|
||||
const state = m.status === "never_scanned" ? "never scanned" : `${m.status || (m.stale ? "stale" : "fresh")} · age ${fmtCacheAge(m.age_hours)}`;
|
||||
|
||||
Reference in New Issue
Block a user