Skip to content

WIP: Migrate pharos site to astro - #1380

Draft
brentswisher wants to merge 39 commits into
developfrom
explore/astro-site
Draft

WIP: Migrate pharos site to astro#1380
brentswisher wants to merge 39 commits into
developfrom
explore/astro-site

Conversation

@brentswisher

Copy link
Copy Markdown
Contributor

🚧 This is a WIP 🚧

You can review what I've done and where it's at in MODERNIZATION.md

This change: (check at least one)

  • Adds a new feature
  • Fixes a bug
  • Improves maintainability
  • Improves documentation
  • Is a release activity

Is this a breaking change? (check one)

  • Yes
  • No

Is the: (complete all)

  • Title of this pull request clear, concise, and indicative of the issue number it addresses, if any?
  • Test suite(s) passing?
  • Code coverage maximal?
  • Changeset added?

What does this change address?
Replaces the tooling used to generate pharos.jstor.org.

How does this change work?
It used astro instead

Additional context

brentswisher and others added 9 commits August 7, 2026 10:47
Adds packages/pharos-site-astro, an Astro port of the Gatsby documentation
site, as a candidate replacement for Gatsby. Both packages coexist for now
so the two can be compared side by side; the Gatsby site is unchanged.

This commit is a deliberate 1:1 copy of the Gatsby site's rendered output.
It prioritizes parity over idiomatic Astro, so it does not yet follow Astro
best practices — content is hand-written .astro rather than Markdown, and
some Gatsby quirks are reproduced intentionally.

The brand-asset zips under public/files/ are gitignored: nothing in the
source or built HTML references them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the follow-up work to make the Astro port idiomatic, now that the
initial commit has landed as a 1:1 copy of the Gatsby output.

Tiered by risk: Tier 1 is parity-safe and can start anytime, Tier 2 and 3
change rendered output and are gated on retiring pixel parity as the
acceptance test. Written for an agent picking this up without prior
context, so it includes the reasoning and verification commands rather
than just task names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The unanchored `lib/` rule targets compiled build output, but matches at any
depth, so it also excluded packages/pharos-site-astro/src/lib/ — six
hand-written source modules the site imports.

They were never committed with the port. A fresh clone had no src/lib at all
and could not build the package.

Negate the rule for that one directory and add the missing sources. Build
output under packages/*/lib/ stays ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`fontSizeMap` was an array paired by index against a filtered token list, so
adding, removing or reordering a line-height token would silently render every
example row at the wrong font size — no error, no build failure, no visual cue.

Key the map by token name and throw on an unmapped token, so the failure is
loud rather than silent.

Build output is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same Token / Value / Example <thead> was repeated in seven pages, the row
loop was structurally identical in all eleven, and a near-identical
`comment?: string` interface was redeclared in six.

Add TokenRows.astro, which renders the shared header and row loop from a `rows`
array. The example cell — the one genuine variation — is passed per row as an
HTML string and emitted with set:html. Column widths are a prop because the
pages disagree on them (40/30, 33/33/36, 40/40, and type-scale's four-column
25/20/25).

Hoist the duplicated interfaces into tokenFormat.ts as CommentedToken, plus
ScaleToken for the two pages where `comment` is required.

Pages drop from 624 to 453 lines. Build output is byte-identical across all 63
pages, and all 11 token pages remain structurally identical to production
(matching row counts, cell counts, and table dimensions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
navigation.ts hardcodes the nav lists and Sidenav.astro derives hrefs from them
via toSlug(), but nothing verified those slugs resolve. A typo shipped a link to
a 404, and a new page left out of navigation.ts was reachable only by direct
URL. Neither failed the build.

Add assertNavigation.ts, which checks both directions and throws. Explicit
ordering is preserved — it only compares sets, never derives order from the
filesystem. Both failure modes were verified by introducing them deliberately.

The page list is read with node:fs rather than import.meta.glob. A glob makes
every matched page a dependency of the calling module, and since the sidenav
renders on every page, that pulled each page's <style is:global> into one shared
bundle and inlined all of them into all 63 pages. PAGES_DIR resolves from
process.cwd() because the module is bundled into dist/.prerender before it runs.

Two pre-existing orphans are listed as intentional rather than fixed:
/content-style-guide/jstor-terms and /design-tokens/overview. Neither appears in
the Gatsby sidenav either, so linking or deleting them is a content decision.

Build output is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Record what each item became, the verification that backs it (byte-identical
build across 63 pages; 11 token pages structurally identical to production),
and the import.meta.glob hazard so the next agent does not reintroduce it.

Also note the .gitignore trap that left src/lib untracked, and restate that the
parity gating question is still open — everything remaining is blocked on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Astro server-renders the `<site-pharos-*>` markup, so the browser paints
it before the module bundle registers those elements. Until registration
an undefined custom element is an unknown tag, which computes to
`display: inline` -- the page collapsed into a run of inline text (the
sidenav laid out as a single ~4300px-wide line of run-together link
labels) and then snapped into place when the bundle landed.

The Gatsby site never showed this: it shipped an empty container div and
built every element client-side, so there was no server-rendered markup
to flash. The regression is a consequence of Astro's SSR output, not of
the port's markup.

Hide the elements with `visibility: hidden` while `:not(:defined)`
matches, reserving each one's final `display` so nothing reflows when
they appear. The rules clear themselves the instant
`customElements.define` runs and need no JS to tear down.

A CSS-animation failsafe reveals everything after 3s regardless. A
JS-set flag was tried first and is wrong: it cannot fire in the case it
guards against -- blocking the bundle left all 61 sidenav links hidden
permanently. The animation runs off the document timeline, so it fires
whether or not any script executes.

Verified on the production build over throttled 3G, measuring painted
frames with requestAnimationFrame rather than external polling:
401 painted-unstyled frames before, 0 after. Blocking the bundle now
degrades to unstyled-but-readable instead of blank. All 63 pages remain
byte-identical once asset hashes are normalised, and body height, nav
width and table geometry are unchanged on six sampled routes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
20 pages stay .astro deliberately: the 11 design-token pages are data
transforms whose body is a single TokenRows element, and the 6 brand-expression
pages are image galleries (iconography.astro has no <p> tags and 15 <img>).
Converting those would mean wrapping nearly every line in JSX. index,
getting-started and 404 are bespoke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b4cbaa3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentswisher brentswisher changed the title WIP: Migrate pharose site to astro WIP: Migrate pharos site to astro Aug 7, 2026
brentswisher and others added 14 commits August 17, 2026 17:18
The MDX conversion introduced two rendering bugs in the component examples,
both invisible in the source and only present in rendered output.

GFM's autolink-literal rule rewrote bare emails and URLs into links, including
inside an existing `<site-pharos-link>` — `alert` got six nested anchors with
`href="mailto:…"` and `target="_blank"` that hijacked the click, and `pill`
turned two plain-text demo labels into live links. GFM is now off: no page on
the site uses a single GFM feature (every table is hand-written HTML, and there
is no strikethrough, task list or footnote), so this removes the whole class
rather than escaping each occurrence.

CommonMark also wrapped element content written on its own line in a `<p>`,
whose 24px bottom margin inflated the element: buttons rendered 58px instead of
34px, large buttons 66px instead of 42px, and every row of `heading`'s preset
demo was wrong. This is core Markdown semantics with no config switch, and a
wrapper component cannot fix it — the paragraph forms below the wrapper, during
parsing. `rehypeUnwrapPharosParagraphs` drops a lone generated `<p>` inside a
`site-pharos-*` element, leaving genuinely multi-paragraph bodies (`alert`,
`modal`) alone.

Content pages are deliberately unmodified: the fix keeps source formatting from
being load-bearing, so a re-wrap for readability no longer changes geometry.

Verified against the production site — all 32 component pages now match on
element count, tag sequence and rendered geometry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reduces inline `style` attributes from 242 to 75. What was extracted was
layout; what stays inline is the subject of an example - nine of the ten
remaining on `typography.astro` are the `font-family` of a type specimen, which
is the thing the page demonstrates rather than styling applied to it.

Spacing is named for its reason rather than its value. An earlier pass used
`.u-mb-1`-style utilities and they are gone: a class named after a measurement
tells a reader nothing about why the space is there, and the number is
meaningless without the Pharos scale in front of you. `.astro` pages express
these in their own scoped `<style>` (3 files used that idiom before, 7 do now);
MDX pages cannot carry one, so their four names live in `markdown.css` as
`.doc-*`. Eleven heading cases needed no class at all.

All four malformed declarations inherited from the Gatsby port are resolved.
Three were `var(--pharos-spacing-one-half-x;` with no closing paren, which
rendered *correctly* by accident - the browser's error recovery closed the
`var()` at end-of-input and the `fill` it swallowed was masked by the colour on
the `<li>`. `typography.astro` carried `margin-bottom: 4rem)`, which production
also drops, so the dead declaration is removed rather than repaired: applying
the 4rem the author intended would change the page.

Two rendering bugs found while comparing every route against production:

- `##` headings rendered 36px against production's 52px. `Heading.astro` set
  Pharos' `no-margin` on `##`, moving the 16px onto the following content, so
  the gap was 0 before a paragraph but 16px before a `###` - inconsistent
  within one page where production is a uniform 0, and every element below sat
  high. `pagination` drifted ~90px; it is now +1% on total height.
- `link.mdx` had a doubled list marker that MDX read as a second list level,
  rendering an empty bullet wrapping a nested list.

Verified against the live site across all 62 routes and by before/after geometry
snapshots of every touched page: element counts, computed colour, margins and
box dimensions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 32 pages under src/pages/components/ repeated the same frontmatter
contract 32 times and enforced it nowhere. They now live in
src/content/components/ behind a single [slug].astro route, with the
contract declared once in src/content.config.ts: title and description
are both required, so omitting one fails the build naming the file and
the field instead of rendering a page with no standfirst.

All 32 pages build byte-for-byte identical to before the move.

assertNavigation.ts had to learn about collections. It scans src/pages
with readdirSync, which cannot see collection entries or resolve a
dynamic route, so the first build after the move failed all 32 pages as
sidenav links to a 404. It now enumerates collection entries separately
and skips bracketed route files, which are renderers rather than pages.
Both failure directions were re-verified afterwards.

The sidenav keeps reading navigation.ts rather than deriving from the
collection. 8 of the 32 sidenav labels differ in case from the page's
own title -- sentence case in the nav, title case on the page -- so
deriving them would visibly change the sidenav. The hand-maintained list
is also what keeps the nav-to-page check meaningful: a list generated
from the collection cannot disagree with it.

storyBookType stays a prop on <Example> in the body rather than moving
into the schema, keeping it next to the demo markup it configures. It
remains unvalidated.

Two notes for future collections. Page routes carried
`export const components = mdxComponents`, which MDX honours only for a
file that is itself a route; through <Content /> it is ignored, so the
route must pass components explicitly or every heading silently falls
back to a plain <h2>. And z is imported from astro/zod, not
astro:content, which Astro 6 deprecated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`astro check` type-checks the Astro package but never inspects template
markup, so pages were not linted for accessibility or dead code the way the
rest of the monorepo is.

Add eslint-plugin-astro and astro-eslint-parser, which hand frontmatter to the
TypeScript parser and the template to the jsx-a11y rules. This needed no new
script: `.astro` joins the extension list the root `lint:eslint` already globs,
matching how every other lint task here runs. All 53 .astro files pass with 0
errors. A per-package lint script was tried and dropped -- it duplicated the
root's job, and eslint is a root devDependency that is not on a workspace's
PATH.

`.astro` is added to lint-staged too; its `*.{ts,tsx,js,mjs}` glob was skipping
every page on pre-commit.

Astro's generated `.astro/` cache is added to the global ignores, alongside the
existing entries for dist/, .cache/ and lib/. Its generated content.d.ts is
otherwise linted with plain JS rules and reports 21 unused type imports.

Also fix 3 pre-existing stylelint comment-empty-line-before errors, so
`yarn lint` passes end to end. Whitespace only -- the built CSS bundle keeps
the same content hash and all 63 pages build byte-identical.

Note the 11 .ts files in src/lib/ are still unlinted: eslint.config.mjs is flat
config but its tsConfig/tsxConfig blocks use eslintrc syntax, so no .ts or .tsx
file in any package is linted. Repairing that surfaces 988 pre-existing errors,
919 of them in the Gatsby package slated for deletion, so it is left as a
documented follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All 82 `<img>` tags now reference imported assets, and the 61 files they point
at move from public/images/ to src/images/ where the build can see them. A
path under public/ is just a string: a typo or a rename is a broken image in
production with a green build. Every reference is a real import now, so the
build fails naming the file.

Payload over the pages with raster images: 5,699kB -> 1,657kB at 1x. color
-93%, tooltip -86%, elevation -83%, voice-and-tone -74%, typography -73%; the
imagery hero alone is 1,458kB PNG -> 47kB WebP. The animated GIF on tooltip
becomes animated WebP with all 82 frames intact (1,687kB -> 320kB) -- worth
checking whenever a GIF is involved, since losing the animation is silent.
Images carrying both width and height go from 1 of 82 to all 82, so the boxes
are reserved before load.

SVGs stay `<img>` with an imported asset rather than becoming components.
Astro inlines an imported SVG, and logos.astro alone references 511kB of them:
inlining would take logos.html from 22kB to ~530kB of uncacheable HTML to save
15 requests. Importing the asset and using `src={x.src}` keeps them separate
and cacheable while still getting validation, a content-hashed URL and
intrinsic dimensions.

`<Image>` emits intrinsic width/height, so any CSS rule that sets only a width
now needs `height: auto` -- otherwise the height attribute pins the rendered
height and the image stretches. It cost 650px of page height on imagery and
broke logos and typography the same way. Four rules needed it, plus .thumb and
the home thumbnails. A global `img { height: auto }` looks like the tidy fix
and is not: Pharos' image-card sets its own image height, so a global rule
overrides it -- image-card grew 422px and the footer logo resized on all 63
pages. It is scoped to the rules that size by width.

Three pre-existing bugs surfaced, all inherited from the Gatsby source and all
previously silent:

- voice-and-tone.mdx's image had no alt at all, which astro:assets makes a
  build error. Gatsby shipped it to production. Alt text is taken from the
  caption directly beneath the image.
- Eight `width="800px"`-style attributes on elevation.astro, plus `width="100%"`
  on index.astro. `width` on `<img>` takes a bare number; browsers parsed the
  px leniently so rendering never changed, but `<Image>`'s types reject it.
  Confirmed in Chrome that stripping px renders identically. The percentage
  moved to home.css, where sizing belongs.
- header.mdx referenced ../images/jstor-logo.svg, a relative path that happened
  to resolve.

The @images alias is repointed from public/images to src/images. It had zero
references before this, so it was dead config.

41 unreferenced files stay in public/images/ -- 32 in logos/ alone. Nothing
links them; deleting assets is a separate decision.

Verified by a normalized dist/ diff masking asset URLs and the CSS hash: the 12
pages with images differ, the other 51 are byte-identical. Playwright geometry
across 14 routes plus an untouched control page shows 12 of 14 at identical
document height, the rest off by 1-2px of sub-pixel rounding. Two type
specimens on typography render 3-5px smaller because they now use the SVG's
authored size instead of being scaled ~4% by their container.

Note that loading="lazy" makes an unscrolled DOM query report below-the-fold
images as broken; measure after scrolling. One image on components/image-card
legitimately never loads -- it is the error-state card, which Pharos hides on
purpose, and it was 0x0 before this change too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Prettier has no built-in parser for `.astro`, so every file resolved to
`inferredParser: null` and prettier errored on it. The failure was silent in
the two ways that matter: `prettier --check` exits 0 despite the error, and
`yarn format` and pre-commit's `pretty-quick --staged` both skip the file. All
65 .astro files in the package have therefore been hand-formatted since the
port, which is why cosmetic slips kept surviving review.

Install prettier-plugin-astro and register it in .prettierrc.js. The parser now
resolves and 16 of 65 files needed reformatting -- mostly wrapping over-long
attribute lists onto their own lines.

Verified as a formatting-only change by diffing dist/ across all 63 pages
before and after. Two pages differed and both were investigated:

- getting-started had a real regression. Prettier moved a `<code>` element onto
  the line after a word, and Astro drops that newline entirely, rendering
  "by importing@ithaka/pharos/..." with no space. This is the most common
  porting bug in this package, reintroduced by the reformat. Fixed with an
  explicit `{' '}`, which is the only form prettier leaves stable -- breaking
  inside the tag (the usual fix here) gets reflowed straight back into the bug.
  A sweep for the same pattern across all 65 files found this one instance.
- brand-expressions/typography differed only in leading and trailing spaces
  inside two headings, which HTML collapses. Confirmed harmless: rendered text
  and document height are identical on both pages.

The remaining 61 pages are byte-identical.

Worth knowing: prettier does have a built-in MDX parser, and it is not safe on
this package's .mdx files -- it de-indents Markdown lists nested in JSX until
they stop parsing as lists, and reflows long single-line block tags until MDX
wraps them in a generated <p>. Those files stay in .prettierignore. Note the
ignore glob only covers src/pages/**, so the 32 pages moved to src/content/ in
the collection refactor are no longer protected; that is a live hazard and is
addressed separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The .prettierignore entry that keeps prettier away from this package's MDX
matched `src/pages/**/*.mdx`. The 32 component pages moved to `src/content/`
in the collection refactor and fell out of that glob, so `yarn format` and
pre-commit's `pretty-quick --staged` have been free to rewrite them since.

That is not theoretical. Running prettier over src/content/ today fails the
build outright: it rewrites MDX comment delimiters ({/* */} -> {/_ _/}),
which is invalid JSX, and acorn cannot parse coach-mark.mdx. Before that it
also collapses do/don't lists -- de-indenting Markdown bullets nested in JSX
until they stop parsing as a list and render as literal "-" text, which builds
cleanly and silently corrupts the page.

Widen the glob to `src/**/*.mdx` so it covers wherever MDX lives next.

{/* prettier-ignore */} is not an alternative, which is worth recording because
it is the obvious thing to reach for: prettier corrupts the marker's own
delimiters before it can take effect. Tried on button.mdx and sidenav.mdx, and
both were reformatted anyway.

Also fix the underlying cause for one of the two hazards. A Markdown list
written directly inside a JSX tag is treated as JSX prose, which is why
prettier reflows it; separating it with a blank line makes MDX parse it as
Markdown and prettier leaves it alone entirely. Added to all 62 such lists
across 29 files. Verified that this is what makes them stable by running
prettier over a converted file and confirming the list is untouched.

The remaining hazard -- long single-line block tags, which MDX wraps in a
generated <p> if prettier splits them -- has no source-level fix. 35 of the 39
are in src/pages/** and only 4 in src/content/, all WCAG link lists. Rewriting
them as Markdown lists produces a nested <ul>, so they stay as they are and
rely on the ignore.

All 63 pages build byte-identical to the previous commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The port deliberately reproduced several bugs from the Gatsby source to
keep visual parity. Now that parity is no longer gating, fix the ones a
reader can see.

- combobox never linked to Storybook. The Gatsby source spells the prop
  `istoryBookType`, so PageSection received no `storyBookType` and
  silently skipped the Example block -- the only one of 32 component
  pages missing its "See live code examples in Storybook" link. The
  story exists (`title: 'Forms/Combobox'`), so the generated URL
  resolves.
- design-tokens/overview showed raw Markdown. Two strings were escaped
  purely to match Gatsby rendering JSX text verbatim; the second put a
  full GitHub URL in the body copy. Both are real Markdown now.
- Four more unterminated `var(` declarations in that page's exported
  style objects. Confirmed harmless before changing -- the browser's
  error recovery closes the `var()` at end-of-input, and computed
  colours are byte-identical either way. Cosmetic only.
- Prose typos: "occured" on radio-button, two missing spaces after a
  period, and two compound adjectives broken by a spaced hyphen
  (`high - level`, `well - defined`). A spaced hyphen used as a dash
  elsewhere is correct and was left alone.

Left alone: `id="misson-text"` in the footer example. That exact id
ships from packages/pharos, so the demo shows real library usage;
changing it here would make the example diverge from what consumers get.

design-tokens/overview is 72px shorter: the link paragraph and the
first paragraph each drop a line now that the defects are gone. No
spacing rule changed.

Verified: normalized dist/ diff shows exactly 3 of 63 pages changed and
the other 60 byte-identical; Playwright confirms combobox gains its
Storybook link, radio-button and a button control page are unchanged in
height, and every swatch colour on overview is identical. Build and
lint clean (65 files, 0 errors, 0 warnings).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`preserveSymlinks: false` is Vite's default, so the setting had no
effect. Its comment also described the opposite of what the value does:
`false` resolves symlinks to their real path rather than preserving
them. The single-copy-of-Lit behaviour the comment claimed to secure
comes from the default, so removing the line changes nothing.

dist/ is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`subSectionLevel`, `lessMargin` and `topMargin` have no call sites.
Converting the prose pages to MDX moved sub-section headings to
markdown/Heading.astro, which maps `###`/`####`/`#####` itself, and
nothing has passed the three props since.

Removing them also removes the `isHeader && subSectionLevel` check,
which used console.error -- a build-time message that scrolls past
unnoticed -- to guard a combination that can no longer occur. Passing
the prop is now a type error instead. Both nested ternaries collapse,
and five unreachable `.section--*` rules leave components.css.

markdown.css derived its heading gaps from those rules by measuring
them, so the measurements are kept but reworded to state the values
directly rather than cite classes that no longer exist.

dist/ is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
38 files (5.2 MB) that nothing links, inherited by copying the Gatsby
site's static/ directory. Most are the brand-expressions logos set;
home-contribute.svg was unreferenced in the Gatsby source too, since
the homepage has three cards rather than four.

jstor-logo-inverse.svg is named by six files in packages/pharos, but
each imports that package's own copy under src/utils/_storybook/, so
none resolved here.

logo.svg is also unreferenced and was kept deliberately -- it is the
site's own logo and a plausible favicon asset, so removing it would be
a content decision rather than a cleanup.

dist/ drops exactly these 38 assets and is otherwise byte-identical
across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`gfm`, `smartypants` and `rehypePlugins` are deprecated on `markdown`
itself. The warning names two replacements and only one of them exists
here: `satteri()` is the default processor in a later release, but
`unified()` ships in the installed 7.2.1 and is the drop-in for the
remark/rehype pipeline this site already uses.

@astrojs/markdown-remark becomes a direct dependency so it can be
imported, pinned exactly -- `~7.2.1` resolves to 7.2.2 while
@astrojs/mdx stays on 7.2.1, and two copies of the Markdown processor
is the same split the cookie dependency already caused.

Verified by inverting smartypants rather than trusting an unchanged
build: `true` yields 217 curly apostrophes, `false` returns to the 3
authored literals. dist/ is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 43 `Ported from the Gatsby site's ...` headers documented where
each file came from, which git log already records.

32 were one-liners naming only a source file and are removed outright.
The other 11 carried real documentation -- BestPractices' slot API,
CrossOut's measuring script, home.css on why the @font-face blocks
moved -- so each keeps its content with the port reference dropped.
Several read purely as comparisons ("the React version did this in
useEffect"); those state the same thing directly instead.

Gatsby mentions that explain why non-obvious code exists are kept:
they are reasoning about this code, not a record of its origin.

Also fixes two prettier failures from earlier commits in this sequence,
which `yarn lint` does not catch because it does not run prettier.

dist/ is byte-identical across all 63 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brentswisher and others added 14 commits August 20, 2026 14:26
Most Gatsby mentions in src/ were archaeology: "Gatsby did X, we do Y".
Once packages/pharos-site is deleted those sentences lose their referent
and read as noise.

Comments that protected a real constraint were rewritten to state the
constraint directly rather than as a comparison — "the example is a plain
<div> so it stays block-level" instead of "the Gatsby page renders a plain
<div>". No knowledge is lost, only the dead reference.

Two mentions are kept deliberately:

- astro.config.mjs, the `cookie` hoist. The older CommonJS copy sits at the
  repo root because Gatsby is still installed, and the comment names the
  live cause of a build failure. It needs revisiting when the Gatsby
  package goes away.
- assertNavigation.ts, the orphan-page allowlist. The claim is checkable
  today against the sibling package and is the evidence that those pages
  being unlinked is not a port defect.

README.md and MODERNIZATION.md are untouched. Both are about the migration
while the two packages coexist; MODERNIZATION.md should be deleted whole
when it lands rather than edited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both were previously justified by a comment saying Gatsby did it. With
pixel parity retired, nothing justifies them, and the preceding commit
removed those comments — so they would otherwise read as ordinary typos.

font-weight: the lowercase specimen read "abcdefghijlkmnopqrstuvwxyz",
transposing jlk for jkl. A scrambled alphabet on the page whose purpose is
displaying letterforms.

grammar-and-style: the acronyms list was <ul><ul><li>, the outer list
holding no <li> of its own. It is invalid HTML — a <ul> may only contain
<li> — and the justification the old comment gave for it was wrong. It
claimed the outer list supplied indentation, but styles/layout.css resets
ul to margin/padding 0, so it contributes nothing; the indent comes from
`.main li { margin-left }` on the inner items. Confirmed in a browser:
removing the outer <ul> leaves the items at the same offset and the page
the same height. The sibling `actions` list already uses a single <ul>, so
this makes the two consistent.

Rendered output is unchanged apart from the corrected alphabet and the
dropped wrapper element.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page existed under content-style-guide but nothing linked to it, so it
was reachable only by direct URL. Add it to contentStyleGuidePages after
"Voice and tone" — terminology before the mechanical guides — and drop it
from unlinkedByDesign so the orphan check catches it if it is ever removed
from the nav again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
Three of the four color variables named custom properties that Pharos does
not declare: the ramps expose `--pharos-color-night-blue-base`,
`-glacier-blue-base` and `-living-coral-base`, but the page asked for them
without the `-base` suffix. Those var() calls had no value, so the spans
inherited black body text and the color coding never appeared. Only
`--pharos-color-jstor-red` is genuinely suffix-less and was working.

This came from the Gatsby source, which is worse off — overview.tsx:44
reads `'var(--pharos-color-night-blue'`, missing the closing paren on top
of the wrong name — so the coding has never rendered in either site.

Pointing them at the real tokens then exposed a contrast failure that was
invisible while the text was black: glacier-blue-base is 1.98:1 against
white and living-coral-base 3.43:1, both under the 4.5:1 WCAG AA needs for
body text. Use the darker steps already on each ramp — glacier-blue-30 at
4.80:1 and living-coral-50 at 5.18:1 — which keeps the hues recognizable.
Component (8.92:1) and category (12.51:1) already passed and are unchanged.

Verified in a browser against the built page: all four definition terms,
the naming-structure line and all sixteen example segments compute to
distinct colors at >= 4.5:1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
…only cue

WCAG 1.4.1 rules out colour as the sole means of conveying information, and
that is what the naming-structure diagram relied on: the only thing tying a
segment to its definition, and to the same segment in the examples, was its
hue. Even after moving to AA-passing tokens the four colours are two reds
and two blues — jstor-red against living-coral-50 differ by just 1.72:1 in
relative luminance, glacier-blue-30 against living-coral-50 by 1.08:1 — so
they separate by hue alone and collapse in monochrome.

Add a superscript numeral to each segment in all three places: the naming
structure, the definition terms, and the sixteen example segments. The
numeral is aria-hidden, since a screen reader gets the mapping from the
<dt> text and reading "pharos dash color two dash brand three" would be
noise.

Also closes the <span> around <component> on the structure line. It was
empty, with the text outside it, so that segment rendered uncoloured while
the other three were coloured — a defect inherited from the Gatsby source.

The rule lives in markdown.css rather than the page: `{` opens an
expression in MDX, so a <style> block's CSS body fails to parse, and no
other .mdx page carries one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
…list

The <dt>/<dd> pairs on the design-tokens overview are `display: inline` and
read as one sentence, with Pharos supplying the separator as a `": "` on
`dt::after`. The UA stylesheet's `margin-inline-start: 40px` on <dd> is
meant to indent a block-level description under its term; here it stacked
on top of that colon and opened a hole mid-line, of a width that varied
with the length of the preceding term, leaving the four rows visibly
ragged.

Zero it, and set the gap on `dt::after` — the trailing space in `": "`
collapses at the element boundary, so leaving the separation to it would
run the term straight into its description.

Both rules are scoped under `.md-body`, and this page is the only one on
the site that uses a <dl>.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
The page existed under design-tokens but nothing linked to it, so it was
reachable only by direct URL. Add it at the top of designTokenPages, where
it reads as the section's introduction ahead of the individual token
pages, and drop it from unlinkedByDesign so the orphan check catches it if
it is ever removed from the nav again.

With this and the JSTOR terms page linked, unlinkedByDesign holds only the
two pages that are genuinely never linked: `/` and `/404`.

Note that the sidenav label and the page heading differ — the label has to
slugify to `overview` to match the filename, while the page's frontmatter
title is "Design tokens". Renaming either is a content decision, so both
are left as they are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
The `pharos` Netlify site built the Gatsby package with its command and
publish directory set in the Netlify UI. Move both into netlify.toml and
point them at the astro package so the config is the source of truth.

`site-astro:build` runs `build:core` first, so build-ignore.sh watches
packages/pharos/ alongside packages/pharos-site-astro/ — a core-only
change now changes the site output and should trigger a deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBcimUyBn3zEXtZ7RmSfBu
#1396)

* docs(site-astro): migrate remaining component pages

* fix(site-astro): preserve component names after minification

* style(site-astro): fix lint spacing

* fix(site): remove name property setting, we are no longer mangling it on build so this isn't needed

* chore(site): Remove changeset

Not needed right now since this is going to another feature branch

---------

Co-authored-by: Brent Swisher <brent.swisher@ithaka.org>
Co-authored-by: Brent Swisher <brent@brentswisher.com>
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
packages/pharos/lib/index.js 1.05 MB (0%)

brentswisher and others added 2 commits August 31, 2026 15:23
The dist import will always be there, but is causing a timing issue in ci, and re-building just for the lint step seemed time intensive
@brentswisher

Copy link
Copy Markdown
Contributor Author

Doing some internal design reviews of the preview site, and should be able to officially open this shortly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants