diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97f9e91..1a92420 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -72,6 +72,20 @@ updater sees v3.1.5 as the latest release and does not offer it to the beta as a
- **The selected scene's info line** was 3.9:1 in Light and 4.1–4.4:1
in Midnight and CaYaDev. On the selected row's accent tint it now uses the secondary text
colour, and the contrast rules cover body and secondary text on selected rows.
+- **The self-test scans a populated panel [777].**
+ - The self-test's settings had no saved scenes, no clip cells with a preview and no slot without
+ a source. So the untranslated-text scan and the contrast audit never drew those screens. The
+ selected scene's 3.9:1 label above was found by hand.
+ - The self-test now adds representative content to the panel in memory:
+ - three saved scenes (white, black and gradient backgrounds) and a selected one that changes
+ nothing;
+ - clip slots that reference them;
+ - a white image, white and black colour slots, and a slot with no source.
+ - It also opens every fold, and puts the panel back afterwards.
+ - It counts what it actually scanned and fails if the selected row, four preview cells or the
+ sourceless slot were not drawn. On the first run they were not: an earlier step leaves the
+ deck at 2×2.
+ - Each theme now measures about 3,040 texts, with none below the threshold.
- **Two labels were unreadable in every theme.** The empty side of the A/B crossfader and the
"built-in" tag in Studio's list were dimmed with opacity to 2.9:1 and 3.7:1. They now use the
secondary text colour. The decorative "·" separators in status lines are hidden from screen
diff --git a/README.md b/README.md
index b6ff7ed..832f0b2 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ layers and **40 GPU effects**, an **OBS overlay**, **Spout / Syphon**, **project
[](https://github.com/CaYatur/SoundVisualizer/releases)
[](LICENSE)
[](#download)
-[](#tests)
+[](#tests)
[](https://www.electronjs.org/)
@@ -1746,7 +1746,7 @@ npm test
npm start -- --smoke
```
-**3229 unit tests, all passing.** They are written to check answers, not to exercise lines:
+**3230 unit tests, all passing.** They are written to check answers, not to exercise lines:
- **Formulas** are checked against values derived by hand from their definitions — Viviani's curve
staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor staying
diff --git a/README.tr.md b/README.tr.md
index 7bcf4ef..b5ca035 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -18,7 +18,7 @@ destesi**, ve **MCP üzerinden yapay zekâ kontrolü**.
[](https://github.com/CaYatur/SoundVisualizer/releases)
[](LICENSE)
[](#hızlı-başlangıç)
-[](#testler)
+[](#testler)
[](https://www.electronjs.org/)
@@ -1749,7 +1749,7 @@ npm test
npm start -- --smoke
```
-**3229 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar:
+**3230 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar:
- **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre üzerinde
kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı kalması ve görüş
diff --git a/ROADMAP.md b/ROADMAP.md
index ce513ff..3960a35 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -133,7 +133,7 @@ npm test
npm start -- --smoke
```
-- **3229 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0;
+- **3230 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0;
105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at
that tag — 469 more with v3.1.4, most of them from the MilkDrop work — 1597 at
that tag — 1402 more with v3.1.5 — 2999 at that tag — and 27
diff --git a/src/main/main.js b/src/main/main.js
index 0f8b6d3..bb3a5ff 100644
--- a/src/main/main.js
+++ b/src/main/main.js
@@ -7070,6 +7070,55 @@ async function runSmoke() {
if (opened !== 'açıldı') errors.push('i18n scan could not open timeline/clipdeck: ' + opened);
await wait(900);
+ /* DOLU PANEL (#777). Öz testin ayarlarında kayıtlı sahne, önizlemeli
+ klip hücresi ya da kaynağı seçilmemiş yuva yok; çeviri ve karşıtlık
+ taramaları o ekranları hiç görmüyordu (seçili sahnenin alt yazısı
+ Aydınlık'ta 3,9:1 kalmıştı, #775). Panelin kendi nesnesine temsilî
+ içerik eklenir, seçili sahne görüntüyü değiştirmeyen bir kopyadır;
+ tema adımından sonra eski hâline döner. Disk zaten korunuyor. */
+ const populated = await awc3.executeJavaScript(`(function(){
+ var P = window.SVPanel; if (!P) return { error: 'panel yok' };
+ var c = P.cfg();
+ // Önceki adımlar başka bir desteyi etkin bırakabiliyor: görünen deste
+ var decks = (c.clipdeck && c.clipdeck.decks) || [];
+ var deck = decks.filter(function (d) { return d && d.id === c.clipdeck.activeDeck; })[0] || decks[0];
+ if (!deck) return { error: 'deste yok' };
+ window.__smokePopulateDeck = deck.id;
+ window.__smokePopulateBackup = JSON.stringify({ scenes: c.scenes || [], slots: deck.slots || [], rows: deck.rows, cols: deck.cols, active: c._activeSceneId || null });
+ // Önceki deste adımı ızgarayı 2x2 bırakıyor; yuvalar ızgaranın içinde kalmalı
+ deck.rows = Math.max(+deck.rows || 0, 3);
+ deck.cols = Math.max(+deck.cols || 0, 4);
+ var same = JSON.parse(JSON.stringify(c)); delete same.scenes;
+ var bgScene = function (id, name, bg) {
+ var d = JSON.parse(JSON.stringify(same));
+ d.background = Object.assign({}, d.background, bg);
+ return { id: id, name: name, data: d };
+ };
+ c.scenes = (c.scenes || []).concat([
+ { id: 'smoke-same', name: 'Smoke current', data: same },
+ bgScene('smoke-white', 'Smoke white', { type: 'solid', solidColor: '#ffffff' }),
+ bgScene('smoke-black', 'Smoke black', { type: 'solid', solidColor: '#000000' }),
+ bgScene('smoke-grad', 'Smoke gradient', { type: 'gradient', gradient: { colors: ['#ffffff', '#ffe066', '#00ffff'] } }),
+ ]);
+ var img = 'data:image/svg+xml,' + encodeURIComponent('');
+ deck.slots = [
+ { row: 0, col: 0, name: 'White scene', type: 'scene', ref: 'smoke-white' },
+ { row: 0, col: 1, name: 'Black scene', type: 'scene', ref: 'smoke-black' },
+ { row: 0, col: 2, name: 'Gradient scene', type: 'scene', ref: 'smoke-grad' },
+ { row: 0, col: 3, name: 'White image', type: 'image', ref: img },
+ { row: 1, col: 0, name: 'White slot', type: 'palette', ref: 'x', color: '#ffffff' },
+ { row: 1, col: 1, name: 'Black slot', type: 'palette', ref: 'x', color: '#000000' },
+ { row: 1, col: 2, name: 'No source', type: 'video', ref: '' },
+ ];
+ P.applyScene('smoke-same');
+ P.rerender();
+ return { scenes: c.scenes.length, slots: deck.slots.length };
+ })()`);
+ console.log('[SMOKE] dolu panel: ' + JSON.stringify(populated));
+ if (!populated || populated.error) errors.push('populated panel: ' + ((populated && populated.error) || 'no result'));
+ await wait(700);
+ const openFolds = "(function(){var h=document.querySelectorAll('#sections .fold-head:not(.open)');for(var i=0;i b.dataset.cat);
const out = [];
+ // Dolu panelin ekranları gerçekten tarandı mı (#777)
+ const seen = { active: false, thumbs: 0, noref: 0, folds: 0 };
for (const t of UT.THEMES) {
click('[data-cat="settings"]'); await sleep(450);
if (!click('.ui-theme-opt[data-theme="' + t.id + '"]')) { out.push({ theme: t.id, error: 'not in the picker' }); continue; }
await sleep(250);
+ /* Her ayar değişikliği seçili sahne vurgusunu kaldırıyor (bilerek):
+ dolu panelin seçili satırı her temada yeniden seçilir. */
+ if (window.__smokePopulateBackup && window.SVPanel) { window.SVPanel.applyScene('smoke-same'); await sleep(250); }
let checked = 0, min = Infinity;
const fails = [];
for (const c of cats) {
click('[data-cat="' + c + '"]'); await sleep(400);
+ // Katlanır bölümlerin içeriği kapalıyken DOM'da yok
+ const folds = document.querySelectorAll('#sections .fold-head:not(.open)');
+ folds.forEach((h) => h.click());
+ if (folds.length) { seen.folds += folds.length; await sleep(250); }
+ if (document.querySelector('#sceneList .scene-item.active')) seen.active = true;
+ seen.thumbs = Math.max(seen.thumbs, document.querySelectorAll('.cd-cell > .cd-thumb').length);
+ seen.noref = Math.max(seen.noref, document.querySelectorAll('.cd-cell.noref').length);
const s = document.getElementById('sections');
- const steps = Math.min(8, Math.ceil(s.scrollHeight / Math.max(1, s.clientHeight)));
+ const steps = Math.min(14, Math.ceil(s.scrollHeight / Math.max(1, s.clientHeight)));
for (let k = 0; k < steps; k++) {
s.scrollTop = k * s.clientHeight; await sleep(100);
const r = A.audit(document, window, { pageBg: getComputedStyle(document.body).backgroundColor, aaa: !!U.current().aaa });
@@ -7188,7 +7251,7 @@ async function runSmoke() {
click('[data-cat="settings"]'); await sleep(350);
click('.ui-theme-opt[data-theme="' + original + '"]');
await sleep(200);
- return { themes: out, restored: U.choice() === original };
+ return { themes: out, restored: U.choice() === original, seen };
})()`);
if (!report || report.error) {
errors.push('ui themes: ' + ((report && report.error) || 'no report'));
@@ -7202,10 +7265,30 @@ async function runSmoke() {
if (r.failCount) errors.push('ui theme ' + r.theme + ': ' + r.failCount + ' text element(s) below the contrast threshold');
});
if (!report.restored) errors.push('ui themes: the user\'s theme was not restored');
+ console.log('[SMOKE] dolu panel tarandı: ' + JSON.stringify(report.seen));
+ const sn = report.seen || {};
+ if (!sn.active || sn.thumbs < 4 || sn.noref < 1) {
+ errors.push('ui themes: the populated panel was not scanned (' + JSON.stringify(sn) + ')');
+ }
}
} catch (e) {
errors.push('ui themes: ' + (e && e.message ? e.message : e));
}
+ // Dolu panel eski hâline (sahneler, yuvalar, seçili sahne)
+ try {
+ await awc4.executeJavaScript(`(function(){
+ var raw = window.__smokePopulateBackup; if (!raw || !window.SVPanel) return false;
+ var b = JSON.parse(raw), c = window.SVPanel.cfg();
+ c.scenes = b.scenes;
+ var d = ((c.clipdeck && c.clipdeck.decks) || []).filter(function (x) { return x && x.id === window.__smokePopulateDeck; })[0];
+ if (d) { d.slots = b.slots; d.rows = b.rows; d.cols = b.cols; }
+ delete window.__smokePopulateDeck;
+ c._activeSceneId = b.active;
+ delete window.__smokePopulateBackup;
+ window.SVPanel.apply();
+ return true;
+ })()`);
+ } catch (e) { /* panel kapanıyor */ }
}
/* --- Panel ekran görüntüleri (--smoke-shots) ---
diff --git a/tests/ui-themes.test.js b/tests/ui-themes.test.js
index 447dad1..faee184 100644
--- a/tests/ui-themes.test.js
+++ b/tests/ui-themes.test.js
@@ -560,6 +560,31 @@ test('öz test her temayı panelin seçicisinden tarıyor ve kullanıcının tem
assert.match(step, /restored/);
});
+/* Dolu panel (#777): öz testin ayarlarında kayıtlı sahne ve önizlemeli
+ hücre yoktu; tarama o ekranları hiç görmüyordu. İçerik ekleniyor, gerçekten
+ tarandığı sayılıyor ve sonra geri alınıyor. */
+test('öz test çeviri ve karşıtlık taramalarını dolu panelde yapıyor', () => {
+ const m = read('src/main/main.js');
+ const p = m.indexOf('DOLU PANEL (#777)');
+ assert.ok(p > 0, 'dolu panel adımı');
+ const pop = m.slice(p, p + 4000);
+ ['smoke-same', 'smoke-white', 'smoke-black', 'smoke-grad'].forEach((id) => assert.ok(pop.includes(id), id));
+ assert.match(pop, /type: 'image', ref: img/);
+ assert.match(pop, /ref: ''/, 'kaynağı seçilmemiş yuva');
+ assert.match(pop, /P\.applyScene\('smoke-same'\)/, 'seçili sahne satırı');
+ assert.match(pop, /c\.clipdeck\.activeDeck/, 'görünen desteye yazılıyor');
+ assert.match(pop, /deck\.cols = Math\.max/, 'yuvalar ızgaranın içinde');
+ // Çeviri taraması katlanır bölümleri açıyor
+ assert.match(m, /if \(await awc3\.executeJavaScript\(openFolds\)\)/);
+ // Karşıtlık taraması kapsamı sayıyor ve eksikse düşüyor
+ const t = m.indexOf('Arayüz temaları: ekrandaki yazıların karşıtlığı (#770)');
+ const step = m.slice(t, t + 7000);
+ assert.match(step, /\.fold-head:not\(\.open\)/);
+ assert.match(step, /window\.SVPanel\.applyScene\('smoke-same'\)/);
+ assert.match(step, /!sn\.active \|\| sn\.thumbs < 4 \|\| sn\.noref < 1/);
+ assert.match(step, /__smokePopulateBackup/, 'içerik geri alınıyor');
+});
+
test('süs ayraçları ekran okuyucudan ve ölçümden gizli', () => {
const dir = path.join(root, 'src', 'admin');
fs.readdirSync(dir).filter((f) => f.endsWith('.js')).forEach((f) => {