Skip to content

Restyle the architecture docs site and animate the sidebar - #9527

Open
kube wants to merge 24 commits into
mainfrom
claude/petrinaut-arch-docs-styling-554323
Open

Restyle the architecture docs site and animate the sidebar#9527
kube wants to merge 24 commits into
mainfrom
claude/petrinaut-arch-docs-styling-554323

Conversation

@kube

@kube kube commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Important

Appearance, plus the diagram renderer. No change to the architecture model or to page content, beyond two overlapping edge labels shortened on one diagram.

Summary

A new look for the architecture docs: Swiss, monochrome, and much quieter. One neutral ramp replaces Starlight's blues and greys, the chrome drops its dividers and steps back until it is pointed at, and everything that moves — the sidebar collapse, the resize, the bands under the header — now moves on one curve. Diagrams follow the page theme instead of staying white in the dark.

Before

9527-before.mp4

After

9527-after.mp4

Links

Changes

Appearance

  • One neutral ramp across both themes
  • No rules drawn in the chrome except the sidebar's depth guide, which carries information
  • Controls read by fill rather than outline: the search field, the diff-build compare chips
  • Accent tokens point at the strongest foreground, taking the blue out of prose links, the active nav row and the active table-of-contents row
  • Body text in Inter, 17px on a 1.65 leading, with a shorter heading scale
  • Blocks 1.5rem apart, 2.25em above a section heading
  • Header drops its social links and the rule that divided them
  • Theme select's focus ring restated in the palette
  • Sidebar and table-of-contents rows are pills; code frames rounded and flat
  • Rails rest at 70% and the collapse toggle at 50%, reaching full strength over 300ms, off under prefers-reduced-transparency
  • A blur-and-tint band under the header and a mirrored one at the foot of the nav, both grown from scroll, so an unscrolled page has nothing laid over it

Sidebar motion

  • Collapse animates the pane, the content column, the handle and both panels on one curve
  • Content inset derives from the pane width, replacing a jump to an unrelated 2rem
  • Pane hides with visibility, so its rows travel with it and still leave the tab order at rest
  • All of it behind prefers-reduced-motion

Resize handle

  • A 2px mark fades in on hover inside the unchanged 6px hit area, and stays lit for the whole drag
  • A gesture marks itself and the transitions stop applying, so the pane tracks the pointer rather than trailing it
  • Width applies once per frame and persists once per gesture, for a held arrow key as well as a drag

Diagrams

  • Recoloured through custom properties, so they follow the page's light and dark themes
  • Set in Inter, and drawn with 16px of padding rather than D2's default 100
  • Inlined into the page at build time, which is what lets page CSS and the tooltips reach them
  • Two overlapping edge labels shortened on the GPU batch lifecycle diagram

Test coverage

  • withThemeableColours unit tests

    Scoping to D2's own class, the drawn colour as every fallback, markup without a scope left alone, and the mask channels left unthemed — theming fill="white" inverts D2's label knockout and erases every connection.

  • Contrast measured on the page, both themes

    The chrome rests behind an opacity, so what a reader sees is composited: worst row of 107 sidebar links, 6 table-of-contents rows, the heading, the toggle and the theme select.

  • lint:tsc and the production build
  • Scripted DOM probes

    One toggle and one drag; the scroll-driven bands; the keyboard gesture's single storage write; diagram theming and badges across both themes.

How to test

  • Open Petrinaut arch docs preview on Vercel
  • Toggle the sidebar from the control left of the logo
  • Expect pane, content column and on-this-page panel to move together, no jump
  • Hover the pane's trailing edge, then drag it
  • Expect a thin mark to fade in, the width to follow the pointer with no lag, and the mark to stay lit outside the strip
  • Focus that edge, then hold ArrowLeft and ArrowRight
  • Scroll the page, and scroll the nav
  • Expect a band to grow under the header and at the foot of the nav, and nothing over an unscrolled page
  • Header > theme select > Light
  • Expect the same monochrome palette inverted, diagrams included

@kube kube self-assigned this Sep 3, 2026
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 5, 2026 2:43am UTC
petrinaut Ready Ready Preview Sep 5, 2026 2:43am UTC
petrinaut-docs Ready Ready Preview Sep 5, 2026 2:43am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 5, 2026 2:43am UTC

Request Review

kube added 2 commits September 4, 2026 00:27
Restate Starlight's palette and type scale as one flat monochrome
system, so the chrome reads as a single surface divided by hairlines
and colour is left to the things that carry meaning.

