Skip to content

feat(web): give display figures an optical size with Inter's opsz axis - #948

Merged
mforce merged 8 commits into
mainfrom
feat/835-836-type-and-mark
Sep 24, 2026
Merged

mforce merged 8 commits into
mainfrom
feat/835-836-type-and-mark

Conversation

@mforce

@mforce mforce commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

A slice of epic #674, milestone "SPA revamp", inherited from #656.

Closes #835

Component plan. docs/designs/822-mui-revamp.md delivery row 13, "#835 opsz", whose design is D7.2.

Two of this issue's three premises were false, and both were measured

The face. The issue's 2026-09-14 amendment is right and this PR confirms it by measurement rather than by reading CSS. @fontsource-variable/inter's default entry loads seven -wght- faces. In Chromium, "Recorded 1,248 eggs" at 32px is 331px wide at opsz 14 and 310px at opsz 32 on the opsz face, and 331px at both on the wght one. The axis was not un-tuned, it was absent. font-optical-sizing: auto is the CSS initial value, so swapping the import is the entire behaviour change: a 32px figure gets the display cut, a 14px row keeps the text cut, and no declaration is added anywhere.

The targets. The issue asks for opsz on h1, h2 and the stat figures. h1, h2 and every Dashboard, Sales, Reports, Inventory and Stock figure are Georgia, serif since #864, pinned by farmTheme.policy.test.ts's "uses serif page headings" case, and Georgia has no variable axes. That half of the issue has no target left. What the axis does reach is the six figures still set in --font: Daily Entry's grade figure (1.375rem) and readout value (1.6rem), the entry chip figure (1.35rem), History's sellable figure (1.5rem), and the Expenses period total (2rem, the largest Inter text in the app).

The budget. D7.2 priced the swap at +118.9 KiB against a projected 1,806 KiB baseline, and named a fallback of "one display cut for h1/h2 only" if it did not fit. Baseline on e0c1f68 is actually 1,860.89 KiB, and that fallback is unbuildable for the reason above. So neither the cost nor the escape hatch held.

Precache, measured by building each option, not by arithmetic

option precache vs main 1,900 KiB ceiling
main (wght, 7 subsets) 1,860.89 KiB — fits, 39.11 spare
full opsz.css, 7 subsets 1,979.79 KiB +118.90 over by 79.79
opsz latin + latin-ext, wght for the rest 1,932.12 KiB +71.23 over by 32.12
declare only the latin opsz faces 1,847.69 KiB −13.20 fits
declare only latin 1,717.13 KiB −143.76 fits
shipped: full opsz.css, five subsets out of the PRECACHE 1,848.93 KiB −11.96 fits, 51.07 spare

The shipped option keeps every subset in dist. The UI is en/es/tl, all inside latin and latin-ext, so globIgnores drops the offline precopy of the other five; unicode-range still fetches them on demand for a Cyrillic or Greek farm or customer name. The two rejected latin-only rows save more and lose those glyphs even online. The ceiling was not touched. This slice pays for its own axis and returns 11.96 KiB.

Evidence

Before/after 1:1 frames are attached as comments. At page scale the change is correctly undramatic: 0.14% to 2.34% of pixels differ across Dashboard, Expenses, History, Daily entry and Login. Magnified, the 32px figure is visibly a finer, tighter cut and body copy is unchanged. Note the figures carry font-variant-numeric: tabular-nums, so their width cannot change by design — only the cut does.

Tests

suite before after
web unit (files) 141 142
web unit (tests) 3,474 3,482
Playwright quick (files) 23 24
Playwright quick (tests) 97 98

Full quick suite at head against an isolated stack: 97 passed, 1 skipped, 0 failed, both projects. npm run typecheck clean in web/ and tools/simulation/ui/. dotnet test --filter "ImagePin|RealTree" 14/14.

Mutation checks, every one shown red before it was claimed

mutation assertion that went red
main.tsx back to the default entry loads Inter faces that carry the opsz axis, and no wght-only face
main.tsx to wght.css explicitly same
globIgnores widened to **/inter-*.woff2 drops only subsets outside the app's locales
latin-ext added to globIgnores same (the not.toMatch(/\blatin\b/) arm)
font-optical-sizing: none on :root never turns optical sizing off
font-variation-settings: "opsz" 32 on :root never pins opsz to one value for every size
default entry + rebuild verify-sw.mjs checks 5a and 5b, naming the two wght faces found in the precache
the Playwright spec run against a stack built at the BASE SHA 32px text renders identically with optical sizing on and off

