Conversation
Modernise the ar5iv theme without changing any rendered output for the existing in-tree arXiv-ID regression corpus. The starting-point audit is preserved verbatim in docs/GLOWUP_AUDIT_START.md; the decisions and surprises behind each phase are recorded in docs/GLOWUP_WISDOM.md. Structural moves - Introduce @layer order (reset, tokens, base, structure, components, math, fixes); leave existing rules un-layered (implicit highest priority, so cascade behaviour is preserved). - Extract design tokens to css/ar5iv/tokens.css. New tokens: --border-hairline / --border-double, --duration-fast / --ease-out, --z-below / --z-page / --z-popover, --focus-ring*, --surface-subtle, --text-color-author-black-dark. Theme-aware tokens now use light-dark() with color-scheme rules on :root[data-theme]. - New css/ar5iv/a11y.css: :focus-visible ring, keyboard-accessible footnote popovers (:focus-within), prefers-reduced-motion guard, :target pulse, ::selection, forced-colors mapping. - New css/ar5iv/print.css: force light, inline footnote content, external-URL annotation, break-inside discipline, @page margin. - Fix the @supports probe to actually test relative-colour syntax (oklch(from white l c h)) rather than calc-in-arguments. - Mirror image-filter / svg padding under @media (prefers-color-scheme: dark) so OS-dark users without a JS-set data-theme still get the matching behavioural overrides. Code hygiene - Replace pre-flow-root clearfix with display: flow-root. - All media queries converted to modern range syntax with a consistent < / <= / > / >= boundary discipline; 0.01rem-offset edges removed. - Replace 100vw with 100dvw for author-block absolute centring. - Wrap epigraph width / margin in min(100%, …) so they stop overflowing at narrow viewports. - Drop text-justify: inter-word at all 6 sites (default text-justify: auto is correct per script). - Remove dead vendor prefixes (-webkit-/-moz-/-ms-/-o- on transition, transform, columns). - Replace opacity: 100 (clamps to 1) with opacity: 1 throughout. - Remove dead z-index: 100 on a static element, dead box-sizing on a single class, dead align-self: normal, commented-out B2 rule. - Substitute named CSS colour `whitesmoke` with --surface-subtle token (theme-aware). - Replace `border-width: thin` keyword with the tokenised hairline. - Frame the transformed-wrappers !important cluster (~50 lines, ~6 !importants) as one feature flag with a banner comment, not six independent decisions. Distribution - Add &display=swap to the three Google Fonts @import URLs. - package.json: exports map, style field, sideEffects allowlist, files allowlist, https repository URL, author-name typo fix. Docs - docs/TOKENS.md — token reference. - CONTRIBUTING.md — layer-order, :is/:where, regression corpus discipline, what-not-to-change. - README.md refreshed with cross-links. - Cross-link rfc_latexml_custom_properties.md from the top of ar5iv.css. Deferred (catalogued in GLOWUP_WISDOM.md): spacing/type/breakpoint scales, container-query pilot, logical-property sweep, sepia / high- contrast themes, OS-preference mirror for the OKLCH author-colour inversion, stylelint config, font-face unicode-range subsetting, and upstream LaTeXML changes for focusable footnote marks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, a11y depth
CSS substance only; docs sync follows in a separate commit.
* dark-mode.css extracted as its own sub-file; implements the RFC's
--fn-{fg,bg,border,fill,stroke}-color-to-dark-mode public override
surface so downstream themes redefine transforms without forking.
* OKLCH/HSL inversion now fires under both [data-theme="dark"] and
@media (prefers-color-scheme: dark) — closes the OS-preference gap
that left os-dark users with un-inverted author colours.
* OKLCH supports branch unified: fill uses the bg scale (0.8237) and
stroke uses the fg/border scale (0.7), matching the HSL fallback's
107/100 split.
* Dark-mode WCAG AA contrast audit: --email-link-color darkcyan
(4.48:1) -> #009999 (~5.3:1); --info-text-color #01719d (3.39:1) ->
light/dark split with #3a9bcc (~5.9:1); --error-text-color #d52f36
(3.77:1) -> #e85a60 (~4.8:1).
* a11y: [hidden] defensive reset; .ltx_note_mark touch-target inflated
to ~24x24 px via transparent ::before (WCAG 2.5.8); MathML focus
rule narrowed to keep the ring on interactive descendants.
* .ltx_no_dark_filter opt-out on the global image dark-mode filter.
* a11y.css breakpoint pair aligned with ar5iv.css's < 96rem / >= 96rem
discipline (drops the residual 95.99rem split).
* 5-deep :not() chain on inline-image sizing rewritten as
:not(:is(...)) — same semantics, comment-acknowledged ugliness
resolved.
* Code-smell residue: four stale "untested" / "this is debatable"
TODOs deleted (10 substantive TODOs retained); --fo_width legacy
fallback dropped after verifying no current upstream consumer.
* Iteration-1 regression caught: display: flow-root on
.ltx_page_content had pushed the title ~32 px lower than before
by suppressing the parent-first-child margin-collapse the old
:after-only clearfix preserved. Fix: dropped margin-top: 2rem from
.ltx_document.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Docs-only follow-up to the previous commit; describes code that just landed. * rfc_latexml_custom_properties.md: corrected the worked example's selector (the prior [style*="color:"] would also match background-color: and border-color:); per-property gates added. Documented the foreground/border 0.7 scale and the HSL fallback's 107/100 asymmetry — only the background 0.8237 was justified before. * GLOWUP_WISDOM.md: appended iteration-2 findings — the flow-root margin-collapse correction, the YAGNI re-pass that deferred most of the iteration-2 audit plan, the .ltx_sr_only retraction, the --fn-* implementation note (with the var()-in-light-dark() pattern we chose not to ship), the OKLCH/HSL fill/stroke cleanup, and a final iteration-2 tally. * GLOWUP_PHASE2_AUDIT.md: new frozen snapshot of the pre-iteration-2 audit (the GLOWUP_PROGRESS.md content as it stood after three critique passes), mirroring the GLOWUP_AUDIT_START.md pattern. * GLOWUP_PROGRESS.md: rewritten as the iteration-3 punch list with a 16-row dimension checklist defining "best-in-class" concretely; the flat 10-item priority order and a hard-vs-soft dependency diagram. * .gitignore: examples/ — local working directory for fetched arXiv pages with rewritten stylesheet links (manual eyeball harness while iteration-3 item #1 is pending). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual `npm publish` for now; jsDelivr / unpkg auto-mirror once published. GH Actions auto-publish on tag deferred until the manual flow is proven. * package.json: version 0.8.4 -> 0.9.0 (matches the iteration-2 contract additions: --fn-* API is a new public surface). Add `lightningcss-cli` devDependency, `build` script, and a `prepublishOnly` hook so `npm publish` always ships a fresh bundle. Add `dist` to `files` and a new `./min` export. * `npm run build` -> dist/ar5iv.min.css (~46 KB, ~37 % smaller than the unminified source) + sourcemap. lightningcss inlines the four local @imports (tokens, a11y, dark-mode, print) into a single file; the @imports in ar5iv-fonts.css (remote Google Fonts URLs) are intentionally not inlined. * .gitignore: dist/ (built locally, shipped via npm-publish only), node_modules/. * README.md rewritten: - CDN recipe with jsDelivr / unpkg URL patterns - self-hosted-from-checkout recipe - local-build recipe - maintainer release flow (git tag + npm publish) * package-lock.json committed for reproducible dev installs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Status table flips 8❌/2⚠️ /6✅ → 7❌/2⚠️ /7✅. Item #7 stays in the priority list with a strikethrough/landed note so the numbering through #8/#9/#10 (and the `#4 → #8` dependency cross-reference) stays stable as more items land. Change-log entry added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item #5 landed. * css/ar5iv.css: wrap the bulk of rules in @layer components, plus the B1/B3 known-bug patches at the bottom in @layer fixes. The transformed-wrappers feature flag stays un-layered so its !important rules keep maximum priority over LaTeXML's inline style="transform:…" declarations (per the cross-layer !important inversion: un-layered !important beats every layered !important). * `reset`, `structure`, `math` declared but empty — reserved as override slots for downstream themes that want to slot rules between ours. * Build verification: lightningcss bundles correctly; 11/11 .ltx_transformed_outer references end up un-layered in the minified output; B1 selector confirmed inside @layer fixes. * CONTRIBUTING.md updated with the new layer placements and an explicit note on what stays un-layered. * GLOWUP_PROGRESS.md: status table 7❌/2⚠️ /7✅ → 7❌/1⚠️ /8✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 backlog is unchanged in scope; doc gains operability. * New "How to pick this up" section at the top: standing workflow for each item (build verify -> open demos -> change -> build/eye verify -> status-table + change-log update -> commit), plus the YAGNI / !important-discipline / visual-changes-must-be-intentional house rules. * Each unfinished item (#1 #2 #3 #4 #6 #8 #9 #10) gets a "Next move" paragraph: tool choices, concrete first action, conventions and pitfalls. No item now requires re-discovery from the frozen GLOWUP_PHASE2_AUDIT archive — that doc stays as deeper rationale but isn't load-bearing for tomorrow's first commit. * Doc grew 250 -> 413 lines from the additions. Will shrink as the remaining items land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A third phantom-margin finding in the same family as the iteration-1
flow-root regression. Iter-2 caught one (.ltx_document margin-top:
2rem), tonight catches the next level deeper.
.ltx_title.ltx_title_document had `margin-top: 1rem`, which
pre-iter-1 collapsed outward and was absorbed by .ltx_page_content's
4rem outer margin. Post-flow-root, the BFC blocks that collapse,
adding ~1rem visible drift.
First instinct was to delete the declaration. That made it worse:
Chrome's UA `h1 { margin-block-start: 0.83em }` re-asserts when the
author rule disappears, and at 1.7rem font-size that's ~1.4rem —
more than the 1rem the author rule had been masking. Correct fix
is `margin-top: 0` explicitly, not deletion.
Wisdom doc updated with both lessons:
- Audit every margin on the first-in-flow-descendant chain when
introducing a BFC, not just the parent's first child.
- Deleting a CSS declaration is not equivalent to setting it to
"natural" zero — for elements with non-zero UA styling, you
have to write zero.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #10. Recipes cover the override patterns authors actually need: re-decorate a single palette token; change the dark-mode inversion strategy (no-inversion, stronger, or via color-mix); add a third data-theme value (sepia + high-contrast walk-throughs); ship a downstream npm package with @layer ordering. Pitfalls section documents the light-dark(var(),var()) caveat, the color-scheme requirement per data-theme, @layer first-appearance ordering, and the !important inversion rule. Path-(b) of iteration-3 item #9 (demonstrated extensibility) is now covered by the cookbook — the recipes are the worked example. Path-(a) (a first-party alt theme) stays available if a user need surfaces. Status table: 7 ❌ / 1⚠️ / 8 ✅ → 6 ❌ / 1⚠️ / 9 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #3. ~60 sites in ar5iv.css converted from `margin-left/right`, `padding-left/right`, `left:`, `border-left/right`, `text-align: left/right`, and `float: left/right` to their logical equivalents — so a `<html dir="rtl">` document mirrors correctly without requiring a separate stylesheet. Stays-physical decisions (with a clarifying comment in the borders section): LaTeXML-internal `.ltx_border_l/r/ll/rr/L/R/r_dashed`, `.ltx_framed_left/right/leftright`, `.ltx_nopad_l/r`, and `.ltx_align_left/right` keep `left/right` because LaTeXML emits physical-side semantics in its class names. Intrinsically-LTR content streams (listing line-number gutter, SVG `<text>`, verbatim, conversion-report, math-overflow alignment, `.ltx_INFO/WARNING/ERROR/FATAL`) also keep physical alignment. The `.ltx_eqn_eqno.ltx_align_right .ltx_tag { float: right }` rule stays physical because its selector is gated on the physical `.ltx_align_right` class — author intent, not flow direction. Float on `.ltx_note_outer` and `.ltx_bibliography dt` / `.ltx_description dt` converted to `float: inline-end` / `float: inline-start`. These are baseline-since-mid-2024 (Chrome 118, Firefox 121, Safari 17.5). On older browsers the float degrades to static positioning — acceptable graceful degradation. Synthetic RTL demo at `examples/ar5iv-1910.06709-rtl.html` (gitignored) for local verification. Status table: 6 ❌ / 1⚠️ / 9 ✅ → 5 ❌ / 1⚠️ / 10 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #4 (partial — font-size scale deferred). Spacing anchors: `--space-xs/sm/md/lg/xl` map to 0.5/1/1.5/2/4 rem, the five values that the audit histogram showed accounting for ~70 % of margin declarations. Property-agnostic — same anchors intended for padding/gap/inset when the value matches. Long-tail literals (0.1, 0.2, 0.25, 0.3, 0.66, 0.75 rem) stay raw because each was hand-tuned for a specific typographic context; snapping them retroactively to a scale step would erase intent. Line-height: `--line-height-prose: 1.5rem` — the dominant value (7 consumers) at the body's 1rem font-size. Other line-height values (`1rem`, `1.1rem`, `1.15rem`, `1.4rem`) stay literal — each is a one-off hand-tune, not a scale step. Font-size: deferred. Histogram is mostly singletons (12 distinct values for ~24 sites). Defining anchor tokens would either invent anchors or freeze hand-tuned values into a fake ladder. Re-evaluate after a second sweep. Migration: 45 margin sites + 7 line-height sites converted in this commit. TOKENS.md updated with the new tokens and three stale colour values brought current (`--email-link-color`, `--info-text-color`, `--error-text-color` — iteration-2 had bumped these for contrast but TOKENS.md still listed the old values; computed contrast ratios on light/dark backgrounds now spelled out in the table). Status table: 5 ❌ / 1⚠️ / 10 ✅ → 4 ❌ / 2⚠️ / 10 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #8. `npm run lint` extends `stylelint-config-standard` with allowlists for the LaTeXML-driven naming conventions and the codebase's known `!important` clusters (transformed-wrappers, per-rule inline-style defeats, `[hidden]` reset, print stack). 45 surviving warnings are all `!important` in those allowlisted clusters — kept advisory rather than promoted to errors. First run also surfaced 10 hard errors, all real cleanups: - four deprecated `word-wrap: …` → `overflow-wrap: …` substitutions (modern spec name; same behaviour) - one deprecated `word-break: break-word` keyword (the legacy alias for `overflow-wrap: break-word`) - four single-colon `:before` / `:after` pseudo-elements promoted to the level-3 double-colon notation - one dead rule: `span.ltx_personname span:first { font-size: … }` — `:first` is not a valid pseudo-class outside `@page`, so this has been silently inert for the lifetime of the file. Removed rather than guessing at the intended pseudo-class. Bare-rem rule deferred. Now that #4 has tokenised the spacing scale, a custom plugin could enforce "prefer `--space-*` over bare rem", but it's ~80 LoC of churn-risk and the migration is fresh — defer until a second offender ships. Status table: 4 ❌ / 2⚠️ / 10 ✅ → 3 ❌ / 2⚠️ / 11 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item #2 partial — these are the bugs a code audit can find; a full DevTools walk for content-driven overflow (long URLs in footnotes, wide formulas, TikZ figures with big embedded text) is still pending. The fixes: - Author-block fly-out (.ltx_role_address/.ltx_role_affiliation, position: absolute) used `width: var(--main-width)` plus an unclamped centering offset. On a 20rem viewport the formula `calc((100dvw - 52rem) / 2)` evaluates to -16rem, pushing the block off-canvas. Now `width: min(--main-width, 100dvw)` and `inset-inline-start: max(0px, …)`. - Conversion-report panel forced a literal `width: var(--main-width)`, overflowing any viewport narrower than 52rem. Switched to `max-width` + `width: 100%` + `box-sizing: border-box`. - Title-page image cap was `max-width: 30rem`, which at 320 CSS-px viewport still overflowed. Now `max-width: min(30rem, 100%)`. - Footnote hover popovers — both the small-screen variant (gated by `(width < 96rem)`) and the wide-screen table/figcaption variant — declared `width: 20rem` plus `padding: 2rem`. Under default `content-box` that's a 24rem visual box inside a 20rem viewport. Added `box-sizing: border-box` so the padding eats into the declared width instead of adding to it. - Epigraph fly-out math (blockquote.ltx_epigraph) used `min(100%, …)` for width and `min(50%, …)` for margin-inline-start — at 320 CSS-px those summed to 27rem in an 18rem column. Now both use the same 50/45 ratio with the same rem cap, so geometry stays inside the column at any width while preserving the wide-viewport intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hypothesis was: in a narrow iframe embedding inside a wide host viewport, the sidenote ladder media query (@media (width >= 96rem)) might pick the wrong band. Synthetic test at examples/embedded-iframe.html (gitignored) — a 600 CSS-px iframe inside a maximised browser host — confirms the opposite: the media query evaluates against the iframe's own viewport when the iframe declares <meta name="viewport" content="width=device-width">. Every LaTeXML HTML output declares that. So the predicted gap doesn't exist. Container queries would still be useful in scenarios that don't exist today (direct embedding without iframe isolation; side-by-side dual-article reader). Per YAGNI, defer until a real consumer surfaces. Test page retained for future re-verification. Status table: 3 ❌ / 2⚠️ / 11 ✅ → 2 ❌ / 2⚠️ / 12 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #9, both paths. Path (a) — first-party sepia variant. `:root[data-theme="sepia"]` declared in css/ar5iv/tokens.css with a cream/brown palette (background #f4ecd8, text #5b4636 ≈ 7.4:1, link #6b4423 ≈ 7.3:1) and `color-scheme: only light`. The `--fn-*` application rules don't fire for sepia because their gate is `[data-theme="dark"]` or `:root:not([data-theme="light"])` inside `prefers-color-scheme: dark` — neither matches `data-theme="sepia"`, so author-supplied colours render as-is on the sepia background. Validates the palette-override pathway on every build. Path (b) — cross-reference the production arxiv-browse vendor theme as the real-world consumer. The cross-check surfaced one real cookbook gap: the upstream `--text-color-author-black-dark` token (added in iteration 2) was the exact answer to a TODO comment in the downstream's stylesheet about wanting a dedicated token for the dark-mode `\color{black}` rescue case. The cookbook now calls this out in recipe 2 with a one-line override example. Also added a new "Extending the token surface" section between recipes 3 and 4 covering net-new downstream tokens for chrome elements (headers, footers, nav, modal forms). The arxiv-browse theme exercises this pattern heavily; the cookbook had only implied it. Also disabled `custom-property-empty-line-before` in stylelintrc (the rule fires inside selector bodies that mix `color-scheme` with custom-property declarations — true for the new sepia block and not worth a layout split). Status table: 2 ❌ / 2⚠️ / 12 ✅ → 1 ❌ / 2⚠️ / 13 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #1. `tools/visual.mjs` renders the three corpus demos in `examples/` (gitignored — populated via `examples/fetch.sh`) at 1280 CSS-px × {light, dark} using Playwright's Chromium, diffs against the committed PNG baseline in `tools/baseline/` using pixelmatch (per-pixel YIQ threshold 0.1, per-image pixel-count tolerance 400). `npm test` runs the diff; `node tools/visual.mjs --update` refreshes baseline after an intentional change. The retrospective evidence for this harness (the iteration-2 flow-root title shift, the post-flow-root UA-default margin re-assertion, both bugs that survived multiple critique passes before someone noticed) all manifests in the first viewport, so the script renders first-viewport-only rather than fullPage. That keeps the committed baseline at 1.4 MB total (vs 25 MB for the fullPage variant — the 2407.16893 paper alone was 8 MB). One-line change in the script flips to `fullPage: true` and the baseline migrates to an out-of-tree tar.zst blob if a class of below-the-fold regression starts slipping through. One-viewport coverage (1280) was a similar judgement call: the structural breakpoints sit at 52rem and 96rem media queries, both visible from a mid-range viewport. Adding 320/768/1920 quadruples the baseline size for marginal coverage. Reflow- specific regressions at the narrow edge are covered separately by iteration-3 item #2 (the structural audit) and would benefit from a dedicated narrow-viewport harness rather than fork the visual-regression budget. With this commit every iteration-2 regression class has a mechanical guard: stylelint catches syntax/idiom drift, visual.mjs catches geometric changes. Status table: 1 ❌ / 2⚠️ / 13 ✅ → 0 ❌ / 2⚠️ / 14 ✅. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A critique pass on the eight iteration-3 commits surfaced these: 1. Cookbook recipe 1 used `light-dark(#literal, var(--same-name))` to "preserve the dark branch" — that's a CSS cyclic dependency (Custom Properties L2 §5.1), the property becomes `unset`. The recipe didn't work as written. Rewritten to either inline both branches (look up the dark value in TOKENS.md) or override per-`data-theme` and per `@media (prefers-color-scheme: light)` separately. Added a "Why you can't var()-reference the original" explainer. 2. Cookbook recipe 2(b) described `oklch(from … calc(1 - 0.85 * l) …)` as "more contrast on near-black inputs". Wrong direction: near-black inputs map to near-white outputs at any scale. The actual effect is at mid-tone and near-white author inputs, where the larger scale spreads them across a wider output range — at the cost of mid-tone outputs landing closer to black, potentially below the contrast floor on a dark background. Renamed to "more dramatic inversion" with the trade-off spelled out. 3. Sepia contrast ratios in tokens.css were committed as ~7.4:1 and ~7.3:1 without computing. Recomputed via the WCAG 2.x relative-luminance formula: 7.48 : 1 (text) and 7.19 : 1 (link). Updated to the precise values with a brief note on the formula used. 4. GLOWUP_WISDOM.md augmented with six iteration-3 entries: stylelint surfacing a 6-year-dead `:first` rule; the arxiv-browse cross-reference closing a real token gap; "falsified by hypothesis test" framing for the container-query close; visual-harness scope as ship-working-iterate-later; the `light-dark(literal, var(--same))` cyclic-dependency mistake; the logical-property "stays-physical" judgment call on LaTeXML-internal classes; and the epigraph fix's moderate-viewport side-effect. 5. The iteration-2 wisdom entry on `--fo_width` reframed: the *hyphen-named* `--ltx-fo-width` is the LaTeXML contract (emitted from `lib/LaTeXML/Engine/TeX_Box.pool.ltxml:385`); the *underscore-named* `--fo_width` was a legacy fallback with no current producer. The iter-2 commit dropped the right one; the wisdom-doc framing as "undefined fallback is a real mystery" was slightly off. 6. CONTRIBUTING.md gained a Provenance section (ar5iv-css forked from LaTeXML.css; the `--ltx-*` and class-name surface is an upstream contract) and a "how to inspect a failed npm test" paragraph pointing at the three artefacts (fresh snapshot, diff PNG, baseline). 7. Container-query close on #6 reframed from "hypothesis falsified" (claims-empirically-disproved) to "spec-based reasoning plus a falsifiable artifact" (the test page exists and would catch a spec change). Closer to what actually happened. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 320 × {light, dark} to the three-demo corpus, six new
baselines totalling ~210 KB. Baseline grew 1.4 MB → 1.6 MB.
The 320 viewport mechanically closes part of iteration-3 #2's
pending in-browser reflow walk — any future structural overflow
regression at the narrow edge now fails `npm test`. The five
reflow fixes already landed in `7e3fc18` (author-block clamp,
conversion-report max-width, title-page image clamp, footnote
popover box-sizing, epigraph width/margin ratio) are now
captured in the baseline; their failure modes would all show up
as horizontal-scroll-induced page-width changes that pixelmatch
catches trivially.
What's still in the ⚠️ partial state: the 400% zoom walk at
viewport 1280, and content-driven overflow that wraps fine at
baseline but might not after edits (long URLs, wide formulas).
Those are now small-scope tasks well-suited to a contributor
walk-through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous first-viewport-only scope was correctly called out as
misleading — it captured only the frontmatter (title, authors,
abstract, maybe first paragraph), missing everything below the
fold in every paper. A regression in the bibliography rules or in
a mid-paper figure had zero coverage. The earlier wisdom-doc
justification ("retrospective bugs all manifested in the first
viewport, so first-viewport is enough") was selection bias: we
caught those bugs *because* they were in the part of the page we
were looking at; bugs below the fold survive unnoticed, which is
exactly the class a harness exists to surface.
Changes:
- `tools/visual.mjs` now renders `fullPage: true` over every
arXiv ID cited in `ar5iv.css` comments (47 papers — list
duplicated in `examples/fetch-corpus.sh` for the bulk fetch).
- `examples/fetch-corpus.sh` (new): bulk-fetches the corpus via
the existing `fetch.sh`, skips already-present, reports
failures (one paper, 2105.10386, currently 503/404 across
both sources).
- 1280 × {light, dark} only; 320 dropped from the matrix because
fullPage at narrow viewports doubles disk cost without
proportional regression coverage. WCAG-1.4.10 narrow-viewport
reflow checks live under item #2 (a separate manual audit).
- Baselines moved out of git history. fullPage × 46 papers × 2
themes is ~440 MB locally — too large to commit. Each developer
generates with `--update`; CI's shared-truth strategy is a
release-artifact tarball at `tools/snapshots-baseline.tar.zst`
(deferred until a CI pipeline lands). Removed the six PNGs
that were previously committed.
- `.gitignore` now uses `examples/*` instead of `examples/`,
with `!examples/fetch.sh` and `!examples/fetch-corpus.sh`
exceptions — `examples/<dir>` ignores the directory in a way
that prevents !exceptions from working at all; `examples/*`
ignores the contents but leaves the directory itself
unignored, so the !exceptions resolve correctly. Bonus: both
fetch driver scripts are now tracked in the repo (previously
they were gitignored alongside the demos they fetched, which
was a real onboarding hazard).
Render times: --update ~3 min, npm test ~5 min for the full
corpus (Playwright fullPage screenshots dominate). Both verify
clean: 92 snapshot(s) OK across 46 papers (the missing one
gracefully skipped).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 47-paper list was duplicated as a bash array in examples/fetch-corpus.sh and a JS array in tools/visual.mjs. Drift hazard — adding a paper in one place wouldn't pick it up in the other. Both scripts now read from tools/corpus.txt (one ID per line, shell-style # comments and blank lines ignored). To add a paper to the regression corpus: append a line. Both fetch and render pick it up automatically on the next run. Sync-with-source-of-source instructions (the comments in ar5iv.css) remain documented at the top of corpus.txt for the case when ar5iv.css grows new cited IDs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User confirmed no embedding plans for ar5iv-rendered papers (no direct CMS-sidebar embedding, no side-by-side dual-article reader). The earlier "deferred per YAGNI" close of iteration-3 #6 is therefore durable rather than speculative-revisit — there is no future consumer the close is waiting on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ext-decoration Per the iteration-3 critique-pass action on item Q3: the arxiv-browse downstream theme replaces ar5iv-css's `border-bottom: var(--border-hairline) dotted` with the standard `text-decoration: underline dotted` idiom. The downstream pattern is a clear upstream win: - `text-decoration` is the semantically correct primitive for link underlines. CSS layout primitives like `border-bottom` shouldn't carry text-decoration meaning. - `text-decoration-skip-ink: auto` (default in current browsers) causes the underline to skip text descenders. The old `border-bottom` approach drew straight across descenders, visibly clipping g, p, y, q, j. - `text-decoration-thickness: from-font` matches the font's intrinsic underline-thickness metric — better typographic fit than a hard-coded `--border-hairline` (0.063rem) across all fonts. Old behaviour was acceptable on Noto Serif but no longer self-tuning for a downstream font swap. - Removes the awkward `border-bottom: none` undo-sites and the cascade subtlety where ".ltx_graphics inside an .ltx_ref get an unwanted underline through them" was always implicit. Sites changed: - `.ltx_page_logo > a` — eLife-like link in the page-logo line - `.ltx_ref` — the catch-all link - `.ltx_ref > .ltx_graphics` — `inline` → `inline-block` so the underline doesn't leak into inline icons (ORCID etc.; retested 1707.04393, no regression) - Four `.ltx_tocentry_*` rules — three `border-bottom: none` undo sites become `text-decoration: none`, one positive `.ltx_tag_ref` site becomes `text-decoration: underline dotted` Harness behaviour: SIZE-mismatch on 80 of 92 snapshots (page heights shrank by 16-30 px each — the old `border-bottom` contributed a hairline to every link's inline-box, slightly inflating line-heights; the new `text-decoration` draws within existing bounds and recovers that height). The 12 papers without `.ltx_ref` instances show only the residual underline-position drift (45-54 pixels of diff each, well below the 400-pixel tolerance). Baseline regenerated; 12-snapshot re-run confirms clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Project owner named this category during iteration 3: a class of bugs where source content was originally typeset for A4 or Letter paper and the fixed-width-print assumptions break under elastic screen geometry. Examples: code listings overflowing on phone viewports, oversized figures crowding a narrow column, horizontally-scrolling tables. The category clarifies the design space — reflow (fix on mobile, break formatting), contain overflow (preserve format, add per-element scrollbar), or constrain at source (re-emission). Each axis has a different cost; the right answer varies by content type and its load-bearing properties (a listing's line-number column is load-bearing in a way prose isn't). Includes an anti-lesson on `overflow-x: auto` as a tactic — it's a one-line fix that frequently produces unwanted in-content scrollbars at desktop viewports (where the content happens to be slightly wider than `--main-width`). Always cross-browser visual-verify before landing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Algorithm listings inside `<figure class="ltx_figure">` inherit `text-align: center` from the figure rule (line 1780). Without an explicit override, that centring cascades down through `.ltx_listing` → `.ltx_listingline` (display: block) → each line's inline content. The visible result is a middle-aligned ragged-edge listing — line numbers, indentation columns, and the leftmost character of each line all sit at different x positions row to row. Pin to `text-align: left` on `.ltx_listing`. Source code is intrinsically LTR (line numbers, monospace columns, indentation all assume left-to-right flow) so physical `left` is the right semantic — `start` would change behaviour in a future RTL document context, which is wrong for code. Caught by the iteration-3 visual-regression harness in fullPage mode (per the iteration-3 critique pass: first-viewport-only would have missed every algorithm listing in the corpus, including the screenshot example the project owner shared from arxiv:1712.01103). Six papers in the corpus carry algorithm listings; pixel diff ranged 25k–205k against the previous mis-centred baseline. Manual cross-check in Chrome confirmed the new layout matches the project owner's reference rendering. The earlier-staged `overflow-x: auto` change to the same rule was a tangent to this fix; rolled back pending separate visual verification per the impedance-mismatch wisdom entry's anti-lesson. Listings that overflow narrow viewports still push horizontal scroll to the page for now — that's the standing behaviour, not a regression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration sequence after the iteration-3 #9 follow-up identified this as a real upstream win from the arxiv-browse stack. Four visual rounds converged on the right shape: 1. Tried `width: 100%; box-sizing: border-box` on `.ltx_bibblock` to fill the remaining cell width. Worked for long keys; left a wide gap for numeric keys because `.ltx_tag` was locked at `width: 14%`. 2. Tried `width: max-content` on the tag column under `table-layout: fixed`. Browsers ignored or mis-interpreted the value; gap got wider, not narrower. 3. Tried `table-layout: auto` so the tag column would size to its actual key width. Worked per-bibitem but produced a *jagged* tag column across the bibliography — `display: table` is per-row, and table-auto distributed leftover width proportionally to max-content, giving short-content rows a wider tag column than long-content rows. 4. Refactored to a parent CSS grid with subgrid per bibitem. `.ltx_biblist` defines two tracks: `max-content` for the tag column, `minmax(0, 1fr)` for the entry content. Each bibitem participates via `grid-template-columns: subgrid` and `grid-column: 1 / -1`, so the *widest tag in the whole list* sets the column width and every entry lines up to it. A bibitem has one `.ltx_tag` and *one or more* `.ltx_bibblock` children (LaTeXML emits one bibblock per logical entry line — title, authors, journal, etc.). Explicit placement: tag at `grid-column: 1; grid-row: 1`; every bibblock at `grid-column: 2`, auto-rows so they stack vertically. Earlier subgrid attempt without explicit placement let bibblocks 2+ flow back into the tag column — "completely broken" per the user. Also two cosmetic adjustments confirmed visually: - `padding-inline-start: 0` on `.ltx_biblist` to drop the <ul> UA-default 40px so `[1]` aligns under the "R" of "References" rather than indenting. - `column-gap: 3em` for breathing room between the key and the entry text. Removed the `inset-inline-start: -2.5rem` hanging-tag offset that no longer makes sense under grid placement; tags now sit flush at the column-1 edge, matching the user's reference screenshot. Subgrid is Baseline 2024 (Chrome 117, Firefox 71, Safari 16) — narrower than the project's other Baseline-since-2023 deps (OKLCH relative, light-dark()) but the visual fallback on older browsers is gracefully degraded (the bibitem just renders as its own grid). Narrow viewport (<=52rem) falls back to a single-column stack via `grid-column: auto; grid-row: auto` resets, so the tag-then-blocks order falls out from source-order placement. Visual harness: 92 snapshots regenerated. Manual cross-checks on 2109.04981 (numeric keys) and 1708.02728 (BFR+13 author-year keys) confirmed uniform alignment in both cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item Q4 answered. The rule pinned `.ltx_p` and
`.ltx_item` containing math with 7+ mrow descendants to
`text-align: left`, working around a 2023-era browser behaviour
where justification around a wide unbreakable formula produced
visibly large whitespace wells.
Obsolescence verified with the canonical trigger paper
(arXiv:2105.10386 "Analysis of Boolean Functions") loaded into
the corpus from a local cache (the paper is currently 503/404
upstream). With the rule disabled, the harness shows 0 pixels of
diff across the entire 47-paper corpus at 1280 × {light, dark} ×
fullPage — including 2105.10386 itself, which exercises the
selector. Current Chromium handles wide-formula justification
cleanly without the override.
Cross-engine verification (Firefox, Safari) deferred — Playwright
supports them via `firefox.launch()` / `webkit.launch()`, but the
harness today is Chromium-only. Will revisit when multi-engine
support lands.
Two wisdom entries added:
- "Defensive rules earn their keep by failing the test, not by
surviving fear" — the test that retires a defensive rule isn't
"does it still trigger" but "does removing it reintroduce the
failure". The conservative bias toward keeping defensive rules
is reasonable when the test is infeasible; "infeasible" deserves
a hard look.
- Practical mechanism note: dropping a local LaTeXML output into
`examples/ar5iv-<id>.html` substitutes for an unreachable
upstream fetch — useful for testing against canonical trigger
papers that have rotted out of public mirrors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verdict on best-in-class question: substantively yes for the CSS capabilities a scholarly theme is expected to cover. Every row of the 16-row dimension checklist has at least a strong answer; 14 ✅, 2⚠️ , 0 ❌. The two⚠️ rows have well-defined remaining work that doesn't block shipping. Remaining open work, captured as iteration-4, is engineering polish + real-world validation, not capability gaps: 1. Cross-engine harness (Firefox + WebKit via Playwright) 2. Parallelize harness rendering (5min → ~75s) 3. CI pipeline + shared baseline tarball 4. Triage `black-on-black-list.md` (15 user-reported issues) 5. Two-column corpus expansion (waiting on user-supplied IDs) 6. 400 % zoom DevTools walk for content-driven reflow 7. Audit other dated workarounds for obsolescence (same shape as the Q4 MathML retirement that just landed) No hard dependencies between items. Picks by appetite: #4 for direct user impact; #1 + #3 for confidence; #2 for inner loop; #7 for deletion-as-refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same disable-and-diff pattern that retired the Q4 MathML rule (5e0521c). Each candidate was disabled, the harness run across the 47-paper corpus, and the rule restored or deleted based on whether any paper showed visible diff. Removed (0 pixels of diff across corpus): - `.ltx_quote .ltx_break + .ltx_break + .ltx_break { display: none }` Original "paper over" for arXiv:1910.06706 where a quote contained a child <p> with many trailing <br>s. The current LaTeXML rendering of that paper has zero `<br/>` tags — the defensive rule has no consumer. - `.ltx_TOC > h6` selector from the title-styled list. When LaTeXML emits `<h6>` inside `.ltx_TOC` it carries `class="ltx_title ltx_title_abstract"`, which already matches the `.ltx_title_abstract` selector in the same rule list. The h6 selector was a redundant alias. Confirmed load-bearing (visible diff under the test): - `.ltx_cite .ltx_font_italic { font-style: normal }` — 4 corpus papers diff substantially when removed (italic spans inside cites still emit). Comment updated to reflect *why* the rule exists rather than the unhelpful "paper over" framing. - `.ltx_para > .ltx_break:first-child:last-child { display: none }` — 4 corpus papers grow by 48px each (one extra blank line) when removed. Bare-break paragraphs are still in LaTeXML's emission. Comment updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-4 item #2. Drains a flat task queue (47 papers × 2 themes = 94 tasks) with N concurrent workers sharing the single Chromium browser instance via separate Playwright contexts. Default concurrency 4, override via `CONCURRENCY=N npm test`. Wall time on this machine: ~5:30 serial → 4:13 at 4-way concurrency (~25 % improvement). Smaller win than the 4x naive expectation — Playwright contexts are competing on screenshot encoding rather than render-bound, so the bottleneck shifts to PNG serialization once a few renders are in flight. Higher concurrency (CONCURRENCY=8) might gain marginally on 8+ core machines; 4 is the safe default. Also a stale corpus.txt dedup: an earlier accidental append duplicated 2105.10386 and was deduplicated via awk, which collapsed some empty `#`-comment separators in the same pass. Cosmetic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ll-row hover Three refinements to the iteration-3 subgrid bibliography (3513fb0) driven by per-screenshot project-owner feedback during iteration-4. 1. Tag column cap. `grid-template-columns: max-content` let very long author-year keys (e.g. "Chandler and Kerswell [2013]") push the column wide enough to leave very little room for the entry. Switched to `fit-content(12em)` — short keys (numeric [1], short author-year [Wil16]) get their natural width; keys longer than ~12em (about 20 body-text chars) wrap inside the cap rather than starving the entry column. Removed the `white-space: nowrap` on the tag for the wide-viewport rule — the cap means we *want* long keys to wrap. Replaced with `overflow-wrap: break-word`. 2. Baseline alignment. `align-items: baseline` on the bibitem grid container so the tag's "[1]" baseline lines up with the bibblock's first-line baseline. Default `align-items: stretch` was making the bibblock content render a hair below the tag's visual top — barely perceptible but the project owner spotted it. 3. Whole-bibitem hover highlight. The previous rule `.ltx_tag_bibitem ~ *:hover` only highlighted siblings of the tag — hovering the bibblock left the tag unhighlighted, and vice versa. `.ltx_bibitem:is(:hover, :active) > *` propagates the highlight to both children when hovering anywhere in the bibitem. Baseline regenerated 2026-05-14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After `sudo apt-get install libavif16`, `playwright.webkit` runs through the existing `--engine=webkit` path with zero harness changes. A partial WebKit baseline (5491 PNGs across 30 papers, ~67 % of full) was generated before host memory pressure cut the unbounded run short. The primary-task validation rationale for landing item #2 — the Chromium-only deletion of the 7-mrow MathML `text-align: left` workaround (commit 5e0521c) — was rechecked on arXiv:1502.04633 chunks p003 and p008 (dense MathML, displayed equations, observation environments). Both Chromium and WebKit justify the surrounding paragraphs cleanly; the original "large whitespace wells" the workaround was designed to prevent do not reappear in WebKit. The deletion stands cross-engine. Doc touch-ups: - CONTRIBUTING.md: Firefox screenshot-height-limit caveat replaced by an engine-agnostic note pointing at the paginated path that landed yesterday (a9d9177). - GLOWUP_PROGRESS.md item #2 marked landed; pickup banner refreshed with finish-the-baseline-with-guards as the recommended next pick. - tools/visual.mjs: trivial comment cleanup ("TBD when libavif16 ships" no longer accurate). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Today's guarded WebKit-baseline retry hung at 0 % CPU on the first missing paper (2106.15835) for 7+ minutes before being killed. Root cause: the HTML's `<meta property="og:image">` and `<meta name="twitter:image">` reference a remote URL; WebKit's `waitUntil: 'networkidle'` blocks on it under headless-no-network, while Chromium handles social-card image fetching differently. Resuming the bare run would hang again. Pickup banner now carries a six-step recovery plan: 1. Block external URLs via `context.route()` (root cause) 2. Switch `waitUntil` from 'networkidle' to 'load' (defensive) 3. Add a `timeout: 15000` to `page.goto` (safety net) 4. Spot-check one short paper first; treat any pixel-diffs the same as the 2026-05-15 AA-drift playbook 5. Then re-baseline the 17 missing WebKit papers under guards 6. Acceptance: full 47-paper WebKit coverage with no PASS-fails No code change committed — the fix is the next session's first work item. Carries forward the system-instability caveat (ulimit + timeout + CONCURRENCY=1 stay required). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small changes to `tools/visual.mjs` `renderAndDiff`:
1. Add `context.route('**/*', ...)` that aborts ONLY remote image
requests. Originally the WebKit run hung on a remote
`<meta property="og:image">` fetch under `waitUntil:
'networkidle'`. A blanket abort (any non-`file:` URL) was the
first attempt, but spot-check on 0708.2787 surfaced an 85 px
page-height shift — the archived HTML references
`https://ar5iv.labs.arxiv.org/assets/ar5iv-site.0.2.2.css`, a
third stylesheet that does affect layout. The surgical
`resourceType() === 'image'` filter blocks the OG/twitter
image fetches (the actual hang trigger) while letting
stylesheets, scripts, fonts, and document fetches through.
2. `waitUntil: 'networkidle'` → `'load'`. The page's own load
event is enough for static archived HTML; networkidle waited
on the trickle of side-channel requests we now mostly avoid.
3. `timeout: 15000` on the `page.goto` call. Safety net so any
future similar hang fails fast and surfaces as ERROR instead
of stalling the whole worker.
Verified:
- WebKit on arXiv:0708.2787 produces 0 px diff against the
existing baseline (image block is layout-neutral for WebKit).
- Chromium on arXiv:0708.2787 shows the same same-machine AA
drift signature as the pre-change bisect — environmental, not
code-caused.
- WebKit on the previously-hanging arXiv:2106.15835 now
completes cleanly in ~3 min (172 snapshots produced).
- The 17 missing-WebKit papers re-baselined under guards
(CONCURRENCY=1 timeout 1500): 4557 snapshots OK, exit 0.
`tools/baseline/webkit/` now covers all 47 papers. Closes
iteration-5 item #2 fully; banner refreshed; iteration-5 item #1
(CI pipeline + release-artifact baseline tarball) becomes the
next pick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-engine audit pass on the iteration-3 #7 paper-over candidates, now that WebKit is wired up alongside Chromium. Substantive finding: `.ltx_cite > .ltx_ref { display: inline-block; line-height: 1.4rem }` (line 246) is *still* load-bearing in 2026. Disable-and-diff on arXiv:2006.09882 (the paper's Introduction `[10, 24, 44]` cite cluster, named in the rule's original comment) shows: - Chromium: 2544 px diff at chunk p012 - WebKit: 8753 px diff at chunk p004, downstream reflow cascade Removed the open-question wording ("Maybe a better approach exists?") and replaced it with a date-stamped verification record anchored to the specific corpus paper and diff magnitudes. Untestable candidates: `.ltx_item > .ltx_theorem` (line 1097) — no `.ltx_item` element with `.ltx_theorem` direct child exists in the 47-paper corpus. Selector never matches. Preserved defensively. Lines 1818 / 2090 / 2093 — these defend against inaccurate LaTeXML output (width estimations, transform: scale()). Disable-and-diff can't verify them: the existing baselines also use these overrides, so the test would tautologically pass. Real verification needs ground-truth (the source PDF) which the harness lacks. Documented as out of scope. Net: one declarative update in CSS, zero deletions. Audit produced confidence, not code reduction. The cite-ref documentation now anchors to the cross-engine evidence (matching the 5e0521c Q4 methodology) so the question doesn't need to be re-asked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `.github/workflows/ci.yml` covering the regressions that don't
need browsers:
- stylelint against `stylelint-config-standard` with project
overrides
- TOKENS.md ↔ tokens.css drift via `tools/check-tokens.mjs`
- lightningcss bundle build (catches brace mismatch and broken
@-rules)
Job runs on `ubuntu-latest`, Node 22, with npm caching. Built
`dist/` uploaded as a 14-day artefact. Concurrent PR runs cancel
in-progress.
Phase 2 — running the visual-regression harness on CI with shared
baselines — deferred until two questions are answered: where to
host the release-artifact tarball (GitHub Releases attachment
vs. equivalent), and how to handle AA drift (same-machine surfaces
24-85 px page-height variance between `--update` calls; across CI
runners the noise floor will be larger and either pin OS/fonts or
accept-and-document is needed). Documented in the iteration-5 #1
entry so the question doesn't get re-asked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight commits today on `glowup`; iteration-5 items #3, #2, and #1-phase-1 all closed; paper-over audit done. Banner now lists all eight commits, points at the two open questions for #1 phase 2 (hosting + AA-drift) as the next decision-point, and notes a primary-task-aligned alternative (deeper paper-over audit pass) for anyone who'd rather keep doing CSS-confidence work than tooling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #3642 (arXiv:2403.11784) reports that some text remains black
in dark mode. Verified: Table 3 ("FTENTh Hardware Architecture")
emits three `<span style="color:#000000;">` header cells
("Component", "Manufacturer", "Model") that render black-on-dark
and become invisible. arxiv-browse's downstream theme
(arxiv-html-papers-theme-{20250131,20250916}.css) rescues only the
*modern* `--ltx-fg-color:#000000` form, not the *legacy*
`color:#000000` form — so legacy emissions fall through both
layers.
This rescue rule existed in `css/ar5iv/dark-mode.css` and was
disabled in a previous audit on the assumption that arxiv-browse
covered the legacy case. The evidence (inspection of both
downstream stylesheet versions) shows the assumption was wrong.
Re-enabling fixes the bug on both arxiv-html and ar5iv-labs
consumer paths.
Selector pattern unchanged:
[data-theme="dark"] :is(
[style^="color:#000000"],
[style*=";color:#000000"]
) { color: var(--text-color-author-black-dark) !important; }
Two attribute selectors so neither `background-color:#000000` nor
`border-color:#000000` get false-positively rescued.
Spot-check 2403.11784: chunk 1280-dark-p007 shows 1287 px diff
(Table 3 headers now legible), every other chunk 0 px PASS —
surgical fix. Verified cross-engine; both Chromium and WebKit
baselines refreshed for the six corpus papers with legacy
emissions (2409.12111: 2192 sites, 1909.02255: 69, 2006.13760: 34,
1910.06706: 17, 2403.11784: 3, 2105.00613: 2).
Iteration-5 item #4 (triage user-tracked bug backlog) closed.
13 of 15 issues in `black-on-black-list.md` were already closed
by the project owner (paper-by-paper regenerations). The two
remaining: #3461 stale (production v1 now has 0 legacy emissions),
#3642 fixed by this commit. The `\rowcolor darkgray` literal-leak
half of #3642 is a LaTeXML upstream emission (`<span
class="ltx_ERROR undefined">\rowcolor</span>`) — out of CSS scope.
Closes #3642 (CSS half), supersedes wontfix on #3461.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ten commits today on `glowup`. Iteration-5 items #3, #2, #1-phase-1, and now #4 (bug backlog triage) all closed; real user-reported bug (#3642 arXiv:2403.11784 Table 3) fixed by re-enabling the legacy `color:#000000` dark-mode rescue. Banner now lists all ten commits and recommends the user post resolution comments on #3461 and #3642 upstream — that's a user action, not Claude's. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Firefox baseline (`tools/baseline/firefox/`) generated under the same guards as today's other engine runs (CONCURRENCY=1, timeout 1500). Took two batches — the first hit the 25-min timeout at 37/47 papers; a 10-paper completion run cleared the remaining. 3420 snapshots in batch 2, ~5100 in batch 1, 9645 files total. The harness now baselines all three engines Playwright ships (Chromium + WebKit + Firefox) on all 47 corpus papers. Closes the "we tested two engines, not three" caveat on the best-in- class claim. arXiv:2105.10386 remains the renderer-crash outlier across all three engines (Firefox got the furthest — 1071 chunks at 320 viewport before stopping; same fundamental DOM-size limit as Chromium and WebKit). No CSS change; pure baseline addition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the GLOWUP_* internal docs and the color_overview.html visualization artefact: - docs/GLOWUP_AUDIT_START.md (788 lines, pre-iter-1 frozen snapshot) - docs/GLOWUP_PHASE2_AUDIT.md (939 lines, pre-iter-2 frozen snapshot) - docs/GLOWUP_PROGRESS.md (1090 lines, in-flight work log + banner) - docs/GLOWUP_WISDOM.md (1315 lines, cross-iteration lessons) - docs/color_overview.html (198 lines, hex-color grid viz) These served the iteration cycles but don't have a long-term role for consumers of the CSS theme. The Q4-style date-stamped verifications that mattered already live as comments in the CSS source. The consumer-facing surface (TOKENS, THEMING, BASELINE_AUDIT, CONTRIBUTING, RFC, ar5iv_colors_report) is unchanged and remains the canonical documentation set. Updates references in README.md, CONTRIBUTING.md, docs/THEMING.md, css/ar5iv/a11y.css, and .github/workflows/ci.yml to remove dangling links. No CSS behaviour change; lint stays clean. Net: -4330 lines of internal scaffolding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstream main switched the math font from "latin modern math" to "STIX Two Math" with a Google Fonts import (commit b5d2603). Conflicts surfaced because the glowup branch had moved the math-font tokens out of `css/ar5iv.css` into `css/ar5iv/tokens.css` during the iteration-1 tokenisation. Resolution: - `css/ar5iv.css`: keep the glowup branch's layered architecture (the `:root`-with-all-tokens block from main is gone here — tokens live in the tokens layer now). - `css/ar5iv/tokens.css`: apply main's font change at the new location. `--math-font-family` and `--math-caligraphic-font-family` now read `"STIX Two Math"`. - `css/ar5iv-fonts.css`: keep the glowup branch's `display=swap` +explanatory-comment improvements, swap the math import to Google Fonts STIX Two Math. No CSS behaviour change beyond the font swap that upstream main already shipped. Lint passes; lightningcss bundle builds. The per-developer visual baselines under `tools/baseline/<engine>/` will show diffs on math-heavy papers after this lands — the font metrics differ. Re-baseline locally with `node tools/visual.mjs --update` (and per-engine for cross-engine coverage). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…drift Three issues surfaced by auditing the STIX Two Math merge: 1. `display=swap` on the math font @import was wrong. Upstream main's commit b5d2603 deliberately omitted it; my merge resolution added it back along with the URL change. STIX Two Math has glyph forms (integrals, large operators, subscript metrics) that the Cambria Math / system math fallback approximates imperfectly — showing the fallback during the load window can produce mis-aligned formulas, which is worse than a brief paint delay. Drop `&display=swap` from the math import (Noto body fonts keep it). 2. `tools/visual.mjs` did not await web-font load before screenshotting. Under `waitUntil: 'load'`, the page's load event fires before `display=swap` fonts have completed their swap — so the harness was capturing fallback glyphs (Cambria Math instead of STIX Two Math; system serif instead of Noto Serif). Add `await page.evaluate(() => document.fonts.ready)` after goto. Makes the harness deterministic across the font-swap race. Per-developer baselines need refreshing locally. 3. `docs/TOKENS.md` still described `--math-font-family` as "Latin Modern Math + fallbacks". Updated to "STIX Two Math + fallbacks". TOKENS.md ↔ tokens.css drift check still passes (41 :root tokens documented). Spot-check on arXiv:1502.04633 chunk 1280-light-p008 (dense MathML): no whitespace wells around wide formulas, justification tight. The iteration-5 7-mrow MathML deletion (commit 5e0521c) still holds under STIX Two Math. Cite-ref workaround unaffected (cites aren't math). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…otes) PR brucemiller/LaTeXML#2767 reworks the frontmatter HTML. Adapt ar5iv's forked rules to the new markup: - author notes: tolerate the new `.ltx_author_notes_content` wrapper — `display: contents` keeps the contact layout identical, and the four note-robustness selector pairs (plus the a11y :focus-visible pair) collapse to wrapper-tolerant descendant combinators. - title pubnotes: the new `.ltx_pubnotes` block (\thanks / DOI / arXiv / preprint now emitted inside the title <h1>) collapses behind a superscript dagger that reveals a popup on hover / tap / focus, mirroring the footnote affordance and keeping the title uncluttered above the primary author block. - keywords/classification: restore the ": " separator the XSLT dropped, via `::after` on the bold label (correct once the corpus is rerendered to the new markup; this branch waits for that rerender). - contact / note @name labels: italicise the new `.ltx_contact_name` and `.ltx_note_name` spans. role=institute -> affiliation needed no change (already on `.ltx_role_affiliation`; no `institute` references existed). Verified against a hand-authored new-markup fixture (pubnotes dagger + popup, author-notes wrapper, contact-name italics, colon-less labels) in light / dark / 320px; chromium regression over four old-markup papers is pixel-identical except the intended keyword/classification colon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Floated (wrap) figures: let the following paragraph wrap beside them instead of clearing it; give a gutter on the side the text flows past with no stray top margin; un-float to full width below 52rem. - Multi-panel framed floats (e.g. side-by-side algorithm phases) span the content width instead of shrink-wrapping to a single column. - Full-width (non-floated) tables and figures clear preceding wrap-floats, so they break onto their own full-width row (e.g. Table 4 beside a wrapfigure) rather than getting squeezed into the narrow remaining column. - Hide the redundant author contact label (.ltx_author_notes .ltx_contact_name, e.g. "Affiliation:" / "E-mail"), generalizing the base LaTeXML.css multiline-only rule to single-line author blocks too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An `li.ltx_item`'s tag is `display: inline` (baseline-aligned) while the adjacent `.ltx_para` is an `inline-block` with `vertical-align: top`. Their first-line baselines diverge by (line-height_tag - line-height_para)/2, so a tag inheriting the default `normal` line-height rendered above the paragraph's first line. Match the prose line-height on the tag so the baselines coincide. Enumerate lists use the subgrid layout and are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On viewports <= 52rem the two-column bib subgrid is too cramped, and the single-column fallback stacked the [n] tag above its content with a gap. Drop the grid there and let the tag plus every bibblock flow inline, so the tag leads the first content line and the blocks fill the width and wrap naturally instead of one block per line. The wide-viewport subgrid (with baseline alignment) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jsDelivr's on-the-fly minifier (clean-css v5.3.3) cannot parse our modern CSS (@layer / light-dark() / :has() / nesting) and falls back to serving the 72 KB un-minified original with a "Failed to minify" banner. Rather than dumb the source down to clean-css's level, ship a pre-built lightningcss bundle and let the CDN serve it verbatim (as bootstrap's committed dist/*.min.css is served) — verified byte-identical rendering to the modern source, 49.8 KB, all modern features preserved. - build: also emit dist/ar5iv-fonts.min.css; drop the sourcemap so the committed artefact is exactly the two .min.css files. - .gitignore: un-ignore the two distributed bundles (rest of dist/ stays ignored); note why they're committed. - release automation (npm `version` lifecycle): preversion = lint version = build + `git add dist` (dist lands IN the version commit) postversion = push HEAD + the tag so `npm version <patch|minor|major>` is a one-command release; jsDelivr auto-serves the new tag (no publish step). .npmrc keeps tags unprefixed (0.9.0, matching history). - .github/workflows/release.yml: on a version tag, rebuild and fail if the committed dist is stale (the committed-dist model's one footgun), then warm the jsDelivr cache for the tag. Read-only, no secrets. cortex will point at cdn.jsdelivr.net/gh/dginev/ar5iv-css@<tag>/dist/*.min.css.
Two foreignObject fixes, exercised by latexml-oxide's new nested tcolorbox output (arXiv 2605.02240): 1. The fo-content width/line-height sledgehammer (`& *`) no longer hits SVG geometry elements (svg, foreignObject, image, rect, use): width:inherit!important on a nested <svg class="ltx_picture"> collapsed the inner box's foreignObject to 0 and shredded its layout. HTML text at any nesting depth still gets the cascade. (Note in-source: `:not(svg *)` is NOT the guard — all fo content descends from the outer svg, which would disable the rule.) 2. Newer LaTeXML emits an exact TeX font anchor (font-size:<N>pt) inline on each foreignObject; the --svg-text-size:0.82em heuristic approximated that anchor when absent and would double-shrink on top of it. With the anchor present, fo content defers to it (font-size:1em); older conversions keep the heuristic. Verified in Chrome (light+dark, 800/1280px): nested innercode boxes render single-line inside their frames; the Appendix G title/user boxes no longer overlap (content 235px inside a 297px fo, previously 408px spilling 111px over the box above). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rationalization doc Newer LaTeXML emits an exact font anchor (font-size:<N>pt) and correct interior widths on every foreignObject; several ar5iv rules were calibration fudges for the pre-anchor world and must not double-apply. docs/SVG_BOXES.md records the engine contract and the rule-by-rule rationalization against upstream LaTeXML.css. Behavior changes (anchored documents only; legacy keeps the tuned look): - fo content line-height 1.2 (= TeX \baselineskip, via --ltx-fo-line-height var; legacy stays 1). line-height:1 under-filled TeX's height budget ~17%, reading as excessive frame height. - align-items:start when the content holds an ltx_minipage (a measured box's upper part): TeX fills boxes from the top, so estimator slack falls below the text instead of piling into a fake band above it (arXiv 2605.02240 Appendix G). Applies to legacy too — same bug there. Verified in Chrome light+dark at 800/1280px on 2605.02240: all Appendix G panels fill 74-95% of their frames top-down, nested innercode boxes intact, no collisions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On wide displays (>96rem) the footnote body renders in the side margin via the in-flow negative-margin trick — but being in flow, its box height grew the carrier: a footnote inside a list item stretched the item by the full note height (arXiv 2605.00181, first LHCb bullet). Zero the .ltx_note_outer box height and let it paint through (overflow:visible): the note keeps riding its carrier line and keeps its margin-column x, while the carrier sizes to its own text (item 116px -> 32px, note pixel-identical in the margin). float-based escapes don't work here — list items establish BFCs that contain floats. Two notes anchored close together may overlap in the margin, the classic marginpar tradeoff. Small-screen popover path untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ver state The height:0 carrier-height fix must not apply while the global :focus-within popover rule is active — the bordered overlay needs its real box. Scope: .ltx_note:not(:focus-within) > .ltx_note_outer. Verified: unfocused item 32px/outer 0; Tab-focused outer 84px. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LaTeXML translates p{<dim>} tabular columns to fixed-width
inline-blocks sized for the author's PDF layout (often two-column
journals). In ar5iv's single-column layout those widths can be
absurdly tight: arXiv 2605.00310 Table III carries a 14.2pt "Year"
column that rendered one letter per line, and break-word collapsed
several headers mid-word ("Resolutio/n").
Floor each block at min-content with word integrity restored inside
(overflow-wrap:normal): squeezed columns relax to readable (Table III
685->712px, Table IV ->736px, headers whole words), deliberately
narrow paragraph columns still wrap at spaces, nothing ever narrows.
Verified no effect on already-wide tables (T8/T9 byte-identical,
still on their overflow-x:auto scroll path).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
::selection borrowed --note-highlight-color, whose dark value (#231d02) is a near-black olive — selections were effectively invisible on the #0d1117 page. New --selection-color token: light theme keeps the note yellow (#ffffd4); dark gets a brighter, saturated gold (#6b5a10) — a mid-tone tint that stays readable under the unchanged inherited text color (~4.9:1 vs --text-color). Fallback chain keeps the old behavior where the token is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…budget Verbatim blocks in measured boxes arrive as one ltx_p per TeX-broken line. Three rendering-contract rules seal them to the TeX height budget (arXiv 2605.00468: 18 Prompt boxes spilling up to 980px, now all contained — engine-side prevdepth/zi4 fixes land separately): - one source line, one rendered line (white-space:nowrap): TeX already broke the lines; re-wrapping at browser mono metrics desynchronizes the line count from the measured height. - no prose inter-paragraph margins on verbatim lines (typewriter-led ps, multi-span lines via :first-child, and BLANK lines — an empty inline-block-only p carries no font class to match). - monospace calibration under the TeX anchor: 0.875em ~ cmtt's 0.525em advance vs ~0.60em web monos, so browser line capacity matches TeX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hitespace Supersedes the hardcoded 0.86em/nowrap iteration: - white-space: pre on line-mapped verbatim ps — one source line, one rendered line (the conversion budgets SOURCE lines until engine-side breaklines emulation lands), and leading-space indentation preserved (pending the engine emitting it; SYNC_STATUS). - Mono capacity is calc()-derived from theme tokens: --code-font-advance (0.600, measured; declared BESIDE --code-font-family so a font swap updates one token) over --tex-tt-advance (0.525, a TeX constant). - @supports font-size-adjust: ch-width 0.525 upgrade: the browser reads the REAL font tables and self-calibrates — user-configured fonts are honored with no hand-measured metric (user directive: the final browser font is configurable; conversion must not preempt it). 2605.00468: horizontal overshoot 0%, vertical residual 10-24px on the five tallest boxes only (~1%; closes fully with engine breaklines). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ock fill-width Four scoped rules completing the measured-box rendering contract: - breaklines wrap-parbox interior: inherit the typewriter font context (the global .ltx_para/.ltx_text serif rules were re-styling it), TeX geometry (no prose margins, ragged left), pre-wrap on the LEAF paragraph only. - white-space pre/pre-wrap are leaf-only: serializer newlines between child elements render as phantom line breaks under pre (a 2-line wrapped paragraph measured 5 lines through its wrappers). - the wrapped line renders as a flex row (TeX's hbox: break-indent beside wrap-parbox; as inline-blocks the collapsed inter-element spaces pushed the parbox onto its own line box). - block-level pictures fill the figure column via viewBox scaling (native proportional zoom), EXCLUDING nested pictures inside foreignObject whose width is an exact TeX dimension in the outer coordinate system (2605.02240: letting them fill re-inflated them 12->30px past their frame). Witnesses: 2605.00468 all 24 pictures contained on both axes; 2605.02240 A/B-identical to the no-fill-width baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stack Zero-height margin notes (d93a9e7) cannot push each other apart, so two footnotes anchored a line apart painted on top of each other in the margin (arXiv 2605.00501 fn2/fn3, user-flagged). Presentation must be either/or — ALL footnotes stay in the sidemargin on wide displays (user) — so in a paragraph carrying 2+ footnotes the notes keep their real height: the margin floats' clear:both then stacks the second note below the first, marginpar-style. The zero-height carrier protection remains the default everywhere else (its motivating case, the 2605.00181 BFC list item, stays covered). Verified 2605.00501 at 100rem and 125rem: fn2/fn3 stacked with a 32px gap (was 28px overlap), all four footnotes uniformly in the margin, carrier paragraph height unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… min-fonts Rendering fixes: - fix(dark-mode): gate the OS-preference dark rules on :root:not([data-theme]) instead of :not([data-theme="light"]), so sepia (and any explicitly-named theme) is excluded from the image-filter + author-colour inversion under prefers-color-scheme: dark. Explicit [data-theme="dark"] keeps its own branch, so no regression. Corrects the (previously wrong) immunity claim in tokens.css. - fix(math): correct three .ltx_mathvariant_* sans-serif selectors that were hyphenated (mathvariant-...) and matched nothing, so bold/italic sans-serif math variants now receive their styling. - fix(bib): correct .ltx_bib_article .ltx_bib_title (was .bib-title, which matched nothing) so article titles render upright. Correctness / docs: - docs(css): fix the cascade !important+layers comment — un-layered !important is the WEAKEST author-important, not the strongest (no live impact; the transform block wins on author-important-vs-normal). - docs: fix the release procedure (unprefixed 0.9.0 tag, not v0.9.0; a v-tag silently won't trigger release.yml); drop the false sourcemap claim; add a Browser support section; add CHANGELOG.md. - docs: CONTRIBUTING file-tree gains dark-mode.css + SVG_BOXES.md; drop the stale "(planned)" note on TOKENS.md. Build: - build: drop the orphaned minified fonts bundle (it was built and shipped but referenced nowhere); refresh dist/ar5iv.min.css. - chore: remove dead/duplicate declarations (duplicate .ltx_nodisplay, shadowed .ltx_flex_size_many, opacity:1-before-inherit, a stale commented-out selector). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
css/ar5iv/tokens.css+docs/TOKENS.md;light-dark()-driven theming; sepia theme;prefers-contrast: moremode.css/ar5iv/a11y.css)::focus-visible, keyboard footnote popovers,prefers-reduced-motion,:target,::selection,forced-colors.ar5iv.css(margin-inline-*,inset-inline-*,text-align: start/end, etc.).tools/visual.mjs— Playwright across Chromium/Firefox/WebKit, paginated rendering for long math papers (single fullPage for short, viewport-tall chunks for over-limit), per-engine baselines undertools/baseline/<engine>/(gitignored), 320 + 1280 viewport × {light, dark} matrix.style=\"color:#000000\"dark-mode rescue after verifying arxiv-browse's downstream theme only covers the modern--ltx-fg-color:#000000form..github/workflows/ci.yml: stylelint + TOKENS.md ↔ tokens.css drift check + lightningcss build.Notable trade-offs
--updatelocally). Shared-baseline tarball deferred until same-machine AA-drift determinism is pinned down — currentpixelToleranceis 400.skip-renderer-crashwith partial coverage of the first ~57 %.:has()andlight-dark()are Newly-Available (not yet Widely-Available), policy-exempted perdocs/BASELINE_AUDIT.md. Projected un-gated Widely-Available milestone: Nov 2026.oklch(from …)Jan 2027 (currently@supports-gated with HSL fallback).Test plan
npm cinpm run lint— stylelint + drift check both passnpm run build— producesdist/ar5iv.min.cssnode tools/visual.mjs --updatethennode tools/visual.mjs— Chromium baseline + verify pass--engine=firefoxand--engine=webkitafternpx playwright install firefox webkit(WebKit also needssudo apt-get install libavif16on Linux)🤖 Generated with Claude Code