From c29c7e3825205b22035972a24609ae8909fcacce Mon Sep 17 00:00:00 2001 From: Vladimir Belov Date: Sat, 3 Oct 2026 16:47:59 +0700 Subject: [PATCH 1/6] fix(ci): format tracked audit prompts --- .project/PROGRESS.md | 21 +++++++++++++++++++++ audit/1-astra-xhigh-prompt.md | 2 ++ audit/2-sol-6-1-xhigh-verify-prompt.md | 2 ++ 3 files changed, 25 insertions(+) diff --git a/.project/PROGRESS.md b/.project/PROGRESS.md index 4b31f82..6e1b160 100644 --- a/.project/PROGRESS.md +++ b/.project/PROGRESS.md @@ -337,3 +337,24 @@ path in a real local Aegea instance. - Do not store language in theme JSON, share URLs, or ZIP output. - Do not stage, commit, switch branches, or push unless the user explicitly asks. - Keep diffs scoped; avoid reformatting unrelated files. + +## Technical iteration: remote quality gate (R02) + +Baseline: `origin/main` at `a95eb2c4df1151a8d9adad5afe3e7919ceef7968`. +The remote implementation still supports shell `system`; its valid-session +fixture is correct. Tests (99/99), JavaScript/style lint, and build pass. Only +Prettier fails on the two already tracked audit prompt documents. + +- [x] Format only `audit/1-astra-xhigh-prompt.md` and + `audit/2-sol-6-1-xhigh-verify-prompt.md`, preserving their content. + Verify with `npm run format:check` and a scoped diff. +- [x] Rerun `npm test`, `npm run lint`, `npm run lint:styles`, + `npm run format:check`, and `npm run build`: all pass locally. + Keep the independent plain-font fix and local UI work out of this branch. + +No lint rules, thresholds, session behavior, or audit reports are changed. +The prompt changes add exactly four blank lines; ignoring blank lines produces +no diff. No new test is needed for Markdown formatting. + +Local verification uses Node 24/macOS. Next small step: create a draft PR and +verify the existing Node 22/Linux CI without running deployment. diff --git a/audit/1-astra-xhigh-prompt.md b/audit/1-astra-xhigh-prompt.md index 20263e6..30faef2 100644 --- a/audit/1-astra-xhigh-prompt.md +++ b/audit/1-astra-xhigh-prompt.md @@ -17,6 +17,7 @@ ## Карта проекта и обязательное чтение Selecta — браузерный генератор визуальных тем для Aegea, а не генератор текстовых тем/идей. Текущая основа: Vite, Vue 3, plain JavaScript, vue-i18n, fflate; статический сайт, без backend. Сначала прочитай: + - `README.md`, `AGENTS.md`, `SPEC.md`; - `UI-ARCHITECTURE.md`, `PREVIEW-BASELINE.md`; - `.project/PROGRESS.md`, `.project/IDEAS.md`, `CHANGELOG.md`, `RELEASE.md`; @@ -38,6 +39,7 @@ Selecta — браузерный генератор визуальных тем ## Требования к выводам Каждому finding дай устойчивый ID `F01`, `F02` и далее. Укажи: + - severity: Critical / High / Medium / Low и уверенность отдельно; - точный файл, строки и symbol/selector на зафиксированном SHA; - нарушенный контракт и источник ожидаемого поведения; diff --git a/audit/2-sol-6-1-xhigh-verify-prompt.md b/audit/2-sol-6-1-xhigh-verify-prompt.md index f886da2..16292f3 100644 --- a/audit/2-sol-6-1-xhigh-verify-prompt.md +++ b/audit/2-sol-6-1-xhigh-verify-prompt.md @@ -34,6 +34,7 @@ ## Verification matrix Дай каждой исходной записи ровно одну строку со следующими полями: + - исходный F-ID и краткое утверждение; - исходный severity; - статус: Confirmed / Partially confirmed / Rejected / Cannot verify; @@ -75,6 +76,7 @@ Partially confirmed должен содержать точную подтвер ## Формат результата и self-check Сохрани в `audit/2-sol-6-1-xhigh.md`: + 1. Executive summary и baseline drift. 2. Полную verification matrix. 3. Rejected / downgraded / already-fixed findings с объяснениями. From e5e51e9cf10e97f4a89e44f41f6970c8aed3fbec Mon Sep 17 00:00:00 2001 From: Vladimir Belov Date: Sat, 3 Oct 2026 16:58:40 +0700 Subject: [PATCH 2/6] fix(preview): match Aegea 11.5 plain font stack --- .project/PROGRESS.md | 35 +++++++++++++++++++ PREVIEW-BASELINE.md | 13 +++++++ src/preview/fonts.test.js | 72 +++++++++++++++++++++++++++++++++++++++ src/preview/style.css | 4 ++- 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/preview/fonts.test.js diff --git a/.project/PROGRESS.md b/.project/PROGRESS.md index 4b31f82..1ea0593 100644 --- a/.project/PROGRESS.md +++ b/.project/PROGRESS.md @@ -311,6 +311,41 @@ controls pane remains comfortable while editing either palette. Use `.project/IDEAS.md` for raw feature ideas and parked future work. Move only the selected next slice from `IDEAS.md` into `Active track` / `Next steps`. +## Technical iteration: Aegea 11.5 plain font parity (R04) + +Baseline: `origin/main` at `a95eb2c4df1151a8d9adad5afe3e7919ceef7968`. +Source oracle: clean Aegea `11.5` / `v4199` checkout at the exact documented +commit `e1d058356e5426bb1878785c6f4ab4e68b6c4995`. Its plain source and +compiled CSS use a system stack; Selecta currently defaults to InterVariable. + +- [x] Replace only `src/preview/style.css`'s default main font family with + the Aegea 11.5 stack; keep note/small inheritance and explicit sources. + Added baseline provenance in `PREVIEW-BASELINE.md`. Named families + use optional CSS quoting accepted by existing stylelint, preserving the + canonical family names and their order. No lint rules change. +- [x] Add focused regression checks for the actual preview CSS default, plain + ZIP inheritance in light/dark, and unchanged system/Google overrides. + Verified: 102/102 tests pass; the new parity regression fails on the + unpatched baseline CSS. Chrome EN/RU and light/dark plain inheritance + pass; explicit Sans-serif/Georgia overrides pass; PT Sans in both slots + produces one Google CSS link, removed on return to plain. +- [x] Run JS/style lint, build, formatting, and scoped diff. Tests, JS/style + lint and build pass locally; scoped formatting passes. Full formatting + still has only the independently reproduced baseline failure fixed by + R02 (draft PR #1). Keep its prerequisite patch in a separate commit + so the font implementation remains independently reviewable. + +Consumer limit: local PHP cannot start because a required libzip dylib is +missing. Source/compiled-CSS and browser checks do not replace a live Aegea +install or paired width/linebreak verification. Do not change engine target. + +Combined R02 + R04 verification in an isolated copy: all five commands pass, +including full formatting and 102/102 tests. R02 CI Node 22/Linux also passes. + +Next small step: commit the isolated font fix, adopt the tested R02 patch as +a separate prerequisite commit, create a draft PR, and inspect Node 22/Linux +CI. Merge R02 first. Live Aegea visual verification remains open. + ## Verification Common checks: diff --git a/PREVIEW-BASELINE.md b/PREVIEW-BASELINE.md index 0f73cbb..eb6fe16 100644 --- a/PREVIEW-BASELINE.md +++ b/PREVIEW-BASELINE.md @@ -20,6 +20,19 @@ - Aegea `11.5` body links use `text-decoration` with `text-decoration-color`; the deferred `12.0a` source keeps the same contract but refines it further. Selecta preview may approximate some link states with a scoped border-bottom subset where that is enough for theme tuning. - The deferred `12.0a` source adds bundled Inter and JetBrains Mono fonts. Selecta MVP still intentionally generates only system web-safe font choices. +## Plain font contract + +- Rechecked against the clean Aegea `11.5` / `v4199` checkout at the exact + baseline commit above; both `system/themes/plain/src/styles/variables.scss` + and compiled `system/themes/plain/styles/main.css` declare the same stack. +- Default `--mainFontFamily`: `system-ui, -apple-system, BlinkMacSystemFont, "SF UI Text", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif`. +- `--noteMainFontFamily` and `--smallFontFamily` remain `inherit`. The preview + supplies this default locally; a plain-source child theme omits these font + overrides and inherits them from Aegea. Explicit system/Google selections + keep their existing generation and font-loading paths. +- A live Aegea render and paired width/linebreak comparison are still needed + to verify visual parity; source equality does not establish pixel parity. + ## Dark mode contract - Aegea dark-mode support is theme metadata plus runtime appearance state: themes declare `supports_dark_mode`, while Aegea applies dark variables only when the blog responds to dark mode. diff --git a/src/preview/fonts.test.js b/src/preview/fonts.test.js new file mode 100644 index 0000000..f6eac22 --- /dev/null +++ b/src/preview/fonts.test.js @@ -0,0 +1,72 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { strFromU8, unzipSync } from 'fflate'; +import { generateThemeCss, getThemeCssVariables } from '../theme/css.js'; +import { SERIF_FONT_STACK, UI_FONT_STACK } from '../theme/fonts.js'; +import { initialThemeState } from '../theme/model.js'; +import { generateThemeZip } from '../theme/zip.js'; + +// Aegea 11.5/v4199, e1d058356e5426bb1878785c6f4ab4e68b6c4995, plain variables.scss +const plainMainFontFamily = + 'system-ui, -apple-system, BlinkMacSystemFont, "SF UI Text", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif'; + +const previewCss = readFileSync(new URL('./style.css', import.meta.url), 'utf8'); +const previewDefaults = previewCss.match(/\.aegea-preview\s*\{([^}]+)\}/)[1]; + +function getPreviewDefault(property) { + return previewDefaults + .match(new RegExp(`${property}:\\s*([^;]+);`))[1] + .replace(/\s+/g, ' ') + .trim(); +} + +test('preview plain defaults match the Aegea 11.5 font contract', () => { + assert.equal(getPreviewDefault('--mainFontFamily').replace(/["']/g, ''), plainMainFontFamily.replace(/["']/g, '')); + assert.equal(getPreviewDefault('--noteMainFontFamily'), 'inherit'); + assert.equal(getPreviewDefault('--smallFontFamily'), 'inherit'); +}); + +test('plain light and dark ZIPs inherit fonts without overrides or external imports', () => { + for (const supportsDarkMode of [false, true]) { + const themeState = structuredClone(initialThemeState); + themeState.meta.supportsDarkMode = supportsDarkMode; + const archive = unzipSync(generateThemeZip(themeState)); + const css = strFromU8(archive[`${themeState.meta.folderName}/styles/main.css`]); + + assert.doesNotMatch(css, /--(?:main|noteMain|small)FontFamily|@import/); + for (const palette of [themeState.palette, themeState.darkPalette]) { + const variables = getThemeCssVariables(themeState, palette); + assert.equal(variables['--mainFontFamily'], undefined); + assert.equal(variables['--noteMainFontFamily'], undefined); + } + } +}); + +test('explicit system and Google fonts override plain defaults independently of palette', () => { + const themeState = structuredClone(initialThemeState); + themeState.meta.supportsDarkMode = true; + themeState.typography.mainFontSource = 'system'; + themeState.typography.mainFontFamily = 'Aptos'; + themeState.typography.noteFontSource = 'system'; + themeState.typography.noteFontFamily = 'Georgia'; + + for (const palette of [themeState.palette, themeState.darkPalette]) { + const variables = getThemeCssVariables(themeState, palette); + assert.equal(variables['--mainFontFamily'], `Aptos, ${UI_FONT_STACK}`); + assert.equal(variables['--noteMainFontFamily'], `Georgia, ${SERIF_FONT_STACK}`); + } + assert.doesNotMatch(generateThemeCss(themeState), /@import/); + + themeState.typography.mainFontSource = 'google'; + themeState.typography.mainFontFamily = 'PT Sans'; + themeState.typography.noteFontSource = 'google'; + themeState.typography.noteFontFamily = 'PT Sans'; + + for (const palette of [themeState.palette, themeState.darkPalette]) { + const variables = getThemeCssVariables(themeState, palette); + assert.equal(variables['--mainFontFamily'], '"PT Sans", system-ui, sans-serif'); + assert.equal(variables['--noteMainFontFamily'], '"PT Sans", system-ui, sans-serif'); + } + assert.equal(generateThemeCss(themeState).match(/family=PT\+Sans/g).length, 1); +}); diff --git a/src/preview/style.css b/src/preview/style.css index 441c522..6175669 100644 --- a/src/preview/style.css +++ b/src/preview/style.css @@ -5,7 +5,9 @@ --marginTopCompact: 16px; --marginLeftCompact: 4%; --marginRightCompact: 4%; - --mainFontFamily: 'InterVariable', sans-serif; + --mainFontFamily: + system-ui, -apple-system, 'BlinkMacSystemFont', 'SF UI Text', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; --noteMainFontFamily: inherit; --smallFontFamily: inherit; --smallFontSize: 12px; From 5a7756c8b04aa1f4d2ad2094084f71f46757e119 Mon Sep 17 00:00:00 2001 From: Vladimir Belov Date: Sat, 3 Oct 2026 16:47:59 +0700 Subject: [PATCH 3/6] fix(ci): format tracked audit prompts --- .project/PROGRESS.md | 21 +++++++++++++++++++++ audit/1-astra-xhigh-prompt.md | 2 ++ audit/2-sol-6-1-xhigh-verify-prompt.md | 2 ++ 3 files changed, 25 insertions(+) diff --git a/.project/PROGRESS.md b/.project/PROGRESS.md index 1ea0593..359ffc7 100644 --- a/.project/PROGRESS.md +++ b/.project/PROGRESS.md @@ -372,3 +372,24 @@ path in a real local Aegea instance. - Do not store language in theme JSON, share URLs, or ZIP output. - Do not stage, commit, switch branches, or push unless the user explicitly asks. - Keep diffs scoped; avoid reformatting unrelated files. + +## Technical iteration: remote quality gate (R02) + +Baseline: `origin/main` at `a95eb2c4df1151a8d9adad5afe3e7919ceef7968`. +The remote implementation still supports shell `system`; its valid-session +fixture is correct. Tests (99/99), JavaScript/style lint, and build pass. Only +Prettier fails on the two already tracked audit prompt documents. + +- [x] Format only `audit/1-astra-xhigh-prompt.md` and + `audit/2-sol-6-1-xhigh-verify-prompt.md`, preserving their content. + Verify with `npm run format:check` and a scoped diff. +- [x] Rerun `npm test`, `npm run lint`, `npm run lint:styles`, + `npm run format:check`, and `npm run build`: all pass locally. + Keep the independent plain-font fix and local UI work out of this branch. + +No lint rules, thresholds, session behavior, or audit reports are changed. +The prompt changes add exactly four blank lines; ignoring blank lines produces +no diff. No new test is needed for Markdown formatting. + +Local verification uses Node 24/macOS. Next small step: create a draft PR and +verify the existing Node 22/Linux CI without running deployment. diff --git a/audit/1-astra-xhigh-prompt.md b/audit/1-astra-xhigh-prompt.md index 20263e6..30faef2 100644 --- a/audit/1-astra-xhigh-prompt.md +++ b/audit/1-astra-xhigh-prompt.md @@ -17,6 +17,7 @@ ## Карта проекта и обязательное чтение Selecta — браузерный генератор визуальных тем для Aegea, а не генератор текстовых тем/идей. Текущая основа: Vite, Vue 3, plain JavaScript, vue-i18n, fflate; статический сайт, без backend. Сначала прочитай: + - `README.md`, `AGENTS.md`, `SPEC.md`; - `UI-ARCHITECTURE.md`, `PREVIEW-BASELINE.md`; - `.project/PROGRESS.md`, `.project/IDEAS.md`, `CHANGELOG.md`, `RELEASE.md`; @@ -38,6 +39,7 @@ Selecta — браузерный генератор визуальных тем ## Требования к выводам Каждому finding дай устойчивый ID `F01`, `F02` и далее. Укажи: + - severity: Critical / High / Medium / Low и уверенность отдельно; - точный файл, строки и symbol/selector на зафиксированном SHA; - нарушенный контракт и источник ожидаемого поведения; diff --git a/audit/2-sol-6-1-xhigh-verify-prompt.md b/audit/2-sol-6-1-xhigh-verify-prompt.md index f886da2..16292f3 100644 --- a/audit/2-sol-6-1-xhigh-verify-prompt.md +++ b/audit/2-sol-6-1-xhigh-verify-prompt.md @@ -34,6 +34,7 @@ ## Verification matrix Дай каждой исходной записи ровно одну строку со следующими полями: + - исходный F-ID и краткое утверждение; - исходный severity; - статус: Confirmed / Partially confirmed / Rejected / Cannot verify; @@ -75,6 +76,7 @@ Partially confirmed должен содержать точную подтвер ## Формат результата и self-check Сохрани в `audit/2-sol-6-1-xhigh.md`: + 1. Executive summary и baseline drift. 2. Полную verification matrix. 3. Rejected / downgraded / already-fixed findings с объяснениями. From 59fd406f9b4eb434c3e437d1b9fab2cbb64f2b10 Mon Sep 17 00:00:00 2001 From: Vladimir Belov Date: Sat, 3 Oct 2026 17:56:21 +0700 Subject: [PATCH 4/6] fix(ci): format already tracked audit reports --- audit/1-astra-xhigh.md | 96 ++++++++++++++++++++-------------------- audit/2-sol-6-1-xhigh.md | 87 ++++++++++++++++++------------------ 2 files changed, 93 insertions(+), 90 deletions(-) diff --git a/audit/1-astra-xhigh.md b/audit/1-astra-xhigh.md index f356110..6ef9b70 100644 --- a/audit/1-astra-xhigh.md +++ b/audit/1-astra-xhigh.md @@ -6,13 +6,13 @@ Подтверждены пять проблем: четыре Medium и одна Low; Critical/High не установлены. -| ID | Severity | Уверенность | Вывод | -| --- | --- | --- | --- | -| F01 | Medium | Высокая | JSON/URL/storage проверяют типы, но пропускают недопустимые значения. Через настоящий URL-импорт скачан ZIP с произвольным CSS-правилом и неверным `based_on`; нечисловой предел `1e999` также принимается. | -| F02 | Medium | Высокая для расхождения CSS | «Как в родительской теме» показывает `InterVariable, sans-serif`, тогда как экспорт наследует системный стек Aegea 11.5. | -| F03 | Medium | Высокая | Максимум ширины панели кэшируется без реакции на изменение окна: при 768 px панель занимает 672 px, превью — 88 px. | -| F04 | Medium | Высокая для локального результата | Четыре обязательные проверки завершаются ошибкой: test, lint, lint:styles, format:check. Сборка проходит. | -| F05 | Low | Высокая | Последнее изменение теряется при reload до истечения 500 ms debounce сохранения; воспроизведено в браузере. | +| ID | Severity | Уверенность | Вывод | +| --- | -------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| F01 | Medium | Высокая | JSON/URL/storage проверяют типы, но пропускают недопустимые значения. Через настоящий URL-импорт скачан ZIP с произвольным CSS-правилом и неверным `based_on`; нечисловой предел `1e999` также принимается. | +| F02 | Medium | Высокая для расхождения CSS | «Как в родительской теме» показывает `InterVariable, sans-serif`, тогда как экспорт наследует системный стек Aegea 11.5. | +| F03 | Medium | Высокая | Максимум ширины панели кэшируется без реакции на изменение окна: при 768 px панель занимает 672 px, превью — 88 px. | +| F04 | Medium | Высокая для локального результата | Четыре обязательные проверки завершаются ошибкой: test, lint, lint:styles, format:check. Сборка проходит. | +| F05 | Low | Высокая | Последнее изменение теряется при reload до истечения 500 ms debounce сохранения; воспроизведено в браузере. | Главный генераторный путь имеет рабочие основы: общая модель для CSS/preview/PHP, корректные ZIP-пути на допустимых данных, отдельное UI-состояние и обоснованная схема dark mode. Основной приоритет — закрыть входные границы и вернуть надёжный контроль качества, затем устранить расхождения preview и размеры панелей. Это направления для независимой проверки, не готовый implementation plan. @@ -52,20 +52,20 @@ Darwin 24.6.0 arm64; Node `v24.20.0`; npm `12.0.2`. CI настроен на Nod ### 2.3. Coverage matrix -| Область | Глубина и метод | Границы | -| --- | --- | --- | -| App/state | Прочитан script/template App целиком; traced init, URL → session → defaults, watchers, preset/random, resize, Reset, export, feedback | Не тестировались все конкурентные async-порядки и teardown/remount | -| Model/generation | Прочитаны model, css, themeInfo, zip, validation, metadata, typography; synthetic JSON/URL/storage → ZIP; браузер URL → Download ZIP | Нет живой установки в Aegea, PHP runtime недоступен | -| Serialization/storage | Полное чтение serialize/storage, соответствующих tests; malformed JSON/version/type tests, Unicode, неизвестные поля, неверные values, Infinity, reload | Не выполнялся stress большим файлом/URL, не проверены реальные browser quota/private mode | -| Random/locks/presets | Полное чтение реализаций; preset data и tests обозрены, все существующие tests запущены; 500 дополнительных детерминированных samples | Не исчерпывающий перебор lock combinations; лимит retries может оставить предупреждения при несовместимых locks | -| Fonts | fonts/googleFonts/catalog, вызовы preview/export и tests; сверка синтаксиса CSS API v2 с первичной документацией | Не загружались все семьи каталога; фактическая доступность каждой гарнитуры/варианта и offline rendering не подтверждены | -| Contrast/color | Полное чтение функций и проверяемых пар; unit tests; traced текущая light/dark palette | Нет WCAG-аудита произвольной пользовательской темы; threshold 3:1 — установленная продуктовая эвристика | -| Preview | Прочитаны Vue-разметка и CSS, просмотрены структура/локализация demoContent; sampled assets, computed styles и Aegea variables/layout | Не построчное сравнение всего demo prose и полного renderer Aegea; нет pixel diff и всех состояний движка | -| UI/a11y | Прочитаны все локальные UI adapters, ThemeControls, RangeControlField, PresetSelector, FieldLock/GroupLock; AX/DOM, labels, disabled/errors, menu/dialog, Escape/focus, radio groups | Нет screen reader, Safari/Firefox/touch, OS zoom; не проверены все keyboard-trap permutations и clipboard-denial | -| Responsive | Chrome на 1680, 1280, 768, 641, 375 px; DOM geometry, screenshot, клавиатурный resize | Browser zoom 200% отдельно не проверен; 641 px header требует дополнительной полировки (см. open questions) | -| i18n | index, паритет ключей unit tests, просмотр обеих locale maps; RU/EN переключение, lang и reload | Не редакторская вычитка каждой фразы; названия семейств шрифтов намеренно не переводятся | -| DX/release | Scripts, configs, CI/Pages, lock versions, release docs, пять npm checks | Не `npm ci`, не Node22/Linux, не security/advisory scan dependencies и не реальный Pages deploy | -| GitHub | Чтение prompt, remote commit и `issues?state=all&per_page=100` (пустой массив) | Нет релевантных Issues для corroboration; статус branch protection и live workflows не проверялся | +| Область | Глубина и метод | Границы | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| App/state | Прочитан script/template App целиком; traced init, URL → session → defaults, watchers, preset/random, resize, Reset, export, feedback | Не тестировались все конкурентные async-порядки и teardown/remount | +| Model/generation | Прочитаны model, css, themeInfo, zip, validation, metadata, typography; synthetic JSON/URL/storage → ZIP; браузер URL → Download ZIP | Нет живой установки в Aegea, PHP runtime недоступен | +| Serialization/storage | Полное чтение serialize/storage, соответствующих tests; malformed JSON/version/type tests, Unicode, неизвестные поля, неверные values, Infinity, reload | Не выполнялся stress большим файлом/URL, не проверены реальные browser quota/private mode | +| Random/locks/presets | Полное чтение реализаций; preset data и tests обозрены, все существующие tests запущены; 500 дополнительных детерминированных samples | Не исчерпывающий перебор lock combinations; лимит retries может оставить предупреждения при несовместимых locks | +| Fonts | fonts/googleFonts/catalog, вызовы preview/export и tests; сверка синтаксиса CSS API v2 с первичной документацией | Не загружались все семьи каталога; фактическая доступность каждой гарнитуры/варианта и offline rendering не подтверждены | +| Contrast/color | Полное чтение функций и проверяемых пар; unit tests; traced текущая light/dark palette | Нет WCAG-аудита произвольной пользовательской темы; threshold 3:1 — установленная продуктовая эвристика | +| Preview | Прочитаны Vue-разметка и CSS, просмотрены структура/локализация demoContent; sampled assets, computed styles и Aegea variables/layout | Не построчное сравнение всего demo prose и полного renderer Aegea; нет pixel diff и всех состояний движка | +| UI/a11y | Прочитаны все локальные UI adapters, ThemeControls, RangeControlField, PresetSelector, FieldLock/GroupLock; AX/DOM, labels, disabled/errors, menu/dialog, Escape/focus, radio groups | Нет screen reader, Safari/Firefox/touch, OS zoom; не проверены все keyboard-trap permutations и clipboard-denial | +| Responsive | Chrome на 1680, 1280, 768, 641, 375 px; DOM geometry, screenshot, клавиатурный resize | Browser zoom 200% отдельно не проверен; 641 px header требует дополнительной полировки (см. open questions) | +| i18n | index, паритет ключей unit tests, просмотр обеих locale maps; RU/EN переключение, lang и reload | Не редакторская вычитка каждой фразы; названия семейств шрифтов намеренно не переводятся | +| DX/release | Scripts, configs, CI/Pages, lock versions, release docs, пять npm checks | Не `npm ci`, не Node22/Linux, не security/advisory scan dependencies и не реальный Pages deploy | +| GitHub | Чтение prompt, remote commit и `issues?state=all&per_page=100` (пустой массив) | Нет релевантных Issues для corroboration; статус branch protection и live workflows не проверялся | Непрочитанное/непроверенное: весь транзитивный dependency source, все тестовые тела построчно (полностью разобраны прежде всего serialize/storage/css/themeInfo/zip), полный Aegea engine, binary demo GIF, весь внешний каталог Google Fonts и сетевые font responses, live production. Большой compiled Aegea core использован только для определения версии; это не security review Aegea. @@ -73,33 +73,33 @@ Darwin 24.6.0 arm64; Node `v24.20.0`; npm `12.0.2`. CI настроен на Nod `$AUDIT_TMP/project` ниже обозначает изолированную копию, не исходное дерево. Никаких machine-local путей и секретов в отчёте нет. -| Команда/действие | Результат | Evidence и предел | -| --- | --- | --- | -| `git status --short --untracked-files=all`, `git rev-parse HEAD`, `git branch --show-current` | PASS | Baseline выше; исходные два изменения сохранены | -| `npm test` в копии | FAIL, exit 1 | 99 tests, 98 pass, 1 fail: `saves and loads a valid session`, storage.test.js:36 | -| `npm run lint` | FAIL, exit 1 | Одна prettier/prettier ошибка, PresetSelector.vue:43 | -| `npm run lint:styles` | FAIL, exit 2 | Семь ошибок App.css:11,16,127,132,143,144 | -| `npm run format:check` | FAIL, exit 1 | Только PresetSelector.vue в исходном наборе файлов | -| `npm run build` | PASS | Vite 6.4.3: 682 modules, CSS 89.07 kB (gzip 12.70), JS 323.87 kB (gzip 107.75); два предупреждения PURE comments из @vueuse/core; ошибки сборки нет | -| Повтор test/lint/styles/format с фиксацией exit codes | FAIL, те же результаты | Нужен был для однозначного статуса отдельных commands в первоначальной shell-последовательности | -| `npm run preview -- --host 127.0.0.1 --port 4179 --strictPort` | PASS после разрешённого запуска loopback | Первая sandbox-попытка EPERM; второй запуск работал. Это не app defect | -| Synthetic Node reproduction: deserialize/decode/loadSession → metadata gate → generateThemeZip/unzip | FAIL контракт, PASS воспроизведение F01 | CSS marker и `missing-parent` сохранены, metadata errors `{}`; code ниже | -| PostCSS parse сгенерированного CSS | PASS воспроизведение F01 | Top-level rules `[':root', 'body', ':root']`, следовательно marker — настоящее правило CSS | -| Chrome URL import → Download ZIP → чтение скачанного архива | FAIL контракт, PASS воспроизведение F01 | Кнопка enabled, field errors отсутствуют; архив имеет `my-theme/theme-info.php` и `my-theme/styles/main.css`, содержит marker и неверный parent | -| Browser file chooser JSON import | NOT RUN до конца | Расширению не разрешён доступ к file URLs; настройки не менялись. Это ограничение инструмента, не дефект Selecta | -| Browser download event ожидание | FAIL инструмента, результат проверен иначе | API ожидания timeout, но файл фактически скачался; его проверили по синтетическому имени/содержимому и переместили во временную evidence-папку | -| Random: 500 samples, LCG seed=1701 | PASS в выборке | Для light и dark 0 наборов с warnings; это не гарантия для всех seeds/locks | -| Chrome default assets | PASS | Четыре изображения loaded с корректным `/Selecta/preview/` prefix | -| Chrome shell/preview modes | PASS в выбранных комбинациях | Light preview + dark shell и dark preview + light shell независимы; dark theme включает supportsDarkMode | -| Reset: open → Escape/Cancel; confirm | PASS | Cancel не сбрасывает; focus возвращается к Reset; confirm возвращает My Theme | -| Metadata input empty | PASS | ZIP disabled, `aria-invalid=true`, связанный error ID, локализованные ошибки обоих обязательных полей | -| Share menu open/Escape | PASS | Два menuitem, меню закрывается; copy/tooltip success/error полностью не прогонялись | -| Chrome resize 1280 → 768 + ArrowRight ×16 | FAIL | Sidebar 672 px, preview 88 px, aria-valuemax 672 | -| Chrome 375 px | PASS в выбранном состоянии | document scrollWidth=375, ZIP action в viewport; не общий сертификат mobile/a11y | -| Chrome обычный reload после autosave | PASS | `Audit saved baseline`, язык ru и theme mode восстановлены | -| Chrome fill `Audit fast edit` → немедленный reload | FAIL | Осталось `Audit saved baseline`; повторяет ранее наблюдавшуюся потерю ввода | -| `php --version`, запрос baseline Git object Aegea | BLOCKED/FAIL среды | PHP dyld/libzip; baseline object отсутствует. PHP lint/install — NOT RUN | -| SHA-256 всех tracked файлов против начального снимка | PASS | Ни один tracked файл не изменился за исследование | +| Команда/действие | Результат | Evidence и предел | +| ---------------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `git status --short --untracked-files=all`, `git rev-parse HEAD`, `git branch --show-current` | PASS | Baseline выше; исходные два изменения сохранены | +| `npm test` в копии | FAIL, exit 1 | 99 tests, 98 pass, 1 fail: `saves and loads a valid session`, storage.test.js:36 | +| `npm run lint` | FAIL, exit 1 | Одна prettier/prettier ошибка, PresetSelector.vue:43 | +| `npm run lint:styles` | FAIL, exit 2 | Семь ошибок App.css:11,16,127,132,143,144 | +| `npm run format:check` | FAIL, exit 1 | Только PresetSelector.vue в исходном наборе файлов | +| `npm run build` | PASS | Vite 6.4.3: 682 modules, CSS 89.07 kB (gzip 12.70), JS 323.87 kB (gzip 107.75); два предупреждения PURE comments из @vueuse/core; ошибки сборки нет | +| Повтор test/lint/styles/format с фиксацией exit codes | FAIL, те же результаты | Нужен был для однозначного статуса отдельных commands в первоначальной shell-последовательности | +| `npm run preview -- --host 127.0.0.1 --port 4179 --strictPort` | PASS после разрешённого запуска loopback | Первая sandbox-попытка EPERM; второй запуск работал. Это не app defect | +| Synthetic Node reproduction: deserialize/decode/loadSession → metadata gate → generateThemeZip/unzip | FAIL контракт, PASS воспроизведение F01 | CSS marker и `missing-parent` сохранены, metadata errors `{}`; code ниже | +| PostCSS parse сгенерированного CSS | PASS воспроизведение F01 | Top-level rules `[':root', 'body', ':root']`, следовательно marker — настоящее правило CSS | +| Chrome URL import → Download ZIP → чтение скачанного архива | FAIL контракт, PASS воспроизведение F01 | Кнопка enabled, field errors отсутствуют; архив имеет `my-theme/theme-info.php` и `my-theme/styles/main.css`, содержит marker и неверный parent | +| Browser file chooser JSON import | NOT RUN до конца | Расширению не разрешён доступ к file URLs; настройки не менялись. Это ограничение инструмента, не дефект Selecta | +| Browser download event ожидание | FAIL инструмента, результат проверен иначе | API ожидания timeout, но файл фактически скачался; его проверили по синтетическому имени/содержимому и переместили во временную evidence-папку | +| Random: 500 samples, LCG seed=1701 | PASS в выборке | Для light и dark 0 наборов с warnings; это не гарантия для всех seeds/locks | +| Chrome default assets | PASS | Четыре изображения loaded с корректным `/Selecta/preview/` prefix | +| Chrome shell/preview modes | PASS в выбранных комбинациях | Light preview + dark shell и dark preview + light shell независимы; dark theme включает supportsDarkMode | +| Reset: open → Escape/Cancel; confirm | PASS | Cancel не сбрасывает; focus возвращается к Reset; confirm возвращает My Theme | +| Metadata input empty | PASS | ZIP disabled, `aria-invalid=true`, связанный error ID, локализованные ошибки обоих обязательных полей | +| Share menu open/Escape | PASS | Два menuitem, меню закрывается; copy/tooltip success/error полностью не прогонялись | +| Chrome resize 1280 → 768 + ArrowRight ×16 | FAIL | Sidebar 672 px, preview 88 px, aria-valuemax 672 | +| Chrome 375 px | PASS в выбранном состоянии | document scrollWidth=375, ZIP action в viewport; не общий сертификат mobile/a11y | +| Chrome обычный reload после autosave | PASS | `Audit saved baseline`, язык ru и theme mode восстановлены | +| Chrome fill `Audit fast edit` → немедленный reload | FAIL | Осталось `Audit saved baseline`; повторяет ранее наблюдавшуюся потерю ввода | +| `php --version`, запрос baseline Git object Aegea | BLOCKED/FAIL среды | PHP dyld/libzip; baseline object отсутствует. PHP lint/install — NOT RUN | +| SHA-256 всех tracked файлов против начального снимка | PASS | Ни один tracked файл не изменился за исследование | ## 3. Findings diff --git a/audit/2-sol-6-1-xhigh.md b/audit/2-sol-6-1-xhigh.md index c48d7ce..74e184d 100644 --- a/audit/2-sol-6-1-xhigh.md +++ b/audit/2-sol-6-1-xhigh.md @@ -36,13 +36,13 @@ Darwin 24.6.0 arm64; Node `v24.20.0`; npm `12.0.2`. Vite 6.4.3; Vue 3.5.34; vue- Каждый F-ID представлен ровно одной строкой. Confidence относится к доказанному утверждению, а не к непроверенной установке. -| F-ID и исходное утверждение | Исходный severity | Статус | Текущая актуальность | Итоговый severity / confidence | Независимое evidence на текущей базе | Подтверждено, опровергнуто, пределы и false-positive risk | Решение | Canonical / план | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| F01: значения JSON/URL/storage пропускают CSS injection, другой parent и неограниченные числа | Medium | Confirmed | Still valid | Medium / высокая | serialize.js:18–23,53–54,70–74; storage.js:20–29,66–90; validation.js:1–15; css.js:47–50,69–76,93–95; themeInfo.js:19; App WT:50–57,233–238,497–520,545–551. `node audit-verification.mjs`: четыре ingress routes; PostCSS parse. Новый browser URL → Download ZIP → unzip. | Новый payload использует **margins**, а не maxWidth Astra; настоящий ZIP содержит top-level `.sol-proof` и `sol-missing-parent`, errors=0, Download enabled. JSON literal 1e999 становится Infinity и следующий roundtrip отвергает null. Проверены font fallback, PHP escaping и folder guard: это не PHP/JS injection или traversal в реальном caller. Недоверенный/ручной payload необходим; обычные controls такой CSS не создают. False-positive risk низкий для artifact defect, impact на установленном сайте не измерен. | Исправлять одну semantic boundary schema, объединить проявления | [C01](#c01) → [R01](#r01) | -| F02: plain font preview не совпадает с наследуемым Aegea 11.5 | Medium | Confirmed | Still valid | **Low** / высокая для CSS, средняя для визуального ущерба | AegeaPreview.css:8–9,19,256–260; fonts.js:91–94; css.js:66–68; PREVIEW-BASELINE:3–20. Browser default computed: preview и note `InterVariable, sans-serif`, 0 Google font links. Локальная distribution 11.5/v4199 variables.scss:115–117 содержит system-ui stack. | Реальное расхождение деклараций подтверждено. Не утверждается конкретное rasterized имя, число иных переносов или install failure. Явные system/google overrides обходят этот fallback. Точный baseline object отсутствует, distribution fingerprint записан ниже. False-positive risk низкий для разных CSS-стеков, выше для ожидаемого масштаба внешнего различия. | Исправлять локальный fallback после consumer parity check | [C02](#c02) → [R04](#r04) | -| F03: cached maximum панели не обновляется при resize | Medium | Confirmed | Still valid | Medium / высокая | App WT:128–132,149–150,396–435,737–750; App.css:63–66,159–207. Chrome: 1280 → 768 + ArrowRight ×16, DOM rects и ARIA. | Независимо получено controls=672, resizer=8, preview=88, aria-valuemax=672. Для fresh measurement 768 px сама формула даёт 408, а не 400: separator 8 px не вычтен. Это расчёт по коду, не отдельный browser замер fresh maximum. CSS не обеспечивает preview min-width. Stacked mobile при 375 работает. Ни потеря темы, ни поломка всей mobile-версии не утверждаются. False-positive risk низкий. | Исправлять реактивное измерение и reclamp | [C03](#c03) → [R03](#r03) | -| F04: четыре обязательные проверки падают, build проходит | Medium | Confirmed | Still valid | Medium / высокая локально | storage.test.js:36–63; storage.js:66–77; PROGRESS:488–491,528–535; PresetSelector.vue:43–47; App.css:11,16,127–144; .github/workflows/ci.yml. Все пять scripts заново выполнены в новой копии. | 98/99 tests; 1 JS formatting error; 7 stylelint errors; format только PresetSelector; build PASS. Stale system fixture противоречит принятой политике, а не доказывает сломанную миграцию. Stylelint `:global` не доказывает broken overlay. Node22/Linux/branch protection не проверены. False-positive risk низкий для красных checks, высокий для переноса их в runtime-bug claims. | Исправлять fixture/локальную lint-интеграцию и нарушения, не возвращать system | [C04](#c04) → [R02](#r02) | -| F05: быстрый reload до debounce теряет последнюю правку | Low | Confirmed | Still valid | Low / высокая | App WT:115–118,571–595; storage.js:101–114. Chrome сначала восстанавливает `Sol saved baseline`; затем fill `Sol rapid edit` → reload в одном batch (88 ms) вновь показывает baseline. | Обычный save/reload работает. Подтверждено короткое окно до timeout; закрытие вкладки/Back в этом конкретном временном окне не воспроизводились. Необходимости quota/storage failure нет. N01 добавляет иной, более долгий сценарий, но не меняет причину F05. False-positive risk низкий. | Исправлять в общем persistence slice, отдельно проверить Reset | [C05](#c05) → [R05](#r05) | +| F-ID и исходное утверждение | Исходный severity | Статус | Текущая актуальность | Итоговый severity / confidence | Независимое evidence на текущей базе | Подтверждено, опровергнуто, пределы и false-positive risk | Решение | Canonical / план | +| --------------------------------------------------------------------------------------------- | ----------------- | --------- | -------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------- | +| F01: значения JSON/URL/storage пропускают CSS injection, другой parent и неограниченные числа | Medium | Confirmed | Still valid | Medium / высокая | serialize.js:18–23,53–54,70–74; storage.js:20–29,66–90; validation.js:1–15; css.js:47–50,69–76,93–95; themeInfo.js:19; App WT:50–57,233–238,497–520,545–551. `node audit-verification.mjs`: четыре ingress routes; PostCSS parse. Новый browser URL → Download ZIP → unzip. | Новый payload использует **margins**, а не maxWidth Astra; настоящий ZIP содержит top-level `.sol-proof` и `sol-missing-parent`, errors=0, Download enabled. JSON literal 1e999 становится Infinity и следующий roundtrip отвергает null. Проверены font fallback, PHP escaping и folder guard: это не PHP/JS injection или traversal в реальном caller. Недоверенный/ручной payload необходим; обычные controls такой CSS не создают. False-positive risk низкий для artifact defect, impact на установленном сайте не измерен. | Исправлять одну semantic boundary schema, объединить проявления | [C01](#c01) → [R01](#r01) | +| F02: plain font preview не совпадает с наследуемым Aegea 11.5 | Medium | Confirmed | Still valid | **Low** / высокая для CSS, средняя для визуального ущерба | AegeaPreview.css:8–9,19,256–260; fonts.js:91–94; css.js:66–68; PREVIEW-BASELINE:3–20. Browser default computed: preview и note `InterVariable, sans-serif`, 0 Google font links. Локальная distribution 11.5/v4199 variables.scss:115–117 содержит system-ui stack. | Реальное расхождение деклараций подтверждено. Не утверждается конкретное rasterized имя, число иных переносов или install failure. Явные system/google overrides обходят этот fallback. Точный baseline object отсутствует, distribution fingerprint записан ниже. False-positive risk низкий для разных CSS-стеков, выше для ожидаемого масштаба внешнего различия. | Исправлять локальный fallback после consumer parity check | [C02](#c02) → [R04](#r04) | +| F03: cached maximum панели не обновляется при resize | Medium | Confirmed | Still valid | Medium / высокая | App WT:128–132,149–150,396–435,737–750; App.css:63–66,159–207. Chrome: 1280 → 768 + ArrowRight ×16, DOM rects и ARIA. | Независимо получено controls=672, resizer=8, preview=88, aria-valuemax=672. Для fresh measurement 768 px сама формула даёт 408, а не 400: separator 8 px не вычтен. Это расчёт по коду, не отдельный browser замер fresh maximum. CSS не обеспечивает preview min-width. Stacked mobile при 375 работает. Ни потеря темы, ни поломка всей mobile-версии не утверждаются. False-positive risk низкий. | Исправлять реактивное измерение и reclamp | [C03](#c03) → [R03](#r03) | +| F04: четыре обязательные проверки падают, build проходит | Medium | Confirmed | Still valid | Medium / высокая локально | storage.test.js:36–63; storage.js:66–77; PROGRESS:488–491,528–535; PresetSelector.vue:43–47; App.css:11,16,127–144; .github/workflows/ci.yml. Все пять scripts заново выполнены в новой копии. | 98/99 tests; 1 JS formatting error; 7 stylelint errors; format только PresetSelector; build PASS. Stale system fixture противоречит принятой политике, а не доказывает сломанную миграцию. Stylelint `:global` не доказывает broken overlay. Node22/Linux/branch protection не проверены. False-positive risk низкий для красных checks, высокий для переноса их в runtime-bug claims. | Исправлять fixture/локальную lint-интеграцию и нарушения, не возвращать system | [C04](#c04) → [R02](#r02) | +| F05: быстрый reload до debounce теряет последнюю правку | Low | Confirmed | Still valid | Low / высокая | App WT:115–118,571–595; storage.js:101–114. Chrome сначала восстанавливает `Sol saved baseline`; затем fill `Sol rapid edit` → reload в одном batch (88 ms) вновь показывает baseline. | Обычный save/reload работает. Подтверждено короткое окно до timeout; закрытие вкладки/Back в этом конкретном временном окне не воспроизводились. Необходимости quota/storage failure нет. N01 добавляет иной, более долгий сценарий, но не меняет причину F05. False-positive risk низкий. | Исправлять в общем persistence slice, отдельно проверить Reset | [C05](#c05) → [R05](#r05) | ## 3. Снятые, пониженные и уже исправленные утверждения @@ -101,8 +101,7 @@ const input = structuredClone(initialThemeState); input.meta.folderName = 'sol-boundary-audit'; input.meta.displayName = 'Sol boundary audit'; input.meta.basedOn = 'sol-missing-parent'; -input.layout.margins = - '2rem; }\n.sol-proof { outline: 13px solid blue; }\n:root { --sol-proof: 1'; +input.layout.margins = '2rem; }\n.sol-proof { outline: 13px solid blue; }\n:root { --sol-proof: 1'; const raw = JSON.stringify({ version: 2, ...input }); const state = deserializeTheme(raw); const param = btoa(encodeURIComponent(raw)); @@ -110,7 +109,12 @@ console.log(validateMetadata(state.meta)); // {} console.log(decodeThemeFromUrlParam(param).layout.margins); const entries = unzipSync(generateThemeZip(state)); const css = strFromU8(entries['sol-boundary-audit/styles/main.css']); -console.log(postcss.parse(css).nodes.filter((n) => n.type === 'rule').map((n) => n.selector)); +console.log( + postcss + .parse(css) + .nodes.filter((n) => n.type === 'rule') + .map((n) => n.selector) +); ``` JSON, synthetic File.text, URL и valid-envelope storage приняли payload. PostCSS top-level selectors: `[':root', '.sol-proof', ':root', '.sol-proof', ':root']`. Это валидные отдельные rules, а не просто строка с punctuation. @@ -128,31 +132,31 @@ sol-boundary-audit/styles/main.css Команды выполнялись в `$AUDIT_TMP/project`; это логическое обозначение новой временной копии, не путь checkout. PASS воспроизведения дефекта не равен PASS продуктового контракта. -| Команда / сценарий | Среда | Результат | Ключевое evidence и граница | -| --- | --- | --- | --- | -| `git status --short --untracked-files=all`, `git rev-parse HEAD`, `git branch --show-current`, hash snapshots | Исходный checkout, read-only | PASS | База и чужой diff выше; 0 drift в 89 tracked файлах | -| `npm test` | Новый temp copy, Node24/Mac | FAIL, exit 1 | 99 tests: 98 pass, 1 fail; storage.test.js:36, system fixture | -| `npm run lint` | Та же копия | FAIL, exit 1 | 1 prettier/prettier, PresetSelector.vue:43 | -| `npm run lint:styles` | Та же копия | FAIL, exit 2 | 7 App.css errors: global ×2, descending specificity ×2, duplicate selector, deprecated clip, property order | -| `npm run format:check` | Та же копия | FAIL, exit 1 | Только PresetSelector.vue | -| `npm run build` | Та же копия | PASS, exit 0 | 682 modules; CSS 89.07 kB/gzip 12.70; JS 323.87 kB/gzip 107.75; два VueUse PURE warnings | -| `node audit-verification.mjs` | Synthetic harness, создан только в temp copy | PASS, exit 0 для assertions | 4 ingress routes воспроизводят F01; 8 других scenario groups ниже. Не repo tests, не browser File chooser | -| `npm run preview -- --host 127.0.0.1 --port 4183 --strictPort` | Изолированный production build, loopback | PASS | Запущен для Chrome, затем остановлен; внешний server не публиковался | -| URL import → Download ZIP → unzip | Новый Chrome tab/origin | FAIL contract; PASS reproduction | ZIP marker/parent подтверждены по реальному download, не только helper | -| Default plain fonts | Chrome + distribution source | FAIL parity | Оба computed stacks InterVariable; export не задаёт mainFontFamily; Aegea11.5 stack начинается system-ui | -| 1280 → 768, keyboard ArrowRight ×16 | Chrome, default root sizing | FAIL layout | controls672 / separator8 / preview88 / max672 | -| 375 px, RU | Chrome | PASS sampled layout | scrollWidth375; separator display none; ZIP left231.89/right367 в viewport | -| После обычного save → reload | Chrome | PASS | Sol saved baseline восстановлен | -| Fill → reload одним batch | Chrome | FAIL persistence | 88 ms; Sol rapid edit потерян | -| No-op Reset → один fill → wait → reload | Chrome | FAIL persistence | Повтор N01, ожидание17 016ms, восстановлены defaults | -| Theme/Interface/locale separation | Chrome | PASS selected combinations | Light preview rgb255/255/255 + dark shell; dark preview rgb32/32/32 + light shell; RU не меняет metadata | -| Два валидных share URL A → B → Back → Forward | Chrome | PASS sampled history navigation | A, B, A, B; query очищен. Не обещание undo imports внутри одного документа, не тест всех BFCache modes | -| Chrome console sampled error/warn | Новая audit tab | PASS sample | 0 записей в запрошенной выборке; не гарантия отсутствия ошибок всех flows | -| `php --version` | Доступный PHP7.0.33 | FAIL среды, exit134 | Не найден libzip.5.dylib. PHP lint/live install NOT RUN | -| `git cat-file -e e1d058356e5426bb1878785c6f4ab4e68b6c4995^{commit}` | Доступный Aegea source | FAIL, exit128 | Baseline object отсутствует; exact-commit parity NOT RUN | -| Version regex + SHA256 Aegea plain files | Доступная distribution | PASS identification | E2_VERSION4199, E2_RELEASE11.5; fingerprints ниже, без чтения всего compiled core в контекст | -| GET GitHub `issues?state=all&per_page=100` | Connected read-only GitHub | PASS lookup | Пустой массив, нет issue duplicates; ничего не создано | -| Read-only `git ls-remote origin refs/heads/main` | Отдельный publication checkout | PASS | Remote a95eb2c… до подготовки второго report; требуется fresh check перед push | +| Команда / сценарий | Среда | Результат | Ключевое evidence и граница | +| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `git status --short --untracked-files=all`, `git rev-parse HEAD`, `git branch --show-current`, hash snapshots | Исходный checkout, read-only | PASS | База и чужой diff выше; 0 drift в 89 tracked файлах | +| `npm test` | Новый temp copy, Node24/Mac | FAIL, exit 1 | 99 tests: 98 pass, 1 fail; storage.test.js:36, system fixture | +| `npm run lint` | Та же копия | FAIL, exit 1 | 1 prettier/prettier, PresetSelector.vue:43 | +| `npm run lint:styles` | Та же копия | FAIL, exit 2 | 7 App.css errors: global ×2, descending specificity ×2, duplicate selector, deprecated clip, property order | +| `npm run format:check` | Та же копия | FAIL, exit 1 | Только PresetSelector.vue | +| `npm run build` | Та же копия | PASS, exit 0 | 682 modules; CSS 89.07 kB/gzip 12.70; JS 323.87 kB/gzip 107.75; два VueUse PURE warnings | +| `node audit-verification.mjs` | Synthetic harness, создан только в temp copy | PASS, exit 0 для assertions | 4 ingress routes воспроизводят F01; 8 других scenario groups ниже. Не repo tests, не browser File chooser | +| `npm run preview -- --host 127.0.0.1 --port 4183 --strictPort` | Изолированный production build, loopback | PASS | Запущен для Chrome, затем остановлен; внешний server не публиковался | +| URL import → Download ZIP → unzip | Новый Chrome tab/origin | FAIL contract; PASS reproduction | ZIP marker/parent подтверждены по реальному download, не только helper | +| Default plain fonts | Chrome + distribution source | FAIL parity | Оба computed stacks InterVariable; export не задаёт mainFontFamily; Aegea11.5 stack начинается system-ui | +| 1280 → 768, keyboard ArrowRight ×16 | Chrome, default root sizing | FAIL layout | controls672 / separator8 / preview88 / max672 | +| 375 px, RU | Chrome | PASS sampled layout | scrollWidth375; separator display none; ZIP left231.89/right367 в viewport | +| После обычного save → reload | Chrome | PASS | Sol saved baseline восстановлен | +| Fill → reload одним batch | Chrome | FAIL persistence | 88 ms; Sol rapid edit потерян | +| No-op Reset → один fill → wait → reload | Chrome | FAIL persistence | Повтор N01, ожидание17 016ms, восстановлены defaults | +| Theme/Interface/locale separation | Chrome | PASS selected combinations | Light preview rgb255/255/255 + dark shell; dark preview rgb32/32/32 + light shell; RU не меняет metadata | +| Два валидных share URL A → B → Back → Forward | Chrome | PASS sampled history navigation | A, B, A, B; query очищен. Не обещание undo imports внутри одного документа, не тест всех BFCache modes | +| Chrome console sampled error/warn | Новая audit tab | PASS sample | 0 записей в запрошенной выборке; не гарантия отсутствия ошибок всех flows | +| `php --version` | Доступный PHP7.0.33 | FAIL среды, exit134 | Не найден libzip.5.dylib. PHP lint/live install NOT RUN | +| `git cat-file -e e1d058356e5426bb1878785c6f4ab4e68b6c4995^{commit}` | Доступный Aegea source | FAIL, exit128 | Baseline object отсутствует; exact-commit parity NOT RUN | +| Version regex + SHA256 Aegea plain files | Доступная distribution | PASS identification | E2_VERSION4199, E2_RELEASE11.5; fingerprints ниже, без чтения всего compiled core в контекст | +| GET GitHub `issues?state=all&per_page=100` | Connected read-only GitHub | PASS lookup | Пустой массив, нет issue duplicates; ничего не создано | +| Read-only `git ls-remote origin refs/heads/main` | Отдельный publication checkout | PASS | Remote a95eb2c… до подготовки второго report; требуется fresh check перед push | Дополнительные synthetic assertions подтвердили: unknown top-level/UI/meta fields отбрасываются; version1/wrong boolean type/malformed link отвергаются; traversal slug даёт metadata error; PHP quotes/backslash экранируются; light/dark sessions принимаются, system/missing/version1 игнорируются; storage exceptions не выбрасываются наружу; все 10 presets round-trip; неизвестная Google family нормализуется к plain; system font с CSS punctuation заменяется safe fallback; dark selector и light metadata colors сохраняются; полностью locked Random сохраняет controlled keys и font sources. @@ -353,12 +357,12 @@ Aegea distribution fingerprints: variables.scss `77c53bfb7c0d94c980bdec2b523bf60 ### Неподтверждённые hypotheses — только эксперименты -| ID | Гипотеза / среда | Минимальный эксперимент и evidence | Решение после PASS/FAIL | -| --- | --- | --- | --- | -| V01 | Pending File.text может перезаписать Reset/второй import/manual edit. App WT:497–511. Browser upload в этом проходе NOT RUN; controlled race не воспроизведена. | В temp component harness задержать File.text(A), затем B/Reset/edit, завершить A. Записать последовательность apply и конечную тему; отдельно проверить error feedback от stale request. UI File input тоже проверить в разрешённом браузере. | Если старый A применяется после более нового пользовательского выбора — новый доказанный finding и отдельное минимальное ordering решение; иначе не добавлять generation token только из предположения. R05 не получает этот scope автоматически. | -| V02 | Exact Aegea baseline/provenance и rendered font parity пока неполны; это **известный verification gap**, не новая runtime проблема. | Получить release11.5/v4199 oracle или нужный commit в авторизованной среде, запустить PHP/runtime отдельно от чужого сайта; сравнить один EN/RU текст и одинаковую ширину, зафиксировать font stack/linebreaks. | Совпадение/подтверждённый mismatch закрывает R04 consumer часть; если oracle различается, пересмотреть target/fallback с владельцем. Не объявлять12.0a новым target молча. | -| V03 | Preview responsive queries по editor viewport и narrow header могут расходиться с consumer шириной/доступностью. Не подтверждено как отдельный F/N. | Парные geometry screenshots preview vs isolated consumer при одинаковой фактической ширине, margins и scale; header641px в RU/EN и200% zoom, bounds/overlap/hit targets. | Functional inaccessible/contract mismatch → новое узкое finding; просто тесный вид → optional polish, без rewrite и без смешения с C03. | -| V04 | Huge payload/Google Fonts catalog/network/contrast edge cases могут давать отдельный ущерб. Пока нет измеренного failure. | Раздельные bounded experiments в temp: размеры1/5/20MB и responsiveness; одна известная family + offline/slow response; marked-title contrast на valid palette. Сетевой тест — только с нужным разрешением и текущей первичной API документацией. | Измеренный freeze/bad response/неверное обещание → evidence и узкий follow-up; иначе no action. Не вводить произвольный size limit, массовый catalog refresh или WCAG policy из гипотезы. | +| ID | Гипотеза / среда | Минимальный эксперимент и evidence | Решение после PASS/FAIL | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| V01 | Pending File.text может перезаписать Reset/второй import/manual edit. App WT:497–511. Browser upload в этом проходе NOT RUN; controlled race не воспроизведена. | В temp component harness задержать File.text(A), затем B/Reset/edit, завершить A. Записать последовательность apply и конечную тему; отдельно проверить error feedback от stale request. UI File input тоже проверить в разрешённом браузере. | Если старый A применяется после более нового пользовательского выбора — новый доказанный finding и отдельное минимальное ordering решение; иначе не добавлять generation token только из предположения. R05 не получает этот scope автоматически. | +| V02 | Exact Aegea baseline/provenance и rendered font parity пока неполны; это **известный verification gap**, не новая runtime проблема. | Получить release11.5/v4199 oracle или нужный commit в авторизованной среде, запустить PHP/runtime отдельно от чужого сайта; сравнить один EN/RU текст и одинаковую ширину, зафиксировать font stack/linebreaks. | Совпадение/подтверждённый mismatch закрывает R04 consumer часть; если oracle различается, пересмотреть target/fallback с владельцем. Не объявлять12.0a новым target молча. | +| V03 | Preview responsive queries по editor viewport и narrow header могут расходиться с consumer шириной/доступностью. Не подтверждено как отдельный F/N. | Парные geometry screenshots preview vs isolated consumer при одинаковой фактической ширине, margins и scale; header641px в RU/EN и200% zoom, bounds/overlap/hit targets. | Functional inaccessible/contract mismatch → новое узкое finding; просто тесный вид → optional polish, без rewrite и без смешения с C03. | +| V04 | Huge payload/Google Fonts catalog/network/contrast edge cases могут давать отдельный ущерб. Пока нет измеренного failure. | Раздельные bounded experiments в temp: размеры1/5/20MB и responsiveness; одна известная family + offline/slow response; marked-title contrast на valid palette. Сетевой тест — только с нужным разрешением и текущей первичной API документацией. | Измеренный freeze/bad response/неверное обещание → evidence и узкий follow-up; иначе no action. Не вводить произвольный size limit, массовый catalog refresh или WCAG policy из гипотезы. | В этом проходе не делались текущие заявления об изменчивом Google API и не повторялся web lookup Astra: его внешняя ссылка не используется как доказательство сегодняшней доступности каталога. V04 требует свежую первичную документацию при выполнении. @@ -384,4 +388,3 @@ Aegea distribution fingerprints: variables.scss `77c53bfb7c0d94c980bdec2b523bf60 - Source/config/dependencies/lockfile/demo/PROGRESS не изменены. В исходном checkout добавлен только этот второй report; первый сохранён. Issues/PR/releases/deployments и execution remediation не выполнялись. Отдельная разрешённая публикация audit-файлов не переносит локальный code diff в remote. - Все F01–F05 учтены ровно по одной строке matrix; добавлен ровно один N01. Rejected claims не возвращены в scope. Canonical mappings и plan не содержат циклов или duplicate fixes. - До сохранения проверено отсутствие существующего `audit/2-sol-6-1-xhigh.md`; исходные tracked snapshots и index проверены повторно. Report не содержит machine-local paths, секретов или настоящих пользовательских payloads. - From 954f71daf3e80104dc559efedd8f554c2aa08bde Mon Sep 17 00:00:00 2001 From: Vladimir Belov Date: Sat, 3 Oct 2026 18:00:52 +0700 Subject: [PATCH 5/6] fix(ci): restore quality gate after UI integration --- .project/PROGRESS.md | 50 +++++++++++++- src/components/App/App.css | 20 ++---- src/components/App/AppLayers.css | 8 +++ src/components/App/shellLayers.test.js | 34 +++++++++ .../PresetSelector/PresetSelector.vue | 6 +- src/main.js | 1 + src/storage.test.js | 69 ++++++++++++------- 7 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 src/components/App/AppLayers.css create mode 100644 src/components/App/shellLayers.test.js diff --git a/.project/PROGRESS.md b/.project/PROGRESS.md index 576027c..aad1457 100644 --- a/.project/PROGRESS.md +++ b/.project/PROGRESS.md @@ -801,8 +801,8 @@ path in a real local Aegea instance. ## Technical iteration: remote quality gate (R02) Baseline: `origin/main` at `a95eb2c4df1151a8d9adad5afe3e7919ceef7968`. -The remote implementation still supports shell `system`; its valid-session -fixture is correct. Tests (99/99), JavaScript/style lint, and build pass. Only +At that original baseline, the remote implementation supported shell `system`; +its valid-session fixture was correct. Tests (99/99), JavaScript/style lint, and build passed. Only Prettier fails on the two already tracked audit prompt documents. - [x] Format only `audit/1-astra-xhigh-prompt.md` and @@ -818,3 +818,49 @@ no diff. No new test is needed for Markdown formatting. Local verification uses Node 24/macOS. Next small step: create a draft PR and verify the existing Node 22/Linux CI without running deployment. + +## Technical iteration: quality gate after main update + +New baseline: `8662ce5a7a94fff4df4f95bab34a0f48eea9b561`. It includes the +UI migration, concrete light/dark session policy, document-root colour scheme, +and already published audit reports. Reproduced on an isolated worktree: +98/99 tests, one PresetSelector formatting error, seven App.css style errors, +and formatting failures in two prompts, two reports and PresetSelector. +Build passes. No dependencies or quality rules are changed. + +- [x] Merge new main into this branch without rewriting history; retain its UI. +- [x] Format only the PresetSelector opening tag, with unchanged attributes. +- [x] Align the session test fixture with the current light/dark contract and + add explicit rejection regressions for legacy system/missing values. + Coordinate `storage.test.js`; leave runtime persistence unchanged. +- [x] Move global root/popper layer declarations out of scoped App.css into + global shell CSS, preserving selectors/values. Consolidate the duplicate + download-error rule and replace deprecated clip with equivalent clip-path. + Coordinate App.css; keep widths, overlays and theme behavior unchanged. +- [x] Format the already published reports in a separate commit under the + renewed technical-fix authorization. Their normalized Markdown and + embedded JavaScript ASTs match the baseline: words, data, links and + semantic structure are unchanged. +- [x] Run all five checks locally: 104/104 tests, JS/style lint, formatting and + build pass. Unknown ordinary CSS pseudo-classes still fail stylelint. +- [ ] Confirm clean Node 22/Linux PR CI after pushing this branch. + +Local clean npm ci is blocked by EALLOWGIT for the pinned hvab-blocks dependency. +No bypass was attempted. Existing installed dependencies were copied with +executable symlinks preserved; the shared and new-main lockfiles match exactly. +The CI clean install remains the authoritative independent install check. + +Verified shell layers: Vue compilation of the former scoped :global rules and +the new global CSS produces identical selectors/declarations/priorities. +Chrome light/dark Share/Reset portals use floating z-index 10 and modal 20; +Copy-link status retains role=status, a 1px box and inset(50%) clipping. Metadata +errors still disable ZIP; download-error flex-basis remains auto in the header. + +Read-only npm audit: 16 flagged package records (14 high, 1 moderate, 1 low), +including inherited dependency records, not 16 independent vulnerabilities. +Production-only audit flags PostCSS and nanoid through Vue's compiler package; +the built browser chunks contain no modules from either package. Tooling +issues include brace-expansion, braces and its stylelint chain, fast-uri, +js-yaml, colord and postcss-selector-parser. Audit proposes breaking downgrades +for the braces/stylelint chain; do not run audit fix --force. Dependencies and +lockfile remain unchanged. Dependency remediation requires separate scope. diff --git a/src/components/App/App.css b/src/components/App/App.css index 14b2b39..f7ff072 100644 --- a/src/components/App/App.css +++ b/src/components/App/App.css @@ -8,15 +8,6 @@ background: var(--hb-color-base-background); } -:global(:root) { - --app-layer-floating: 10; - --app-layer-modal: 20; -} - -:global([data-reka-popper-content-wrapper]) { - z-index: var(--app-layer-floating) !important; -} - .app__topbar { display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; @@ -50,10 +41,6 @@ align-items: flex-end; } -.app__download .download-error { - flex-basis: auto; -} - .app__download { display: flex; flex-direction: column; @@ -127,10 +114,11 @@ .download-error { flex-basis: 100%; margin-top: 0.5rem; + color: var(--hb-color-text-danger); } -.download-error { - color: var(--hb-color-text-danger); +.app__download .download-error { + flex-basis: auto; } .app__visually-hidden { @@ -140,9 +128,9 @@ padding: 0; margin: -1px; overflow: hidden; - clip: rect(0 0 0 0); white-space: nowrap; border: 0; + clip-path: inset(50%); } .app__import-json-input { diff --git a/src/components/App/AppLayers.css b/src/components/App/AppLayers.css new file mode 100644 index 0000000..36576fc --- /dev/null +++ b/src/components/App/AppLayers.css @@ -0,0 +1,8 @@ +:root { + --app-layer-floating: 10; + --app-layer-modal: 20; +} + +[data-reka-popper-content-wrapper] { + z-index: var(--app-layer-floating) !important; +} diff --git a/src/components/App/shellLayers.test.js b/src/components/App/shellLayers.test.js new file mode 100644 index 0000000..84f5cc9 --- /dev/null +++ b/src/components/App/shellLayers.test.js @@ -0,0 +1,34 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { compileStyle } from 'vue/compiler-sfc'; + +const layerCss = readFileSync(new URL('./AppLayers.css', import.meta.url), 'utf8'); +const mainJs = readFileSync(new URL('../../main.js', import.meta.url), 'utf8'); +const formerScopedLayers = ` +:global(:root) { + --app-layer-floating: 10; + --app-layer-modal: 20; +} +:global([data-reka-popper-content-wrapper]) { + z-index: var(--app-layer-floating) !important; +} +`; + +function compiledCss(source, scoped) { + const result = compileStyle({ source, filename: 'layers.css', id: 'data-v-shell', scoped }); + assert.deepEqual(result.errors, []); + return result.code.replace(/\s+/g, ' ').trim(); +} + +test('unscoped shell layers preserve the compiled Vue global selectors and priorities', () => { + assert.equal(compiledCss(layerCss, false), compiledCss(formerScopedLayers, true)); + assert.doesNotMatch(compiledCss(layerCss, false), /data-v-shell|:global/); +}); + +test('global shell layers load after library styles and before the app', () => { + const libraryImport = mainJs.indexOf("import './ui/hvab.css';"); + const layerImport = mainJs.indexOf("import './components/App/AppLayers.css';"); + const appImport = mainJs.indexOf("import App from './components/App/App.vue';"); + assert.ok(libraryImport >= 0 && layerImport > libraryImport && appImport > layerImport); +}); diff --git a/src/components/PresetSelector/PresetSelector.vue b/src/components/PresetSelector/PresetSelector.vue index 102c685..8ef20fb 100644 --- a/src/components/PresetSelector/PresetSelector.vue +++ b/src/components/PresetSelector/PresetSelector.vue @@ -40,11 +40,7 @@ function getPresetLabel(preset) { - +