That last one is not a hand-edited file. It is the same spec against two real builds on two isolated stacks, green on the opsz build and red on the wght one.

One guard rewritten because it proved nothing

The browser spec first measured a stat figure's width. Every figure this slice is for carries tabular-nums, which fixes each digit to one advance by design, so the two cuts are required to render the same width. It passed at ~1px of noise whichever face was loaded. It now probes letters at 32px and 14px and separately asserts the app's own display text resolves to that same family, so the proof applies to the screen.

Deletions

No CSS class, component or i18n key was deleted, so no repo-wide grep was needed. One test block was dropped in the deslop commit: its serif assertion duplicated styles.test.ts:26, and pinning five literal figure sizes would have failed on any legitimate restyle with no defect behind it.

Docs

docs/designs/822-mui-revamp.md D7.2 carries an amendment recording which of the five options was chosen and why, since that paragraph says the doc records it. web/scripts/verify-sw.mjs's ceiling comment is corrected: it still projected "+118.9 KiB, ~1,907 KiB, over by ~7" and named the unbuildable h1/h2 fallback. No GLOSSARY or Help change: #835 adds no concept and renames no control.

Closes #835 in part; the rest of the issue's premise no longer holds.

The default `@fontsource-variable/inter` entry loads seven `-wght-` faces,
which pin every size to Inter's opsz 14 text cut, so `font-optical-sizing`
on them is inert. Measured in Chromium: "Recorded 1,248 eggs" at 32px is
331px wide at opsz 14 and 310px at opsz 32, and 331px at both on the wght
face. Loading the `opsz.css` entry is the whole behaviour change, because
`font-optical-sizing: auto` is the CSS initial value.

The full entry costs 118.9 KiB of precache and lands at 1,979.79 KiB, 79.79
over #825's ceiling. The five Inter subsets no locale renders are dropped
from the PRECACHE instead, not from the build: they still ship in dist and
are still fetched on demand by unicode-range. 1,848.93 KiB, 11.96 below
main.
Comments in main.tsx and vite.config.ts trimmed to the measurement a reader
cannot derive. Dropped opticalSize.test.ts's last block: its serif assertion
duplicates styles.test.ts, and pinning five literal figure sizes would fail on
any legitimate restyle with no defect behind it.
The first version measured a stat figure's width. Every figure this slice is
for carries font-variant-numeric: tabular-nums, which fixes each digit to one
advance by design, so the two cuts are REQUIRED to render the same width: the
check passed at ~1px of noise whichever face was loaded. It now probes letters
at 32px and 14px and separately asserts the app's own display text resolves to
that same family, so the proof applies to the screen.
@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Before frames

Captured 1:1 from an isolated stack (cw835b, port 8135) built at the base SHA e0c1f68, default-farm, simulation fixture.

Dashboard

Dashboard at 1280 in light, before
Dashboard at 1280 in dark, before
Dashboard at 390 in light, before
Dashboard at 390 in dark, before

History (a Field Console ledger)

History (a Field Console ledger) at 1280 in light, before
History (a Field Console ledger) at 1280 in dark, before
History (a Field Console ledger) at 390 in light, before
History (a Field Console ledger) at 390 in dark, before

Flocks (a setup list)

Flocks (a setup list) at 1280 in light, before
Flocks (a setup list) at 1280 in dark, before
Flocks (a setup list) at 390 in light, before
Flocks (a setup list) at 390 in dark, before

Daily entry

Daily entry at 1280 in light, before
Daily entry at 1280 in dark, before
Daily entry at 390 in light, before
Daily entry at 390 in dark, before

Expenses (the largest Inter figure in the app)

Expenses (the largest Inter figure in the app) at 1280 in light, before
Expenses (the largest Inter figure in the app) at 1280 in dark, before
Expenses (the largest Inter figure in the app) at 390 in light, before
Expenses (the largest Inter figure in the app) at 390 in dark, before

Login

Login at 1280 in light, before
Login at 1280 in dark, before
Login at 390 in light, before
Login at 390 in dark, before

@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

After frames

After frames captured at c882c91.

