diff --git a/docs/my-resources.md b/docs/my-resources.md
index b42ab794..a9a31eb2 100644
--- a/docs/my-resources.md
+++ b/docs/my-resources.md
@@ -253,6 +253,26 @@ download that works without it.
of Python. Both pip packages are pinned: unpinned, a rebuild from the same commit
could produce different documents.
+## What a modification can and cannot change
+
+The deck vocabulary describes *structure*, not *style*: `bullets`, `compare`,
+`table`, `callout`, `figure`, `image`, `section`, `title`. None of them carries a
+colour. The palette lives in `scripts/render_pptx.py` as fixed constants —
+`ACCENT` (#2563EB), `INK`, `MUTED`, `RULE`, `PAPER` — and the model never sees
+them.
+
+So an instruction like "make it yellow" has nowhere to land. The model applies it
+to the only colour lever it has: the `image_prompt` text, which produces yellow
+*figures* on an otherwise blue deck. That is not the modification going only to
+the images — it is the model using the one field that accepts a colour at all.
+
+The exception is a `custom` slide, whose shapes take `fill` and `color` (see
+`slideShapes.js`). A model can restyle those, but it will rarely rebuild an
+ordinary slide as a custom one just to change a colour.
+
+If deck styling should be changeable, the honest fix is a theme — an accent
+colour on the resource, passed to the renderer — not a wider slide vocabulary.
+
## The image library
Library → **Images** is every picture the account has generated, across all
diff --git a/public/js/myResources.js b/public/js/myResources.js
index 270b24d9..d63d5612 100644
--- a/public/js/myResources.js
+++ b/public/js/myResources.js
@@ -14,6 +14,10 @@
document.addEventListener('tabChanged', function (e) {
if (!e.detail || e.detail.tab !== 'myresources') return;
if (!inited) { init(); inited = true; }
+ // Reopening the tab is a fresh start, not a resumed one. The DOM survives
+ // the visit, so without this the previous run's illustration and status sit
+ // under an empty form as though they belonged to it.
+ else clearResults();
loadLibrary();
});
@@ -162,10 +166,25 @@
el.style.color = tone === 'bad' ? 'var(--red)' : tone === 'good' ? 'var(--green)' : 'var(--g600)';
}
+ // Everything the last run left on screen. The tab keeps its DOM between
+ // visits — app.js loads a component once and marks it data-loaded — so an
+ // illustration from a previous generation stayed visible under an empty form
+ // when the tab was reopened, looking like output for a topic nobody had
+ // typed. It cleared on a full page refresh and only then, which is why it
+ // read as a leak.
+ function clearResults() {
+ ['mr-images', 'mr-searches', 'mr-image-failures'].forEach(function (id) {
+ var el = document.getElementById(id);
+ if (el) el.textContent = '';
+ });
+ status('');
+ }
+
function runGenerate() {
var topic = (document.getElementById('mr-topic') || {}).value || '';
if (!topic.trim()) { status('Enter a topic first.', 'bad'); return; }
+ clearResults();
var btn = document.getElementById('btn-mr-generate');
if (btn) { btn.disabled = true; btn.innerHTML = ' Generating...'; }
status('Searching the library and writing. This takes a moment.');
@@ -605,6 +624,7 @@
if (!instructions) return say('Say what to change.', 'bad');
if (btn) { btn.disabled = true; btn.innerHTML = ' Applying'; }
+ clearResults();
say('Rewriting…');
fetch('/api/my-resources/' + encodeURIComponent(id) + '/refine', {
method: 'POST',
diff --git a/test/my-resources-stale-results.test.js b/test/my-resources-stale-results.test.js
new file mode 100644
index 00000000..54b98066
--- /dev/null
+++ b/test/my-resources-stale-results.test.js
@@ -0,0 +1,39 @@
+// The tab keeps its DOM between visits (app.js loads a component once and marks
+// it data-loaded), so whatever the last run rendered stays on screen. An
+// illustration from a previous generation sat under an empty form as though it
+// were output for a topic nobody had typed, and only a full page refresh
+// cleared it.
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+
+const ui = fs.readFileSync(path.join(__dirname, '..', 'public/js/myResources.js'), 'utf8');
+
+test('a new generation starts from a clean result area', () => {
+ assert.match(ui, /function clearResults\(\)/);
+ const gen = ui.slice(ui.indexOf('function runGenerate'), ui.indexOf('function reportSearches'));
+ assert.match(gen, /clearResults\(\);/, 'generate clears before it runs');
+});
+
+test('a modification clears it too', () => {
+ const start = ui.indexOf('function runModify');
+ const mod = ui.slice(start, ui.indexOf('function renderRow', start));
+ assert.match(mod, /clearResults\(\);/);
+});
+
+test('reopening the tab clears it, but the first visit does not need to', () => {
+ const tab = ui.slice(ui.indexOf("e.detail.tab !== 'myresources'"), ui.indexOf('var available'));
+ assert.match(tab, /else clearResults\(\);/,
+ 'only on a revisit — on the first visit there is nothing to clear and init() has not run');
+});
+
+test('clearing covers every container a run writes into', () => {
+ const fn = ui.slice(ui.indexOf('function clearResults'), ui.indexOf('function runGenerate'));
+ for (const id of ['mr-images', 'mr-searches', 'mr-image-failures']) {
+ assert.ok(fn.includes(id), id + ' is not cleared');
+ }
+ assert.match(fn, /status\(''\)/, 'the status line is part of the debris');
+ // textContent, not innerHTML: these hold model-derived text and image nodes.
+ assert.match(fn, /el\.textContent = ''/);
+});