Skip to content

feat: the Kestrel website (Next.js, captured from the real app, minimal redesign) - #1

Merged
3ncryptor merged 42 commits into
mainfrom
website
Sep 29, 2026
Merged

3ncryptor merged 42 commits into
mainfrom
website

Conversation

@3ncryptor

@3ncryptor 3ncryptor commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

What this adds

The Kestrel website in website/: a Next.js 16 static site (landing page and docs), built from the product itself.

The rule it follows: everything on it that looks like Kestrel is Kestrel. These are all captured from the app by bun run website:generate, and CI fails if they're stale:

  • the dashboard and the crash replay;
  • the quit dialog;
  • --help and the kestrel init output;
  • the key map;
  • the palette and the meter colours.

The landing page (minimal, modelled on AlgoLedger)

  • Hero: the whole first screen. "KESTREL" in meter cells, coloured by Kestrel's own meter bands, above its CPU graph drawn in the same cells; the graph scrolls once a second.
  • Stat strip: three checkable facts.
  • How it works: four tabs in one terminal (install, kestrel init, the live dashboard, the captured crash and its recovery), pinned and walked by scrolling on desktop.
  • Features, kestrel.json, Packages, Trust, FAQ, then a minimal footer.
  • Nav: the wordmark, then a Search pill and a Menu pill. The Menu is full screen.
  • Search: ⌘K or / opens DevClub's spotlight search, shown the way macOS shows Spotlight. It matches titles and section text, from a static /search.json.
  • Easter egg: press q for Kestrel's real quit dialog.
  • Type: Plus Jakarta Sans for text, Kestrel Mono for terminals, all self-hosted.

Docs

Guide, Configuration, Commands, Keys and Changelog, rendered at build time from the repository's own Markdown and captured CLI data.

Guarantees

  • No third-party requests. No analytics and no fonts or scripts from a CDN; the end-to-end tests fail on any request that leaves the site.
  • Security headers. A CSP (with 'unsafe-inline' for scripts, which is documented), HSTS and no framing.
  • Reduced motion is honoured everywhere, through one motion gate.
  • Accessibility. WCAG AA is checked with axe on every route in CI, and search, the menu and the tabs follow the ARIA patterns.

Test plan

  • bun run website:generate is deterministic, and the CI freshness check covers hero-quit.json.
  • In website/: lint, typecheck, unit tests (49) and the build pass locally.
  • CI: website job (generate freshness, lint, typecheck, tests, build, Playwright + axe) and the product jobs.
  • After merge: connect Vercel with Root Directory website and analytics off, then check the live headers and Lighthouse.

captureSpans() output → a palette-indexed, per-line delta format that the site replays;
round trips are exact and the gzip size is measurable against the budget.
The myapp stack on a developer's Mac: the api serves requests while its memory creeps up,
the worker crashes on a real-looking bug at tick 12, Kestrel retries it and it is ready
again by tick 18. A fixed clock and seeded data make every run identical.
bun run website:generate renders the real App headlessly (the bench-ui pattern), presses
Tab and v like a user, and captures 30 delta frames (6.8 KB gzipped) plus compact, wide
and NO_COLOR frames; it also writes the key bindings, real CLI output and the palette as
CSS variables. Runs in UTC so every machine produces identical files.
Tailwind 4 themed from the generated tokens.css, self-hosted monospace subsets with braille
and box drawing (Cascadia Mono and DejaVu, renamed as their licences require, 28 KB total),
security headers with a CSP that allows only this origin, the nav, footer and layout.
Exact dependency versions, no install scripts, no analytics.
npx @devclubnst/ui@2.0.1 add for code-block, toast, animated-button, glowing-badge, dither,
spotlight-card, bento-grid, animated-counter, scales, mac-switch, mac-slider, task-list,
file-tree, dotted-accordion, spotlight-search and reveal-sheet. They are our source now;
adapted to strict TypeScript and the lint rules (a keyboard-inaccessible click handler removed
from the accordion, a guarded uniform setter in dither). Their dependencies pinned exactly;
no network, storage, eval or HTML injection in any of them.
MacSwitch and MacSlider gain an xs size and a label (aria-label); the slider also takes
valueText, so it announces 'standard' rather than 1. AnimatedButton gets an AnimatedLink
twin, since a button inside a link is invalid. Dark variants follow the site's dark class.
Frame 1 is server-rendered; the 30 frames load as a separate chunk and replay at 1 Hz,
pausing off-screen, in a hidden tab and under reduced motion. Live, Colour (Kestrel's
NO_COLOR mode) and Width (compact, standard, wide) switch between real captures. The
window tilts towards the cursor, the headline rises word by word, and npm, npx and curl
install tabs copy with a toast.
The frame where the worker is retrying stays pinned while five steps light up CPU, memory,
the managed stack, logs and ports. The spotlight is positioned in terminal cells inside the
frame, so it lands on Kestrel's own boxes at any size; the steps are buttons without pin.
… numbers