Same stack recipe (cw835a, port 8136), same farm, same data, same viewports. The two commits after that build change comments and a test file only, nothing the browser renders.

Dashboard

Dashboard at 1280 in light, after
Dashboard at 1280 in dark, after
Dashboard at 390 in light, after
Dashboard at 390 in dark, after

History (a Field Console ledger)

History (a Field Console ledger) at 1280 in light, after
History (a Field Console ledger) at 1280 in dark, after
History (a Field Console ledger) at 390 in light, after
History (a Field Console ledger) at 390 in dark, after

Flocks (a setup list)

Flocks (a setup list) at 1280 in light, after
Flocks (a setup list) at 1280 in dark, after
Flocks (a setup list) at 390 in light, after
Flocks (a setup list) at 390 in dark, after

Daily entry

Daily entry at 1280 in light, after
Daily entry at 1280 in dark, after
Daily entry at 390 in light, after
Daily entry at 390 in dark, after

Expenses (the largest Inter figure in the app)

Expenses (the largest Inter figure in the app) at 1280 in light, after
Expenses (the largest Inter figure in the app) at 1280 in dark, after
Expenses (the largest Inter figure in the app) at 390 in light, after
Expenses (the largest Inter figure in the app) at 390 in dark, after

Login

Login at 1280 in light, after
Login at 1280 in dark, after
Login at 390 in light, after
Login at 390 in dark, after

@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

What actually changed, enlarged

At page scale the frames above look identical, which is the correct result for a change of letterform
cut. Measured pixel difference between each before/after pair:

frame pixels changed
Expenses 1280 light 0.404% (4,138 of 1,024,000)
Dashboard 1280 light 0.268% (2,741 of 1,024,000)
Daily entry 390 light 2.342% (7,708 of 329,160)
History 1280 dark 0.368% (3,769 of 1,024,000)
Login 1280 light 0.140% (1,438 of 1,024,000)

Daily entry at 390 moves most because it carries the most Inter figures. Login moves least because its
only display text is the h1, which is Georgia.

Crops from those same 1:1 frames, magnified with no smoothing. The $835.00 total is visibly a finer,
tighter cut after; body copy at 14px is unchanged, which is the half of the issue that says keep body
text at text optical size
. The figures carry font-variant-numeric: tabular-nums, so their width
cannot change by design — only the cut does, and that is why the browser guard measures letters.

Expenses total, body copy and the sidebar slot, before and after, magnified with no smoothing

@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Closed without merging: the owner decided on 2026-09-24 to skip #835 (display optical size) and keep the current logo (#836). Neither change is wanted.

@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Reopened: the close earlier today was a coordinator misreading of the owner's reply. The owner meant to skip the screenshot preview, not the change. #835 continues here. #836 stays closed (the logo is kept unchanged).

verify-sw.test.mjs's minimal fixture didn't include the Inter latin/
latin-ext opsz faces #835 added as check 5, so "passes and reports
headroom..." failed CI. Extend the fixture with zero-byte stubs for
both faces (and, ahead of the next commit, a matching runtime-caching
route) so the passing case satisfies every current check while the
failing (over-ceiling) case keeps failing for the size reason alone.
…sized

#948 review round 1 (Codex gpt-6-sol), P2: #835's opsz.css import left
font-optical-sizing at its CSS default (auto) everywhere, and auto
varies CONTINUOUSLY with an element's real font-size — so MUI body1
and TableCell at their real 16px on phones drifted toward the display
cut too, not just the six intended stat figures.

Pin font-variation-settings: "opsz" 14 once, globally, at styles.css's
:root (inherited by everything), and clear that pin with an explicit
"normal" on exactly the six stat figures, handing them back to
automatic, size-driven sizing.

Updates the two tests that assumed no opsz declaration would ever
exist: opticalSize.test.ts's "never pins opsz" case now allows the one
root pin and still rejects a pin anywhere else; the Playwright spec's
body assertion was a detached 14px probe that never rendered anything
MUI actually ships, replaced with a real-page check (real display
figure vs. real ~16px body text at phone width), shown failing before
this fix (0fd0b97) and passing after.
#948 review round 1 (Codex gpt-6-sol), P2: #835's globIgnores dropped
Cyrillic/Cyrillic-ext/Greek/Greek-ext/Vietnamese from the precache with
no replacement, so a farm or customer name in one of those scripts had
no offline path once the browser's own HTTP cache aged out.

Add a narrow CacheFirst runtime-caching route keyed to exactly those
five woff2 filenames (by name, not just extension, so it can never
match /api or /health). verify-sw.mjs's check 2 now allows exactly one
runtime-caching route and asserts, against the real emitted worker,
that it's CacheFirst, matches all five dropped subsets, matches
neither already-precached Latin subset, and cannot answer any of the
denylist's own /api or /health probe paths — mutation-tested by
removing the route, widening its pattern to match everything, and
swapping its strategy, all three shown red before this commit and
green after.

Precache stays 1849.11 KiB, 50.89 KiB under the #825 ceiling — the
route adds a service-worker rule, not precached bytes.
@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Fixed all three findings; head is now d33e1f5. No logo/icon changes anywhere in this branch (git diff origin/main...HEAD --stat — #836 stays out).

CI fix — verify-sw.test.mjs:46 red on 0fd0b97

scripts/verify-sw.mjs's check 5 (added by #835) requires the Inter latin/latin-ext opsz faces to be precached; the test's minimal fixture had neither, so the passing case failed its own guard.

Fix: 65fff2a — extended buildFixture with zero-byte stub font files for both subsets (and, once check 2 changed below, a matching runtime-caching route), so the passing case satisfies every current check while the over-ceiling case still fails for the size reason alone.

Verification: node --test scripts/verify-sw.test.mjs — red before (1 !== 0, passes and reports headroom...), green after (2/2).

Codex gpt-6-sol round 1, P2 — main.tsx:8: opsz leaks into body text

font-optical-sizing: auto (the CSS default #835's opsz.css import relies on) varies continuously with an element's real font-size, so MUI body1/TableCell at their real 16px on phones drifted toward the display cut along with the six intended stat figures.

Fix: 1597a4f — font-variation-settings: "opsz" 14 pinned once, globally, at styles.css's :root (inherited everywhere); the six stat figures (Daily Entry's grade readout and summary stats, the entry-chip figure, History's sellable figure, Expenses' period total) clear it back to normal, handing them to automatic size-driven sizing.

Test: replaced the detached-14px-probe assertion in optical-size.spec.ts (it never measured anything MUI actually renders) with a real-page check at phone width on /expenses: the real period-total figure (getComputedStyle(...).fontVariationSettings === "normal") next to a real body1 label (pinned '"opsz" 14' by inheritance, and its rendered width matches the same text forced to opsz 14 within 3px). Shown red on 0fd0b97 ("did not inherit the root's text-cut pin" — Expected: '"opsz" 14', Received: "normal"), green after the fix, rebuilt and re-verified against a live isolated stack both ways.

One correction en route: Expenses' own <td>s turned out not to demonstrate this bug — FieldConsole.tsx pins every ledger table cell to 0.75rem at every breakpoint via a higher-specificity sx override, so the "16px TableCell" the review named doesn't literally render anywhere in the shipped app once a table is wrapped in the Field Console redesign (#899). Used the app-bar's real body1 farm-name label instead — same mechanism (unpinned inherited text at a real page-rendered size), same bug.

Codex gpt-6-sol round 1, P2 — vite.config.ts:109: five subsets dropped with no offline replacement

Cyrillic/Cyrillic-ext/Greek/Greek-ext/Vietnamese were dropped from the precache (globIgnores) with runtimeCaching: [], so a farm/customer name in one of those scripts had no offline path once the browser's own HTTP cache aged out.

Fix: d33e1f5 — a narrow CacheFirst runtime-caching route keyed to exactly those five woff2 filenames (by name, so it can't match anything without a matching filename — never /api or /health, which have no .woff2 extension). verify-sw.mjs check 2 now requires exactly one runtime-caching route and asserts, against the real emitted worker, that it's CacheFirst, matches all five dropped subsets, matches neither already-precached Latin subset, and can't answer any of the denylist's /api//health probe paths.

Mutation checks, all shown red before green:

  • removed the route entirely → expected exactly one runtime caching route..., found 0
  • widened the pattern to /.*/ → 8 failures (would answer /api from cache, /health, both Latin subsets)
  • swapped CacheFirst → NetworkFirst → uses NetworkFirst strategy, not CacheFirst

Precache: 1849.11 KiB, 50.89 KiB under the #825 1,900 KiB ceiling — unchanged from before this fix (a runtime-caching rule adds no precached bytes).

Full verification

  • npm run typecheck — clean
  • npm run test:coverage — 3482 tests, 141-142/142 files green; the one intermittent failure (DailyEntryPage.test.tsx's role-change-session case) is a load flake under full-suite parallelism, confirmed by running it standalone twice (passes both times) — unrelated to this diff, matches a flake the original implementer already logged on this same branch.
  • node --test scripts/verify-sw.test.mjs — 2/2
  • tools/simulation/ui/specs/optical-size.spec.ts — both tests, both projects (chromium, chromium-phone) green against an isolated stack (cw948a, port 8148) rebuilt at head; also rebuilt and reran at 0fd0b97 to confirm both new/changed assertions are genuinely red there
  • pstack:deslop — reviewed the full diff; already consistent with the repo's WHY-comment convention, no dead code/any/defensive scaffolding found, no changes needed

Isolated stack (cw948a) fully torn down: down -v, image removed, compose/env files deleted, host clean.

…t requests

#948 review round 2 (Codex gpt-6-sol), P2: the Inter extended-subset
CacheFirst route's RegExp urlPattern is tested by Workbox against the
request's FULL href, unanchored — so it also matched
/api/v1/x/inter-greek-opsz-normal-deadbeef.woff2, a same-origin path
that merely ends with the right suffix. verify-sw.mjs's check 2 only
probed bare denylist paths (/api, /health, ...), never one with a
font-like suffix appended, so it never exercised the actual gap.

Replace the regex with a match-callback function
(font-cache-match.mjs's matchesInterExtendedSubsetFont, a plain
dependency-free module both vite.config.ts and the test import
directly): same-origin, url.pathname starting with /assets/, ending in
.woff2, and request.destination === "font" — a signal a regex cannot
see at all.

verify-sw.mjs's check 2 now requires the runtime route to be a
match-callback function (a RegExp fails outright) and statically
verifies the function's source carries all four guards, keyed on
substrings that survive minification (destructured parameter
*property* names and member-expression/string-literal text, never the
renamed local bindings). Confirmed the old d33e1f5 regex itself
matches the vulnerable path, and that the old verify-sw.mjs (checked
out at d33e1f5) passed a fixture built from that exact regex — the gap
was real. The new guard rejects it outright
("rejects a RegExp runtime-caching route" test), and three more
mutations (dropping the /assets/ prefix, dropping the destination
check, restoring the d33e1f5 regex via a real vite build) all shown
red before green.

matchesInterExtendedSubsetFont also gets direct behavioral tests
(never eval'd text, a real function import) proving the review's exact
case: a font-suffixed API path is rejected, the real asset path is
accepted, plus cross-origin and non-font-destination rejection.

Precache unchanged: 1849.11 KiB, 50.89 KiB under the #825 ceiling — a
route matcher, not precached bytes.
#948 review round 2 (Codex gpt-6-sol), P3: the amendment claimed no
font-optical-sizing/font-variation-settings declaration existed
anywhere and that opticalSize.test.ts would fail if one ever appeared
— true when #835 first landed, false since #948's own P2 fix pinned
font-variation-settings: "opsz" 14 at styles.css's :root. A maintainer
following the old text could "fix" the doc's claimed invariant by
removing that pin and reopening the 16px phone-body regression.

Add an amendment describing the root text-cut pin, the stat-figure
overrides that clear it, and what opticalSize.test.ts now actually
enforces (exactly one numeric pin, at the root; nothing else pins a
value).

dotnet test tests/Cluckwork.Application.Tests --filter
"FullyQualifiedName~ImagePin|FullyQualifiedName~RealTree" run before
committing: 14/14 passed.
@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Fixed both round 2 findings; head is now de08553.

Codex gpt-6-sol round 2, P2 — vite.config.ts:136/verify-sw.mjs:179: font runtime-cache regex partially matches API URLs

Round 1's urlPattern was a RegExp, which Workbox tests against the request's full href, unanchored — so it also matched /api/v1/x/inter-greek-opsz-normal-deadbeef.woff2, and a regex has no way to see request.destination at all. verify-sw.mjs's round-1 check only probed bare denylist paths (/api, /health, …), never one with a font-like suffix appended, so it never exercised the actual gap.

Fix: 63d4770 —

  • Replaced the RegExp with a match-callback function, matchesInterExtendedSubsetFont (new web/scripts/font-cache-match.mjs, plain and dependency-free so both vite.config.ts and the test suite import the exact same function, no build step): same-origin, url.pathname starts with /assets/, ends in .woff2, and request.destination === "font".
  • verify-sw.mjs check 2 now requires the runtime route to be a match-callback function — a RegExp-typed route fails outright — and statically verifies the extracted function's source carries all four guards. Never eval'd: the checks key on substrings that survive minification (a destructured parameter's property name, e.g. sameOrigin: in {sameOrigin:l}, and member-expression/string-literal text like .pathname.startsWith("/assets/")), not the renamed local bindings.

Verification:

  • Confirmed the vulnerability was real, not theoretical: the exact d33e1f5 regex matches /api/v1/x/inter-greek-opsz-normal-deadbeef.woff2 → true, and running the old verify-sw.mjs (checked out at d33e1f5) against a fixture built from that exact regex → exit 0 (it passed; the gap was real and uncaught).
  • New guard against the same d33e1f5 regex, rebuilt via a real vite build: red — runtime caching route is a RegExp, not a match-callback function.
  • Three more mutations, each shown red then green: dropping the /assets/ prefix check, dropping the request.destination check, and the d33e1f5-regex rebuild above.
  • node --test scripts/verify-sw.test.mjs: 9/9, including a permanent negative fixture (rejects a RegExp runtime-caching route (#948 round 1 regression)) and direct behavioral tests of matchesInterExtendedSubsetFont proving the review's exact case — a font-suffixed API path rejected, the real asset path accepted, plus cross-origin and non-font-destination rejection.
  • Precache unchanged: 1849.11 KiB, 50.89 KiB under the web: set and enforce a precache bundle budget for the MUI migration #825 ceiling.

Codex gpt-6-sol round 2, P3 — docs/designs/822-mui-revamp.md:324: amendment contradicts the fix

The D7.2 amendment said no font-optical-sizing/font-variation-settings declaration existed anywhere and that opticalSize.test.ts would fail if one ever appeared — true when #835 first landed, false since round 1's P2 fix pinned font-variation-settings: "opsz" 14 at styles.css's :root. A maintainer following the old text could "fix" the doc's claimed invariant by removing that pin and reopening the 16px phone-body regression.

Fix: de08553 — added an amendment describing the root text-cut pin, the stat-figure overrides that clear it back to normal, and what opticalSize.test.ts now actually enforces (exactly one numeric pin, at the root; a pin anywhere else, or a changed root value, fails).

Verification: dotnet test tests/Cluckwork.Application.Tests --filter "FullyQualifiedName~ImagePin|FullyQualifiedName~RealTree" run before committing the markdown — 14/14 passed.

Full verification

  • npm run typecheck — clean
  • npm run test:coverage — 3482 tests; 141/142 files green, the same single load-flake as round 1 (DailyEntryPage.test.tsx's role-change-session case), unrelated to this diff
  • node --test scripts/verify-sw.test.mjs — 9/9
  • pstack:deslop — reviewed the full diff; removed one dead ternary branch in verify-sw.mjs (a route's kind is always "regex" when that check fails, so route.kind === "regex" ? "RegExp" : route.kind was unreachable in its else branch), otherwise consistent with the repo's WHY-comment convention

Isolated stack from round 1 is already torn down; this round needed no running stack (no UI/Playwright-facing change).

@mforce

mforce commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

The review loop stops here by owner decision (2026-09-24). The PR ships at de08553 as it is.

Codex gpt-6-sol round 3 at de08553 confirmed every round-2 fix. Its two findings are not fixed, by owner decision:

  • P2, verify-sw.mjs:247: the static guard over the generated worker could be satisfied by a contrived callback. This is a gap in the test scaffolding. The shipped callback itself correctly rejects /api/ paths.
  • P3, font-cache-match.mjs:22: the runtime font matcher accepts any same-origin /assets/*.woff2 font request, not only the five Inter subsets.

The owner judged more tests not worth adding for these.

@mforce
mforce merged commit a8de94e into main Sep 24, 2026
16 checks passed
@mforce
mforce deleted the feat/835-836-type-and-mark branch September 24, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web: give display text an optical size using Inter's opsz axis

1 participant