Collapsing the left panel now animates: the pane hides with
visibility rather than display, the content inset derives from the
pane width, and every box sized from it transitions on one curve.
A drag or a held arrow key already tracks its input, so the handle
marks the gesture and the transitions stand aside.
The borders framing the header, the sidebar and the on-this-page
column are gone, leaving the sidebar's depth guide as the only rule
drawn in the chrome, where it says how deep a row sits in the tree.

The search field and the compare chips now read by fill rather than
by an outline, one step off the page background in either theme.
Both rails and the collapse toggle sit at 70% until the pointer or
keyboard focus reaches them, over 300ms, with the whole rail as the
hover target. prefers-reduced-transparency turns the effect off.

A 30px band under the header blurs and tints the content passing
beneath it. backdrop-filter cannot ramp on its own, so the ramp is
three stacked layers of increasing radius, each masked to a shorter
band, and each filtering what the one beneath it produced.

Blocks now sit 1.5rem apart with 2.25em above a section heading, and
body copy is 17px: Starlight defines --sl-text-body but never applies
it, so the size had to be set here.
The band ran both to 30px, which hid the blur: its strongest layers
sit at the top, which is exactly where the tint is opaque. The tint
keeps its 30px and the blur now runs to 60px, so below the tint there
is a stretch where content is blurred and still visible.

Radii go up with it, 4px to 16px.
The three layers now differ by height rather than by mask alone. All
are anchored under the header and run to a different depth: 60px at
1px of blur, 40px at 2px, 20px at 4px. They overlap towards the
header and thin out to one at the bottom, so the tallest and weakest
is the layer that meets unblurred content.

Each fades out over its own lower half, so a layer ending reads as a
soft edge rather than a line across the page.
The band's construction moves to a shared class so the rail and the
content column build it from one set of rules.

The rail scrolls independently of the page, so its band keys off the
pane's own scrollTop and appears only once rows have gone up behind
the header. Shown unconditionally it would sit on the first row of an
unscrolled rail and dim it for nothing.
@vercel
vercel Bot temporarily deployed to Preview – petrinaut September 4, 2026 00:08 Inactive
Height and opacity both come off `--pnd-nav-progress`, which the
header script writes from the rail's scrollTop, 0 to 1 over the first
60px. At rest the band has no height, so it stays off the first row of
an unscrolled rail. Nothing eases it: the value already tracks the
scroll frame by frame, and a transition would leave the band lagging
the rows it covers.

The ramp is now two layers rather than three, 60px at 1px of blur and
30px at 2px, in both places the band is used.
Both of them: the one above duplicated the header's, which is already
gone, and the one below separated the bar from content scrolling
beneath it, which the fade band now does by starting below the bar.

The dropdown and the section name beside it also sat too close.
@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team labels Sep 5, 2026
Starlight pulls that bar up by one to sit on the header's border, so
it ends a pixel above where the two heights put it. The bar's own
bottom border used to cover the difference; with that gone a sliver of
unblurred content showed through.
Two authored diagrams colour a node for something other than a layer
family: the device a batch runs on, and a case that falls back. Those
two pairs were outside the themeable set, so in dark mode they kept a
pale fill and took the inverted label colour on top of it.

`d2` knocks its connections out from behind their labels with a mask
built from a white rect that shows everything and black rects over
each label. There white and black are not colours but show and hide,
so theming them inverts the mask and every masked connection vanishes
apart from the stubs behind its own labels. They are now left alone,
and n7 is the page background rather than transparent, since it is
also the paper those knockouts are drawn on.
`d2` draws the badge outside its palette, so it stayed a white disc
with a near-black glyph whatever the host did.

Matched on a pair of attributes rather than on `white` alone, and that
pair is the point. The mask that knocks connections out from behind
their labels is built from a white rect that shows everything and
black rects over each label, where the two are not colours but show
and hide. A mask holds only rects and none of them carry a stroke, so
a path carrying both a fill and the badge's stroke cannot be one:
across every diagram that is 623 discs and no mask element.
`cached backend + calibrations` and `release` sat on adjacent parallel
edges between the same pair of nodes, and the long one ran under the
short one by 44px.

`d2` accepts a `near` on a connection but the elk layout ignores it,
so the fix is in the text: the edge runs from the cache, which makes
`cached` redundant, and `release (evict?)` now matches the wording on
the node it points at.
@cursor

cursor Bot commented Sep 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large client-side chrome and markdown build changes affect every docs page and diagram render path, though scope stays limited to the internal docs app and arch-docs bundle output.

Overview
Restyles apps/petrinaut-docs on a shared neutral ramp: monochrome Starlight tokens, Inter body text, pill nav/TOC, fill-based search and diff chips, idle rail opacity, scroll-driven header/nav fade bands, and animated sidebar collapse (visibility + max(sidebar-width, 2rem) inset) with resize gestures throttled via requestAnimationFrame and data-pnd-resizing. Header GitHub social links are removed; README documents the three-part chrome.css system.

