Skip to content
91 changes: 91 additions & 0 deletions .project/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1474,3 +1474,94 @@ screen-reader verification remain unverified; no V01–V04 expansion is claimed.
PR #1 was closed as a duplicate because R02 is already included in main.
Next small step: owner decision whether to merge PR #2. No merge or deployment
was performed.

## 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.

Updated after main `8662ce5`: the font regression now reads the relocated
AegeaPreview.css and uses corrected relative theme imports. The updated R02
branch is merged as the quality prerequisite; its Node 22/Linux CI passes.
Combined verification passes locally: 107/107 tests, JS/style lint, formatting
and build. Chrome on the new UI confirms EN/RU/light/dark plain inheritance,
explicit system overrides, one PT Sans CSS link and removal on return to plain.
Next small step: push the updated draft PR #4 and inspect its CI. Merge R02
first. Live Aegea visual verification remains open.

## R04 refresh against current main (2026-10-19)

- [x] Merge current `origin/main` into the existing R04 fix branch without
rewriting history; resolve only integration conflicts.
- [x] Recheck the pinned Aegea 11.5/v4199 font contract, relevant markup,
variables and preview states against the available source checkout.
- [x] Verify the existing font regression, generation/validation compatibility
and quality commands after a clean dependency install.
- [x] Record factual results and prepare the updated PR description; independent
review and browser smoke accepted. Publication and remote CI completed.

Refresh results: merged main `6b69a417cc50473d15d0e9e69abc1a222d7655c7`
without rewriting history; the only conflict was this progress document. Main's
R01 validation, session fixes and hvab-blocks v0.3.0 are retained. The R04 diff
against main remains the font default, existing font regression and provenance.

Clean `npm ci --cache` succeeded after the sandbox DNS failure was isolated;
the completed network-enabled clean install was repeated after the first
process exited. No dependency files or install-script policy were changed.
All five quality commands pass: 131/131 checks, JS/style lint, full formatting
and production build. The targeted font regression passes 3/3; evaluating it
against unpatched main CSS fails only the intended plain-stack assertion (2/3).
Existing R01 boundary coverage passes together with the plain ZIP and explicit
system/Google override checks. Install reports 19 inherited audit package
records (2 moderate, 17 high); dependency remediation is outside R04.

The pinned engine source/compiled CSS and current development source were
rechecked; details are in PREVIEW-BASELINE.md. This is source-contract evidence,
not a live Aegea width/linebreak comparison. The earlier clean-install blocker
is resolved. Independent review found no issues, and the root accepted the
source diff and current browser smoke before publication.

Browser smoke in the in-app browser on macOS confirms canonical computed stacks
for EN/light plain preview, note and metadata; the shell keeps its own
`system-ui, sans-serif` stack. Explicit Sans-serif interface and Georgia note
choices apply correctly. PT Sans in both slots creates one Google CSS link;
returning to plain removes it. RU/dark preview with a dark shell retains plain
font inheritance. These are UI/computed-style checks, not proof of rasterized
font identity or paired live Aegea widths/linebreaks. A fresh `php -v` check
still fails because `libzip.5.dylib` is missing, so live Aegea verification
remains blocked; no environment repair was attempted.

Published code commit: `8e1685e5efe8228fdb1704e28eaf9cd971fdb92d`.
[Remote CI succeeded](https://github.com/hvab/Selecta/actions/runs/39243742098)
for that code commit; PR #4 is mergeable against main `6b69a41`. This result
does not claim CI coverage for the subsequent documentation-only update.

Next small step: the owner decides whether to merge PR #4. The unresolved live
Aegea width/linebreak comparison remains a separate verification limit because
PHP cannot start without `libzip.5.dylib`. No PR merge is part of this refresh.
24 changes: 24 additions & 0 deletions PREVIEW-BASELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -74,3 +87,14 @@ When preview-related code changes:
5. Recheck link underline behavior for the selected Aegea target version.
6. Recheck whether the preview still covers all required states from `SPEC.md`.
7. Update this baseline when the checked Aegea release target or the preview contract changes.

## R04 refresh source check (2026-10-19)

- The available release checkout still matches the pinned `11.5` / `v4199`
commit. Source and compiled plain CSS retain the system font stack above.
- The available development checkout at
`fada36ef1e5fba78d8d9f66dcc1b0d646e2df2fb` still uses InterVariable;
it remains outside this release target. Its plain CSS-variable names and
English/Russian preview content match the pinned release. Its layout adds
a drafts block outside Selecta's supported preview states.
- This refresh changes no preview markup or represented states.
4 changes: 3 additions & 1 deletion src/components/AegeaPreview/AegeaPreview.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
72 changes: 72 additions & 0 deletions src/components/AegeaPreview/fonts.test.js
Original file line number Diff line number Diff line change
@@ -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('./AegeaPreview.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);
});
Loading