htop and pm2 slide together into Kestrel. Each feature card holds a small working demo: core
meters, dependency-order start, a searchable logs panel, and the type-the-name confirmation
Kestrel uses for another user's process (it kills nothing). Counters roll up to the measured
figures.
The first run is typed out with Kestrel's real, captured output and ticked off in a task
list. The kestrel.json example explains each key on hover or focus and lights up its process
in the dependency graph, next to a project tree with .kestrel/logs.
Copyable verify commands for provenance and attestations, the platform matrix linking each
npm package, answers to the questions people ask, and a last install command.
Section extraction that fails when an expected heading disappears, links rewritten to site
routes or GitHub, heading anchors and a table of contents, wide tables that scroll, and
build-time Shiki highlighting in Kestrel's palette. Raw HTML in markdown is dropped; tests
prove script and event-handler markup never reach a page.
…ter labels

onSelect passes the whole item (so a result can navigate), filterLabels renames the four
filters, and autoFocus serves a search dialog.
/docs (the npm README from Install on), /docs/config (docs/CONFIG.md), /docs/commands
(the real kestrel --help), /docs/keys (the app's key map: press a key and its rows light
up) and /changelog as a timeline. A docs shell with an animated sidebar, an 'on this page'
list that follows your reading, copy buttons on code, prev/next and edit-on-GitHub.
Cmd-K or / searches every page, section, command and key (DevClub spotlight search in a
reveal sheet), ? lists the shortcuts, and g h / g d / g c move around. Keys are ignored
while typing; the nav has a search button too.
'kestrel: no such page (exit 2)' with the pages people usually want; titled and noindex.
The Open Graph image is drawn with next/og: the wordmark, the headline, the install
command and a CPU skyline in the app's meter colours.
The task list's live region was an li with role=status inside the ul (a non-item child);
it is now a p after the list. The accordion's index numbers meet contrast on dark.
…inline links

Small muted text used overlay0 (4.3:1 on black); it now uses k-muted (overlay1, 5.6:1), and
overlay0 is decoration only, as the plan said. Code that scrolls takes focus, one-line
commands wrap instead of scrolling, the tour's inactive steps keep their contrast, and the
inline security link is underlined.
Every route: one h1, no console errors, nothing loaded from another origin, and no serious
axe violations (the captured terminal frames are excluded: they are the product's own
output, aria-hidden with a text alternative). Plus the hero replay and reduced motion, the
colour and width controls, tabs and copy, Cmd-K search, the ? sheet, the keys page and 404.
website/ changes (and the documents the site renders) run only the website job, not the
product suite; UI, --help, version and capture-script changes run it as well. Pinned by
tests.
The job checks the committed captures are current (bun run website:generate + git diff),
then lints, type-checks, unit-tests, builds and runs the Playwright + axe suite. The font
script is shellchecked. vercel.json builds with Bun and skips deploys when nothing the site
depends on changed.
Copilot AI balanced review requested due to automatic review settings September 29, 2026 17:19

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

