Changelog
3.3.4 - 2026-08-12
- Enforce each semantic font family at the role boundary so nested prose cannot
override headings, navigation, labels, or controls through selector
specificity.
- Treat native form controls with browser-owned
normal line height as a
platform exception while continuing to verify their size, family, and weight.
- Bound visual-contract asset and font waits and include exact typography
failures in the generated report.
- Resolve semantic font roles within
site-main so a role always wins over a
generic element rule, including label text represented by a paragraph.
- Report the URL and status of failed browser requests in visual-contract
output instead of emitting an unactionable generic console error.
- Add a responsive figure-label role for the research compass, preserving its
compact mobile geometry without violating the semantic type contract.
- Force lazy images to load during rendered audits and identify clipped icon
controls by their accessible name or class.
- Measure icon-only controls from their visible children so an invisible,
absolutely positioned tooltip cannot create a false clipping failure.
- Own the shared design-consistency audit in core instead of maintaining an
identical copy in each consumer.
- Reset each visual-contract case to the page top and let temporary menu
transitions settle before screenshots are captured.
- Use bounded settle intervals for screenshot positioning because background
headless pages may throttle animation frames indefinitely.
- Include the centralized design audit in core’s own JavaScript syntax gate.
- Let semantic font roles win through their content-boundary specificity
instead of bypassing the cascade with
!important.
- Pin Puppeteer Core to the prior mature release so GitHub’s minimum-release-age
supply-chain policy can verify and install the visual-audit dependencies.
3.1.0
- Publication filtering, topic counts, active states, and horizontal topic
navigation now have one shared implementation. Topic rows expose arrow
controls only when they overflow, instead of clipping the next topic.
- Added a Scholar sidecar generator that refreshes citations and direct record
links while leaving canonical publication titles, abstracts, and links under
local editorial control.
- Added a headless visual contract for desktop/mobile and light/dark review. It
audits typography, overflow, images, controls, and shared design invariants,
and stores screenshots as CI artifacts.
- Personal publication records stack actions below titles at the narrower
author-rail measure; the lab layout retains its wider side-by-side form.
3.0.0
- The eye colours are no longer Google’s.
#34A853 was Google green exactly,
ΔE00 of 0.00, and #EB4336 sat 0.29 from Google red, which is below any
perceptual threshold. The original artwork carried the whole four-colour set;
the rebrand dropped blue and yellow and kept these two without checking what
they were. Green becomes #1C9444 and red #E22D39, both about ΔE00 6 from
Google: measurably ours, visually the same green and red.
- Green also failed non-text contrast on the light paper at 2.63:1. Deepening
it takes that to 3.37:1. Red goes from 3.35 to 3.87 and moves 6 degrees
cooler, widening the gap to the clay accent from 10.8 to 14.0 so the two stop
reading as an imperfect match.
--logo-red follows, as does the red in the wordmark’s final syllable.
2.12.2
- The inline partial’s id prefix is a caller-supplied
uid rather than a
constant. A page can include the same mark twice, as the personal site does
in the nav and the author profile, and two copies of #robot-knockout means
the second mask resolves to the first.
2.12.1
- The inline partials carry no
<style> block. The sites own no CSS, the core
does, so a stylesheet inside a consumer’s markup both fails the ownership
audit and forks the styling. Nothing needed it: every element already carries
its own fill and the ink rides currentColor. The generator now asserts the
partials reference no classes at all.
2.12.0
- The marks render as inline SVG instead of
<img src="...svg">. An <img> is
still an image: the browser rasterises it into a bitmap whose resolution
depends on the intrinsic size, the device ratio and whatever compositing the
ancestors force, which is why the header and compass logos stayed soft on
phones through three attempts at those variables. Inline SVG is live vector
and none of it applies.
brand/tools/build_brand.py writes the partials into each site’s
_includes/brand/, so the artwork still has one source. Ids are namespaced
per partial so two marks can share a page.
- One element now serves both themes: the ink is
currentColor, driven by
.brand-mark { color: var(--text) }, replacing the light/dark image pair and
its display: none toggling.
2.11.0
- One chest board at every size. The tab icon carried a simpler one-trace board
while the header logo had two, so they were not the same mark. The simpler cut
read better at 16px, but an icon that changes its internals by size is worse
than a little softness. The second cut is gone.
- Moved the header’s
backdrop-filter onto a pseudo-element behind the content.
It promotes its own element to a composited layer, and WebKit can rasterise
that below device resolution, which softened the logo and nav text on phones.
Behind the content the effect is identical and the children stay sharp.
- Added
brand/social/ with square org avatars for GitHub and Hugging Face.
Neither platform serves a light and a dark avatar, so these are drawn on solid
white instead of transparent, and sized to survive a circular crop.
2.10.2
- Fixed the header logo looking blurry on phones. The generated SVGs declared
no width or height, so a browser treats their intrinsic size as its 300px
default and some engines rasterise at that before scaling up. The header
lockup sits at 138 CSS px, which is 414 device px at 3x: past 300, so it was
upscaled. Desktops never exceed 276 and so never showed it. Every generated
SVG now declares a generous intrinsic size.
2.10.1
- Fixed the favicon looking blurry on phones. The ladder jumped 48 to 192, so a
device at 3x needing 48-72 real pixels had to stretch the 48. It now runs
16/32/48/64/96/128, each drawn at its size and all declared, and iOS matters
here because Safari ignores SVG favicons and only sees these.
- The whole favicon ladder uses the small cut. Pixel count is not perceived
size: a 64px icon in a tab is a small icon rendered at 3x, not a large one.
2.10.0
- Icons are no longer one 48px bitmap that the browser rescales into every
context, which is what made them look soft. Each size is now rendered at that
size: a scalable
favicon.svg that follows the tab bar’s light or dark theme,
native 16 and 32 PNGs, a favicon.ico packing real 16/32/48 renders, a 192
for Android, and the 180 touch icon. Sizes at or below 32 use the small cut,
larger ones the display cut.
- Both sites declare the full set through
rel="icon" rather than the legacy
rel="shortcut icon", and the personal site gained the touch icon it lacked.
2.9.3
- The favicon is framed identically on both sites. They had drifted to 42/48
and 39/48 of the canvas height, so the same mark sat at two different sizes
depending on which tab you were looking at. One constant now covers both, and
the generator asserts the two files come out byte for byte identical.
2.9.2
- Removed the faint horizontal line that appeared under the robot’s eyes at
favicon sizes, joining the two mouth tabs. The head frame was a filled rect
with the window punched through it, and the two bottom edges sat 0.4 apart:
they cancel exactly at size, but inside one small pixel the black composites
over the white rather than subtracting, leaving about 20% ink across the whole
head. The frame is now a top bar and two walls, so there is nothing to cancel.
2.9.1
- Fixed endless empty scrolling below the footer on mobile. The nav scrim is an
absolutely positioned child of a sticky header, and it held a viewport-tall
box even while the menu was closed, so it travelled down the page and kept
extending the scrollable area. It now holds no layout until it opens.
.site-main gained a dvh fallback for its minimum height, matching the
other viewport-height rules.
2.9.0
- Both websites now load the robot from
brand/logos/ through the submodule
instead of keeping their own copies. The vectors live here; each site keeps
only the rasters whose framing is its own: favicons, touch icons, Open Graph.
brand/src/ holds the robot as authored and brand/logos/ is generated, so
there is one place to edit and no ambiguity about which file is canonical.
- Dropped what nothing rendered: an unused alternate line-art robot that still
carried the pre-circuit chest, the styles only it used, and the wordmark the
icon files never draw. Files are 17-49% smaller and render identically.
- Each
viewBox is now tight to the artwork, so the SVGs drop straight in where
a tight-cropped PNG used to sit.
2.8.0
- The DeCLaRe robot moves into the core as
brand/, with the generator that
writes every consumer asset from one definition. Both websites showed the
mark already; neither owned it.
- The robot’s chest panel becomes a circuit board. Its window and slot merge
into one panel carrying traces painted into the same luminance mask as the
silhouette, so the board takes the theme colour and stays transparent behind.
- Two optical cuts: two mirrored traces for display sizes, one heavier trace
for 32 px and below, where the panel is too shallow to hold two.
2.7.0
- Added a compact publication-year selector with sticky-toolbar-aware scrolling
and automatic disabling of years hidden by active filters.
- Kept section rails available as an optional composition while allowing both
sites to use simpler single-column page flows.
2.6.3
- Section menus withdraw links to sections the page has filtered away, and
refresh when those sections appear or disappear.
2.6.2
- Publication topic badges always start their own row.
2.6.1
- Page leads fill their column instead of leaving unused space beside short
introductory copy.
2.6.0
- Restored a two-family type system.
--font-sans carries headings, navigation,
controls and labels; --font-serif carries reading prose; --font-mono carries
metadata. A single family had left every role separated only by size and weight.
- New warm palette: paper
#f0eee6, ink #141413, clay accent #c1653f, with a
matching dark theme. Replaces the cool paper and cobalt accent.
- Removed the rule under the site header; the page header below it already opens
with a 2px ink divider.
- Filter controls (
.pub-cat-btn, .pub-cat-badge) are quieter: smaller, lighter
border, muted until active. They are navigation, not calls to action.
2.5.0
- Changed the unified default family from Archivo to Spectral for a more
scholarly, editorial voice across both sites.
- Removed Archivo-specific variable-width settings; every semantic role now
inherits the same family without font-specific axes.
2.4.2
- Made native code and keyboard elements inherit the single site family,
removing the browser’s implicit monospace exception.
2.4.1
- Narrowed the single-family verifier to real
font-family declarations so it
does not mistake the --font-family token definition for an override.
2.4.0
- Replaced the serif, sans-serif, monospace, display, and Bengali family tokens
with one public
--font-family token.
- Made every semantic type role inherit the same Archivo family, including
prose, metadata, navigation, controls, publications, and code.
- Replaced the three family customization arguments with
$font-family.
- Added consumer checks that reject legacy family tokens and multiple web-font
imports.
2.3.1
- Added
--rule-heavy token (2px solid var(--rule-strong)) for the heavy ink
rule that closes a page header. Routed the page-header divider and the
publication-card top rule through it, so the 2px structural weight is defined
in one place instead of being repeated as a literal in consuming sites.
2.3.0
- Added reusable compact and split page-lead compositions.
- Added a public density API for page insets, title-to-lead spacing, section
boundaries, object padding, layout gaps, and content measure.
- Reduced duplicated whitespace in the default desktop and mobile rhythm.
2.2.2
- Audit active page sources for inline style attributes and JavaScript style
writes before those declarations can reach the rendered DOM.
- Continue to permit custom-property-only inline content data, such as
per-person portrait positioning.
2.2.1
- Centralize shared color constants used by both sites.
- Make the ownership verifier reject literal consumer colors and
!important
overrides in addition to local typography and inline visual styles.
2.2.0
- Enforce single ownership of typography by rejecting all consumer declarations.
- Add a deterministic style-ownership auditor and mechanical cleanup mode.
- Promote multilingual personal-hero text to explicit shared semantic roles.
- Add compact semantic roles for academic-activity records.
2.1.5
- Classify update-category tags as shared labels rather than wrapper metadata.
- Keep update dates on the metadata tier while removing local visual drift.
- Assign compact item and supporting roles to mentorship and academic-contribution groups.
2.1.4
- Use regular weight for inactive contextual navigation.
- Preserve semibold emphasis for the active section only.
2.1.3
- Lower structural-fallback specificity so explicit semantic roles always win.
2.1.2
- Remove the legacy article-wide supporting-text fallback.
- Align structural CSS fallbacks with the compact-content DOM roles.
2.1.1
- Keep page-level narrative prose on the body tier.
- Reserve supporting typography for repeated records, cards, FAQs, openings, and compact content groups.
2.1.0
- Separate quiet navigation typography from primary action controls.
- Map repeated record and card headings to the shared item-title role.
- Preserve subsection scale for explanatory structures such as FAQs and openings.
2.0.0
- Replace wrapper-specific typography with one hierarchy derived from semantic HTML.
- Add a shared XPath contract consumed by both the browser and build pipeline.
- Add a deterministic Python fixer/verifier for roles, inline typography, and heading order.
- Exclude standalone project systems from the two-site typography audit.
1.8.1
- Extend literal typography checks to standalone project HTML pages.
1.8.0
- Define and document one semantic type ladder from display titles through labels.
- Add utility classes and customization arguments for every public type role.
- Normalize service rows, update-card lists, lab-note heroes, and people captions.
- Reject consumer CSS that introduces literal pixel/rem font sizes or numeric weights.
1.7.1
- Extend customization arguments to light and dark surfaces, text, borders, and complete accent palettes.
- Strengthen metadata typography selectors so semantic roles beat generic card-paragraph rules.
- Add shared statistic sizing and compact-control coverage for note links.
1.7.0
- Add the public
declare-customize mixin for brand, typography, shape, layout, and rhythm arguments.
- Document layout selection, consumer setup, supported customization, ownership, and upgrades.
- Preserve clicked section-menu targets when multiple sections share a horizontal row.
- Ensure inline menus reach their docked state before scroll-spy selection resumes.
- Centralize section-label chips, compact list grids, technical content formatting, and mobile content wrapping.
- Strengthen consumer verification against local copies of shared content and token rules.
1.6.0
- Add selectable lab and personal site shells with documented customization variables.
- Centralize wrapper columns, author-sidebar geometry, responsive stacking, and print behavior.
- Correct inline and mobile submenu targeting by measuring the menu’s docked position.
- Center both footer text and footer links.
- Promote FAQ answers to supporting-text size and centralize the FAQ row pattern.
- Reject consumer redefinitions of shared shell and FAQ selectors.
1.5.0
- Add a shared right-rail page composition for contents and publication-year navigation.
- Make rail placement independent of DOM source order and stack it consistently on smaller screens.
- Keep long desktop rail labels inside their boundary while preserving horizontal mobile navigation.
- Centralize muted, parenthesized publication-category counts and sticky archive controls.
- Guard the canonical shared layout classes against consumer redefinition.
1.4.1
- Give the shared page composition classes one inherited width-safety baseline.
- Leave flex/grid geometry and sidebar placement explicitly site-specific.
1.4.0
- Make global design tokens and base element rules core-only.
- Centralize inner-page metadata strips, title spacing, and divider treatment.
- Reject consumer redefinitions of shared tokens and page headers.
- Remove obsolete personal-site component styles from the lab consumer.
1.3.0
- Add one shared publication archive component family.
- Centralize publication search, filters, records, metadata, links, and abstracts.
- Keep archive composition and year-rail placement site-specific.
- Enforce single ownership for shared primary and secondary controls.
1.2.4
- Position anchored content from the rendered bottom of a sticky inline menu.
1.2.3
- Detect horizontal menus by layout rather than only by overflow.
- Apply the correct sticky-menu offset even when every item already fits.
1.2.2
- Reserve anchor offset ownership for the shared section-menu component.
- Reject consumer
scroll-margin-top declarations that can hide headings.
1.2.1
- Keep anchored section headings clear of sticky inline section menus.
1.2.0
- Move site titles, logo variants, and theme-aware logo switching into core.
- Centralize footer link states and responsive masthead logo dimensions.
- Reject consumer stylesheet definitions of shared site-chrome selectors.
1.1.0
- Introduce one inherited section-menu component with rail and inline variants.
- Centralize sticky, active, hover, focus, and mobile overflow behavior.
- Prevent consumer stylesheets from redefining shared section-menu classes.
- Preserve the first mobile item border by removing edge-clipping masks.
1.0.6
- Correct the consumer update instructions to reference the shared sync helper.
1.0.5
- Allow the sync helper to update initialized consumers before their first
parent-repository commit.
1.0.4
- Remove selectors absent from the combined generated output of both sites.
1.0.3
- Normalize nested lab call-to-action controls to the shared control scale.
- Reject reintroduced duplicate shared-contract blocks in consumer CSS.
1.0.2
- Enforce semantic type roles against legacy high-specificity component rules.
- Require the explicit
declare-core document inheritance contract.
1.0.1
- Preserve the established 1.65 baseline leading outside content regions.
- Add consumer synchronization and cross-repository alignment checks.
1.0.0
- Establish shared color, type, spacing, control, and chrome tokens.
- Unify base typography, buttons, navigation, footer, and theme controls.
- Centralize mobile primary-menu and in-page section-menu behavior.