Fixes diagram theming end-to-end: @local/petrinaut-arch-docs renders D2 with vendored Inter, tighter padding, and post-processes SVGs via withThemeableColours (--pnd-diagram-* fallbacks, mask-safe badge rules, vitest coverage). The docs site adds rehypeInlineDiagrams (plus @astrojs/markdown-remark) so bundle diagram images become inline SVGs and pick up dark overrides in chrome.css; framed diagram borders are dropped for inlined drawings.

Reviewed by Cursor Bugbot for commit dbeef3e. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved moderate issues affect accessibility, persistence behavior, content scope, and SVG processing coverage.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Restyles the Petrinaut architecture docs, animates sidebar behavior, adds Inter fonts, and enables theme-aware inline diagrams.

Changes:

  • Introduces monochrome styling, updated typography, fades, and responsive animations.
  • Improves sidebar resizing, persistence, and scroll effects.
  • Adds themeable SVG diagrams and supporting documentation.
File summaries
File Description
yarn.lock Resolves new Markdown and HAST dependencies.
libs/@local/petrinaut-arch-docs/src/emit/d2.ts Adds themeable SVG processing. Moderate: Add focused tests for scoped-rule insertion and unchanged output without a D2 scope.
libs/@local/petrinaut-arch-docs/src/cli.ts Supplies the font path during D2 rendering.
libs/@local/petrinaut-arch-docs/README.md Documents diagram theming. Nit: Use an opaque host-background value for n7 to avoid lines crossing labels.
libs/@local/petrinaut-arch-docs/fonts/OFL.txt Adds the Inter font license.
libs/@local/petrinaut-arch-docs/content/diagrams/gpu-batch-lifecycle.d2 Moderate: Revert the semantic label edits or document and validate them as content changes.
apps/petrinaut-docs/src/styles/chrome.css Implements the redesign and animations. Moderate: Raise idle opacity values to meet control and text contrast requirements.
apps/petrinaut-docs/src/plugins/inline-diagrams.mjs Inlines generated SVG diagrams.
apps/petrinaut-docs/src/components/SiteTitle.astro Adds fades and revised resize behavior. Moderate: Persist keyboard-resized width once on keyup or blur, not on every repeated keydown.
apps/petrinaut-docs/src/components/Head.astro Emits and preloads Inter.
apps/petrinaut-docs/README.md Documents styling, motion, diagrams, and fonts.
apps/petrinaut-docs/package.json Adds Markdown-processing dependencies.
apps/petrinaut-docs/astro.config.mjs Registers diagram inlining and Inter.
Review details
  • Files reviewed: 12/15 changed files
  • Comments generated: 5
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/petrinaut-docs/src/components/SiteTitle.astro Outdated
Comment thread apps/petrinaut-docs/src/styles/chrome.css
Comment thread libs/@local/petrinaut-arch-docs/src/emit/d2.ts Outdated
Comment thread libs/@local/petrinaut-arch-docs/README.md Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7c60f8a. Configure here.

Comment thread apps/petrinaut-docs/src/plugins/inline-diagrams.mjs
kube added 3 commits September 5, 2026 04:29
A held arrow key repeats about thirty times a second and each repeat wrote
to localStorage, which writes synchronously. The width a keyboard gesture
reaches is now kept in hand and written when the gesture ends on keyup or
blur, the same as a drag already did.
The rails and the header controls rest behind an opacity, so what a reader
sees is their colour composited against the page, not the colour written
in the stylesheet. Starlight's gray-3 arrives at 3.6:1 that way where body
text owes 4.5:1, and at 50% the toggle's icon is 2.3:1 against the 3:1 a
control owes.

The fade is kept exactly as designed and the colour underneath it is
raised instead, to the greys whose composite clears each threshold. The
theme select moves to the rails' opacity: it is the one header control
carrying a visible label, and at 50% over a white page no colour reaches
4.5:1, pure black included.

Measured on the page rather than computed from the tokens: worst of 107
sidebar rows and 6 table-of-contents rows, both themes, 4.93:1 dark and
5.09:1 light for text, 3.68:1 and 3.19:1 for the icon.
The theming hook matches a format d2 owns: the scope class it puts on a
drawing, and the colours it wrote. A change to either would stop every
host override silently, with the diagrams still rendering in the palette
they were drawn in. Tests cover the scoping, the drawn colour standing as
each fallback, markup without a scope passing through, and the mask
channels being left alone, which is the failure that erased every
connection line the first time.

The README offered n7 as transparent. It is the drawing's paper and also
fills the rects that knock connections out from behind their labels, so a
transparent one leaves every label struck through.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

2 participants