/** A link from a repository document, rewritten for the site: its own pages locally, other files on GitHub. */
export function rewriteHref(href: string, sourceFile: string): string {
if (href.startsWith('#') || /^(mailto|tel):/.test(href)) return href;
let target = href;
bun test treats bare arguments as substring filters, so tests/unit also matched
website/tests/unit, whose Vitest files need the site's own dependencies. ./tests/...
names the directories themselves.
Defence in depth from the security review: rewriteHref drops javascript:, vbscript: and
data: links outright instead of relying on them being rewritten into a GitHub URL.
From the code review: a '## Heading' on the last line with no newline lost its last
character (so excluding it by name stopped matching), and a document that begins with a
'## ' section dropped it. Both pinned by tests. Also uses import.meta.dirname in the
Vitest config and ignores the agent notes Next's dev server writes into website/.
The vendored github-activity generated random activity when it had no data.
It now draws only the days it is given, from pure, tested helpers
(src/lib/heatmap.ts): Sunday-first weeks, GitHub-style quarter levels,
month labels, and a text summary that is the grid's accessible name.
Kestrel's green scale; no spring or scale-up under reduced motion.
Added from DevClub UI: noise, gooey-nav, profile-menu, hook-sidebar,
proximity-sidebar, pixel-card, sparkle-button and orb. New pinned deps:
lenis 1.3.26, thinking-orbs 0.3.2, voice-glow 0.2.1.

Adaptations:
- profile-menu: no theme switcher or localStorage, no demo sections,
  remote avatar, email or links; a real button with aria-expanded and
  aria-haspopup, menu/menuitem roles, a plain-key shortcut (m) that
  ignores typing and never collides with Cmd/Ctrl+K
- gooey-nav: a named tab list (not a nav landmark), aria-controls,
  and arrow keys move focus with the selection
- spotlight-search: the combobox pattern (listbox, options,
  aria-activedescendant, a live result count), instant under reduced motion
- proximity-sidebar: dashes are the tab stops, arrow keys walk them,
  a visible focus ring
- sparkle-button, dither: reduced-motion guards; orb: display only
- pixel-card: not a tab stop itself; lights up when a control inside it
  is focused
- shadcn-style theme tokens (border, muted-foreground…) mapped to the
  Kestrel palette
- useMotionGate: one site-wide answer to how much motion a page may make
  (reduced motion, an effects switch, low-power devices); off on the
  server so hydration always matches
- SmoothScroll: Lenis on GSAP's ticker, synced to ScrollTrigger; dialogs
  scroll natively and a modal freezes the page behind it
- Scramble and LineReveal (ScrambleText, SplitText) for section titles and
  leads, once on entry; the final text is in the markup from the start
- Magnetic hover for a fine pointer; a scroll meter drawn as a Kestrel meter
- the search E2E targets the combobox by name

The full-page canvas grain is dropped (and the vendored noise with it): it
recomposited the whole page 12 times a second and made the E2E suite
time out under parallel load. Without it the suite is stable and twice
as fast.
Self-hosted from the pinned, checksummed 2.7.1 release (SIL OFL, no
Reserved Font Name): the variable font with every weight, subset to Latin,
23 KB, preloaded with the HTML. Kestrel Mono stays for terminal frames.

The font build is now reproducible: the rename step no longer stamps the
build time into each font, so rebuilding gives byte-identical files (the
mono fonts change once, by that timestamp only).
The hero is now one signature visual: KESTREL set in a 43×9 grid of
meter cells, coloured by Kestrel's own meter bands (a unit test checks
them against ui/logic/charts.js). Pure SVG and CSS: it fills in column
by column from the first paint, a few empty cells flicker, the pointer
leaves a trail; no JavaScript, still under reduced motion.

Below it: one line, the install command, a white pill and a ghost pill,
then the live dashboard. A quiet radial glow replaces the WebGL light
field (dither, ogl, HeroBackdrop and RiseText removed).
Following AlgoLedger's structure:
- the hero is the first screen and only the name: KESTREL in meter
  cells above Kestrel's CPU history graph drawn in the same cells,
  coloured by height like the product's graph (a unit test checks both
  colour rules against ui/) and scrolling once a second; KES / TREL on
  phones; a caption and a scroll cue
- a stat strip of three checkable facts (the measured ~3% CPU, 1 app,
  0 install scripts)
- how it works: four tabs in one terminal, pinned and walked by scroll
  on desktop: install, kestrel init (captured output), the live
  dashboard, and the captured crash and recovery with 'Crash it again'

Removed what this replaces: the hero player and its colour and width
controls, the quick start (which showed npm output that was never
captured), the dashboard tour, and the task-list, mac-switch and
mac-slider components. The terminal window is flat; the scenario's frame
numbers live with the frame data (server-safe).
Replaces the vendored spotlight search (a full-width sheet, a serif
title, filter bubbles for its demo's Apps/Folders/Workspaces, results
clipped by the sheet, titles only) with a centred, keyboard-first
palette: a › prompt, results grouped as Docs / Configuration /
Commands / Keys / Changelog, the best group first, and a ↑↓ ↵ esc
footer. The combobox pattern, as before.

Sections now carry the start of their text, so a query finds what a
section says: 'crash' finds Restarts. The index is a static
/search.json fetched when the palette first opens, instead of being
embedded in every page. The ? shortcuts list uses the same panel;
reveal-sheet and spotlight-search are gone.
- the footer is the closing screen: 'One command, then kestrel.' with
  the install command and pills, link columns, and the name once more in
  the hero's meter cells, dimmed until the pointer passes over it; it
  replaces the separate closing CTA
- section headers follow the new language: centred, a pill bar, a
  letter-spaced eyebrow, a bold title
- removed Why one app, and the Numbers grid (the stat strip under the
  hero states the same figures); the platforms section is 'Packages'
- MergeCards, Numbers, ClosingCta, animated-counter and animated-button
  are gone
… search)

The DevClub spotlight search is back, used the way macOS does it: a
floating bar over a lightly blurred page (no sheet, no title box), the
filter bubbles parting from the bar as it opens, results below in the
same glass. Adapted: the filters are the site's own groups (Docs,
Config, Commands, Keys), ranking comes from lib/search.ts (so 'crash'
still finds Restarts), the geometry scales with width and height, and
narrow screens keep the full-width bar without bubbles.
The name, the tagline and the one install command (with copy), three
short link columns, and a single mono line: year, licence, version, no
telemetry, back to top. The big headline and the giant meter-cell mark
are gone.
The nav is the name on the left and two pills on the right: Search (⌘K)
and Menu. Menu opens the site's few places over the whole screen, big and
numbered (Home, How it works, Features, Docs, Changelog), with the
project links, the install command and Star on GitHub beside them. A
Radix dialog: focus stays inside, Esc and Close return.
…used

- press q anywhere (not while typing, not on the keys page) and
  Kestrel's own quit dialog appears: captured by the generator, which
  now presses q with the stack running (hero-quit.json). y 'quits' the
  page for a moment, Esc cancels
- removed vendored components nothing uses any more (code-block,
  gooey-nav, hook-sidebar, orb, pixel-card, profile-menu,
  proximity-sidebar, scales, sparkle-button, toast, button) and their
  dependencies (thinking-orbs, voice-glow, @radix-ui/react-icons,
  @radix-ui/react-slot, class-variance-authority, next-themes)
- github-activity and lib/heatmap stay for the Built in the open
  section, which waits until the repository has a few weeks of history
@3ncryptor 3ncryptor changed the title feat: the Kestrel website (Next.js, real captured dashboard, docs) feat: the Kestrel website (Next.js, captured from the real app, minimal redesign) Sep 29, 2026
The website job is removed: CI covers the package, and Vercel builds the
site. A change under website/ now runs no job (only the final CI result
check), like a docs change. The change detector drops its website output,
and shellcheck no longer covers website/scripts.
@3ncryptor
3ncryptor merged commit 5af7020 into main Sep 29, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants