feat(docs): live-demo hero panel via @wavehouse/sdk - #290
Conversation
Replace the static terminal mock + wave-mesh animation in the landing hero with a live demo panel: real star/fork counts and a curated live GitHub-activity feed for Wave-RF/WaveHouse, queried in the visitor's browser from the public read-only role on stats.wavehouse.dev — counts via pipe(), an events-over-7-days aggregation via the structured query builder, and the feed via liveQuery() (backfill + SSE). The docs site becomes a real @wavehouse/sdk consumer (workspace dep; check-docs and dev-docs now build the SDK first). The panel renders a layout-stable skeleton without JS and degrades to it if the demo instance is unreachable. A live stargazer chip joins the closer band, fed by the same fetch. screenshot.mjs switches networkidle → load (the SSE stream never goes idle) and waits on the visual column's entrance instead of the deleted terminal lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more pre-existing timing knife-edges surfaced by the runner migration (#283), both test-only: - query.test.ts max_execution_time_ms: a zero-row query on a tiny table can complete sub-millisecond and beat the 1ms deadline before cancellation is ever observed, so a single attempt is a coin flip. The enforced property is existential — a 1ms budget must produce deadline 500s — so retry via waitForCondition (unique event_id per attempt, never cache-served) and pass on the first deadline error. Broken enforcement still fails: every attempt succeeds and the wait times out. - batching.test.ts 500-item flush test: the ack rides 500 sequential JetStream publish-fsyncs (5104ms observed on APFS vs a 5000ms bound), and when the ack outlasts the 5s linger the timer fires mid-publish, splits the batch, and the buffer never holds 500 rows — the size-trigger property is unobservable in that regime, not merely slow (review finding). Now: 15s sanity bound on the ack; strict <5s flush assertion only when the publish beat the linger with margin (ack <4s); integrity-only verification otherwise; 45s explicit test timeout so the worst honest path clears the 30s default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nats-server's Start() installs a process-wide SIGINT handler unless
NoSigs is set; its handler races main's graceful shutdown — either
os.Exit(0)ing past run()'s deferred cleanup, or double-Shutdown
panicking ("close of nil channel", exit 2), which also skips the Go
coverage flush and zeroes the e2e suite's coverage gate. WaveHouse owns
the lifecycle and already stops the server via EmbeddedNATS.Close().
Also backfills the CHANGELOG entry for the live-demo hero panel
(previous commit on this branch).
Closes #287.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dening, docs) Code review: NoSigs on testutil.NewJetStream's twin options literal (#287 sweep); anchor the batching size-trigger proof to startTime — measured from apiEndTime, a broken trigger flushing on the first-row linger would still pass (supersedes the #283 version of the bound); LiveDemo drops the action prefilter (liveQuery applies filters to raw producer rows client-side — VERBS stays the arbiter, backfill margin 24→48) and normalizes zone-less producer timestamps on live SSE rows. Docs review: sdk.md stops promising '7d' durations (#285), quote all ten zsh-hostile ?table= curls, Getting Started warns about the 60s schema-refresh 404 window (+ admin-only tags on /v1/schema/refresh mentions), development.md documents the docs→SDK build prereq, e2e file lists gain ndjson. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 10s SIGINT-to-kill budget was calibrated when the embedded NATS signal handler os.Exit(0)'d the process early (#287). A real graceful exit with no OTel collector running — the e2e default — serializes the traces/metrics/logs exporter shutdowns at ~5s of gRPC dial backoff apiece and lands ~15s (#288), so the orchestrator was SIGKILLing the binary before the Go coverage flush, zeroing the e2e coverage gate. Fast exits are unaffected: the wait selects on process exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, api.md admin docs) Code: normalize every rowKey part across the two row sources (backfill is CH-materialized RFC3339/defaults, live SSE is raw producer payload — raw interpolation could double-render a star landing in the backfill window); scope the prefilter comment + CHANGELOG claim to the backfill leg (the SSE leg filters in-browser); byline now names all three SDK surfaces honestly. Docs: api.md schema/DLQ sections get the admin-only callout + 401/403 (and 500 for refresh) error rows the rest of the reference has; time_range row mirrors the #285 duration caveat; sdk.md links #285 per file convention; getting-started names the 60s worst case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oc truth) The closer-band chip had the same [hidden]-vs-author-display bug the feed empty-state already guarded: display:inline-flex beat the UA [hidden] rule, flashing the "★ —" placeholder at exactly the no-JS / demo-down visitors the skeleton protects (verified by computed style both ways now, not attribute presence). EmbeddedNATS.Close() and the testutil cleanup gain WaitForShutdown() — owning the lifecycle (#287) means waiting it out. ast.go's Until comment catches up with what resolveTimeValue accepts. Docs: sdk.md stops presenting wh.dlq.stream() as working (no server-side DLQ stream exists — caveated against #197 in all three spots); the admin-only sweep reaches /v1/dlq/stats (callout + error rows), wh.dlq (role note), and the SDK README codegen section (--auth note). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… notes) sdk.md says since AND until take relative durations (matching api.md and resolveTimeValue); api.md states relative values mean that long ago and that until without since is silently ignored; the README and deployment quickstarts get the same 60s schema-refresh 404 note getting-started gained; query-builder's DEFAULT_LIMIT comment stops claiming it matches backend DefaultMaxRows (1000 vs 10000 — it is a deliberately tighter client default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…curl that can 404 api.md's structured-query limit row now states the DefaultMaxRows clamp (omitted or >10,000 → 10,000, policy max_rows lowers further), matching sdk.md. The README quickstart's schema-refresh 404 note moves below the ingest command — /v1/stream accepts any table value and can never produce it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
api.md and architecture.md claimed the DLQ payload is the inner data object; sendToDLQ (worker.go:502) republishes the full EventMessage envelope with the failure context in X-DLQ-* headers — an operator parsing per the old docs would mis-read every message. Also drops architecture.md's phantom jsInput/dlqOutput identifiers (nothing in the repo has those names) in favor of the real shape + a pointer to the Ingest Pipeline page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR enables NoSigs for embedded NATS and waits for server shutdown; adds a LiveDemo landing-hero with SSE-backed feed, counts, and styling; standardizes docs (curl examples, admin-only notes, DLQ envelope, duration formats); updates docs build/screenshot tooling; and hardens e2e tests for timing variability. ChangesEmbedded NATS Shutdown Lifecycle Fix
Interactive Live Demo Feature
API Contract & Documentation Clarifications
E2E Test Flakiness Fixes
🎯 3 (Moderate) | ⏱️ ~25 minutes Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
|
📚 Docs preview is live: https://eb2c5361-wavehouse-docs.wave-rf.workers.dev Live on |
…ctivity_recent) Builds on the WaveHouse-Stats follow-ups now merged (Stats#18/#19): every headline number comes from one cached gh_summary call (incl. reconciler-observed open_issues/open_prs, live for any future [data-wh-live] placement), and the feed backfills from the server-curated, server-deduped, cached gh_activity_recent pipe — the SSE tail connects first so nothing lands between backfill and stream (composite-key dedup absorbs the overlap). The pre-pipe reads stay as runtime fallbacks: a stats-stack rollback degrades the panel to slower, not blank. Per-visitor load drops from 3 uncached reads + SSE to 2 cached, singleflight-collapsed pipe hits + SSE. Verified all three paths headless: pipes live (exactly 1+1 pipe calls, 0 legacy reads, 6 rows, stream live), new pipes blocked (legacy fallback renders), full blackout (layout-stable skeleton, chip computed-hidden). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it needs The only admin-endpoint example without a token — 403s as written against every documented setup (trial policy default_role is public). Adds the bearer header + a pointer to the JWT-generation section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-out /v1/dlq/stats error table gains its real 500 (stream info failed, dlq.go:43) + the pre-failure 200 empty shape; the JWT-testing section says where change-me-in-production comes from and that the compose quickstart ships no secret (set WH_AUTH_JWT_SECRET first); four ingest-pipeline.md spots stop asserting an SSE/WS fan-out — no WebSocket transport exists anywhere in the tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) A secretless deployment actually accepts admin tokens forged with the empty-string HMAC key (#291, verified against golang-jwt v5.3.1) — so that parenthetical was a false safety claim. Removed the clause I'd added this round; the operational guidance (set WH_AUTH_JWT_SECRET on the compose service) stays. The pre-existing instances of the same claim in five other docs files, and the code-level fix, are tracked in #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hero's live panel had a hard ~325px min-width floor and the hero's overflow:hidden chopped its right edge on any screen narrower than ~365px: on a 360px phone the stream "live" pill, the EVENTS·7d stat, every feed timestamp, and "Full demo ↗" were all sliced off. Root cause was a min-content chain: .wh-hero__visual is a flex container (child min-width defaults to auto) and .wh-live__stats was repeat(3, 1fr) (tracks floored at min-content), so the three stat columns set a floor the panel couldn't shrink below — and the mobile hero track was a bare 1fr (also min-content-floored). Fix breaks the chain at every level: minmax(0,1fr) on the mobile hero track and the stat grid, min-width:0 on the visual wrapper and each stat. Labels get nowrap+ellipsis (single-line) and the mobile breakpoint tightens the number/label/padding; the feed-row title now ellipsis-truncates instead of being dropped. Verified headless: 320/360/375/390 × dark/light all clean (zero document overflow, panel + stream label + star button + Full demo + stats + timestamps all within viewport, no truncated labels), and desktop/wide 1024–2560 unchanged (panel holds 480px). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…me, layout, chrome) A measured audit (every page, both themes, via a screenshot+computed- style harness and parallel auditors) found the docs reading as a polished landing bolted onto stock Starlight. This unifies the system: - Radius: ~11 ad-hoc values → one scale (4/8/12/16/999px). All buttons 8px, all cards/panels/code-frames/tables 12px (EC borderRadius set so frame + inner share one value, no 1px lip). - Buttons: the same CTA rendered three ways — the closer-band pills are now 8px rectangles matching the hero; "View on GitHub" renders its ↗ (Starlight parses icon:external into an object, so the string compare never matched); stat strip no longer fake-lifts; dead .action.* gone. - Light theme: new --wh-accent-text role (AA in light, was 2.6:1); light status-hue overlays; page bg stepped darker so white cards read as layers (was ~1.04:1); ink-subtle/terracotta lifted to clear 4.5:1; focus ring AA-safe and no longer squares corners. - Layout: content widens to 58rem at ≥100rem (TOC-safe), 50rem on laptops; prose held to ~46rem; .main-frame caps+centers on ultrawide (kills ~590px stranded dead space at 2560); fixed-layout reference tables can't overflow. Zero overflow across 14 pages × 8 widths × 2 themes. - Grid bg: full-bleed + visible in light + faint page texture (was a shrinking center blob, hero-only, invisible in light). - Starlight chrome branded: pagination, tables, blockquotes, search trigger, sidebar group eyebrows + hover + current item. Dead data-section accent system and unused grid/orb/glass utilities removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ess-control + configuration The docs gate found the masking framing for #291 still shipping in two files this PR hadn't touched: access-control.md called a secretless deployment "a pure public deployment" and configuration.md said "no token can validate" — both contradicted by internal/auth (an empty-string-HMAC token validates and can carry role:admin). Pure deletions of the false guarantee, matching the api.md edit earlier in this PR; the legitimate tokenless-quickstart wording in getting-started.md is left intact. The underlying code fix (fail-closed when no verifier is configured) remains tracked in #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elevate hero CTAs The grid/glow lived inside .wh-hero, whose overflow:hidden clipped them to a centered ~1080px box — flat margins on wide screens, a hard rectangular edge, the glow sliced into an abrupt colored corner, nothing under the navbar or below the hero. Moved them to two page-level fixed layers scoped via body:has(.wh-hero): a continuous full-viewport signal-field grid (teal-tinted, soft vignette, edge-to-edge under the glass navbar and down the whole page) + a soft breathing teal hero glow with pure radial falloff (no clipped edges). Genuinely full-bleed, top-anchored, continuous, both themes. Hero CTAs: the pnpm-add hint is now a click-to-copy command chip (copy→ check feedback, clipboard API + selection fallback), and a live "★ N stars" chip joins the meta — fed by the same demo pipe() data as the panel, so the hero carries live social proof. Removed the hero's clip + orb elements and the now-dead orb keyframes/ light rules. No overflow 320–2560 × both themes; grid is landing-only (scoped); breathe respects reduced-motion. Demo wow-factor metrics (ingest→live latency, events/min sparkline) filed as Stats#33/#34. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-metrics The re-architected page-level grid/glow (fc8685b) were structurally correct but invisible: they sit at negative z-index, and Starlight's LAYERED opaque `body { background }` (@layer starlight.reset) painted over them. Deploying as-is would have shown no grid at all. Fix: set the landing `body` transparent (scoped via :has(.wh-hero)) so html's base color is the canvas and the grid/glow paint above it. Grid is now visibly full-bleed, under the navbar, continuous — both themes. Wire in the live "wow" metrics now that Stats#33/#34 shipped: an "⚡ ingested → live in ~N ms" latency readout (browser computes Date.now() − received_timestamp per SSE event, clamped for skew), a live events/min sparkline (new gh_events_per_minute pipe), and a lifetime "N tracked" total (gh_summary.total_events). All degrade to placeholders when the stream is quiet or a pipe is unavailable. Implemented + verified by subagent: grid visible both themes, no overflow 320–2560, astro check clean, 0 console errors in the prod build, reduced-motion respected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ush review) The gh_events_per_minute pipe's row order is a cross-repo contract; drawEpm (plots left→right) and bumpEpm (treats last as current minute) both assume ascending. Sort in loadEpm so a descending pipe response can't silently reverse the sparkline / misfire the live bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # docs/src/content/docs/api.md # docs/src/content/docs/sdk.md # internal/query/ast.go
Merging origin/main (#292) closed #285 — `7d`/`2w` now resolve to hours in the structured-query builder, so two rationale comments that justified "168h, not 7d, because #285" are now false: - LiveDemo.astro:259 — fallback comment's `("168h" not "7d" — see #285.)` - CHANGELOG.md — the live-demo entry's `(168h, since day-suffixed durations are #285)` The `168h` literal in the fallback query is unchanged (an explicit hours value is still perfectly valid); only the obsolete justification is removed. Flagged by the pre-push code review after the merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # tests/e2e/sdk/query.test.ts
…agram zoom Addresses the live-demo PR feedback round (diagrams + preview comment): - Mermaid node labels clipped their last glyph on the live preview (`Buffer Consumer` -> `Buffer Consume`): the build-time Chromium measured each box at the default font-weight 400 while global.css displays labels at weight 500, so every box was ~1px too narrow. Fixed upstream in astro-themed-mermaid via a new `measurementCss` option (v0.3.0) that lets the consumer inject its label metrics into the render page so Mermaid measures what the browser displays. mermaid-theme.mjs passes the node label weight/letter-spacing + a 1.5px safety pad and resets cluster titles (overflow:visible pills) to default. Verified 0/116 nodes clip. - `.diagram-pair` no longer flips to a side-by-side row at >=1500px; the DIY-vs-WaveHouse comparison stacks vertically at full content width (the side-by-side variant shrank each diagram to ~450px on wide monitors). - Click-to-zoom lightbox for every diagram (MermaidZoom.astro): small diagrams scale up, wide ones show at natural size with pan-scroll. View- transition-safe; the clone is re-id'd so its id-scoped <style> survives. - AGENTS.md gains a top-level "Authoring Mermaid diagrams" directive: favour vertical (TB/TD) over horizontal (LR) so diagrams fit the page. - Richer docs-preview sticky comment: the head commit's subject (linked) and the upload time, so a stale preview is obvious (ci.yml). The astro-themed-mermaid pin is the v0.3.0 fix commit (Wave-RF/astro-themed-mermaid#1, draft pending your merge); switch the pin to #v0.3.0 once that PR is merged and tagged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
This branch rebuilds the docs landing experience around a live
@wavehouse/sdkconsumer and, in the process, turns the docs from "a polished landing bolted onto stock Starlight" into one coherent, branded, accessible system. Four loosely-coupled workstreams plus the fixes the review gate shook loose:make cihonestly green and 7 rounds of dual-reviewer self-review.A focused "Where this deviates from Astro/Starlight defaults" section is near the bottom, since that's easy to lose track of.
1. Live-demo hero panel (
docs/src/components/LiveDemo.astro, new)The hero's visual column is a real SDK consumer against the public read-only role on the WaveHouse-Stats dogfood deployment (
PUBLIC_WAVEHOUSE_STATS_URL-overridable):gh_summarypipe call.setCount()writes every[data-wh-live="<key>"]on the page, so the same fetch also feeds the hero star chip and the closer-band chip.gh_activity_recentpipe, then a live SSEstream()tail. The stream connects before the backfill so nothing lands in the gap; a composite-key dedup absorbs the overlap and webhook redeliveries.⚡ ingested → live in ~N ms(per-event, 30-sample median, skew-clamped → Stats#33) and an events/min sparkline (gh_events_per_minutepipe → Stats#34).gh_summary→ per-number pipes;gh_activity_recent→ a structured-query backfill; both gone → a layout-stable SSR skeleton with an "offline" pill. A stats-stack rollback makes the panel slower, never blank.normTs()converts zone-less ClickHouse timestamps → ISO-8601 UTC; the wire filter is type-only (anactionIN-filter would drop rows whoseactionis absent), with aVERBSallowlist as the final arbiter.close(), all timers).is:globalstyles because feed rows are runtime-created DOM.Hero.astrodropsterminalLines, the.wh-terminalblock, and the.wh-hero__meshSVG; thepnpm addhint becomes a click-to-copy command chip; the "View on GitHub"↗glyph finally renders (Starlight parsesicon: externalinto an object, so the old string compare never matched).Built on the upstream pipes filed + merged on WaveHouse-Stats: #18 (
gh_activity_recent), #19 (gh_summary). Per-visitor load is 2 cached, singleflight-collapsed pipe hits + SSE (was 3 uncached reads + SSE).2. Site-wide design-system / UI consistency pass (
docs/src/styles/global.css)A measured audit (every page × both themes, computed-style harness) drove a unification:
--radius-xs/sm/md/lg/pill= 4/8/12/16/999px). All buttons 8px; cards/panels/code-frames/tables 12px (no more 1px frame lip).--wh-accent-textrole (6:1) rewired through links/sidebar/focus-ring/stats. Status hues (amber/emerald/rose) got light overrides; page bg stepped darker so white cards read as layers (was 1.04:1, flat).--sl-content-widthis now a responsive track (45rem → 50rem ≥72rem → 58rem ≥100rem; prose held to 46rem so text stays ~70–75ch while code/tables/diagrams use the width)..main-framecaps + centers at ≥100rem, killing ~590px of one-sided dead space at 2560px. Zero horizontal overflow across 14 pages × 8 widths (320–2560) × both themes.body:has(.wh-hero). Requiredbody:has(.wh-hero){background:transparent}to defeat Starlight's layered opaque body background.…nav > ul > li > amatched 0 elements — the DOM has no nestednav— so it was a silent no-op until rewritten.)3. Mermaid diagrams — clipping fix, vertical layout, click-to-zoom
The most visible regressions you flagged on the preview:
Buffer Consumer→Buffer Consume). Root cause: the build-time Chromium measures each node's box, then the browser displays it — andglobal.cssrenders node labels at font-weight 500 while Mermaid measured them at the default 400, so every box came out ~1px too narrow and the longest label in each node clipped on the right. Present onmaintoo (across all 116 nodes), which is why it kept coming back. Fixed upstream inastro-themed-mermaid(newmeasurementCssoption, v0.3.0) that injects the consumer's label metrics into the render page so Mermaid measures what the browser shows.mermaid-theme.mjspasses the node-label weight/letter-spacing + a 1.5px safety pad; selectors are bare (.nodeLabel p) because Mermaid measures before the flowchart<svg>wrapper exists. Verified 0/116 nodes clip after the fix.main(same source, same Mermaid config, no version bump) — nothing in the geometry regressed. What you were seeing is the.diagram-pairwrapper (the DIY-vs-WaveHouse comparison): it flipped to a side-by-side row at ≥1500px, shrinking each diagram to ~450px on a wide monitor. That ≥1500px variant is removed — the pair now always stacks vertically at full content width.MermaidZoom.astro, new) — clicking any diagram opens it in a lightbox: small diagrams scale up (capped 2.5×), wide ones show at natural size with pan-scroll for full detail. Esc / backdrop / button closes; keyboard-focusable (Enter). View-transition-safe (one delegateddocumentlistener, re-enhanced onastro:page-load). The clone is re-id'd so its id-scoped inline<style>(font, themed fills) andurl(#…)refs keep working.AGENTS.md§"Authoring Mermaid diagrams": author vertically (TB/TD) over horizontal (LR) so diagrams fit the page; never sit two large diagrams side-by-side; keep labels short.4. Docs-accuracy corrections (prose, surfaced by the review gate)
Each is a code↔docs accuracy fix: all ten
?table=curls quoted (unquoted?aborts on zsh before curl runs — the first runnable landing command was one);/v1/admin/queryexample gains its missingAuthorizationheader; the silent 10klimitcap,time_rangeuntilsemantics, and admin-only callouts + error tables documented across/v1/schema*and/v1/dlq/stats; the DLQ payload corrected to theEventMessageenvelope (+X-DLQ-*headers);wh.dlq.stream()marked non-functional (#197); the fictional "SSE/WS fan-out" removed fromingest-pipeline.md; schema-refresh 404 caveats in the quickstarts; and the false "secretless ⇒ no token validates / pure public deployment" framing removed fromapi.md/access-control.md/configuration.md(see #291 below).5. Backend / test fixes (surfaced by the gate)
fix(mq)bug(mq): embedded NATS installs its own SIGINT handler — races main shutdown, panics, and zeroes e2e coverage #287 — embedded NATS installed its own SIGINT handler (NoSigsunset), racing graceful shutdown → panic (exit 2, coverage counters never flushed → 0.0% gate) oros.Exit(0)past all defers. Both option literals getNoSigs: true; both ownersWaitForShutdown().fix(e2e)30s orchestrator budget — removing the NATS early-exit exposed a ~15s OTel exporter dial-backoff on clean shutdown (filed bug(observability): graceful shutdown stalls ~15s when the OTel collector is unreachable #288); the old 10s budget SIGKILLed before the coverage flush.startTime; the 1ms deadline-500 test rewrapped inwaitForCondition.6. Docs preview / tooling
ci.yml) now shows the commit subject (linked) and upload time (UTC) alongside the URL, so a stale preview is obvious at a glance.screenshot.mjs:networkidle→load(the SSE stream never goes idle); the screenshot-ready signal moved to the visual column's entrance, independent of live data.@wavehouse/sdkis a docs workspace dep;check-docs/dev-docsgained abuild-tsprerequisite.Where this deviates from Astro/Starlight defaults
The bits a future reader (or Starlight upgrade) needs to know are non-stock:
Config (
docs/astro.config.mjs)Hero,SiteTitle,Footer,Headare all replaced with local components.SiteTitlerenders a custom<WaveMark/>(theme-awarecurrentColor) and Starlight'slogois deliberately omitted so it never renders;Footerre-renders Starlight'sEditLink/LastUpdated/Paginationitself (overridingFooterdrops them) and now also mounts<MermaidZoom/>;Headlive-swaps the theme-adaptive favicon.borderRadius: calc(0.75rem - 1px)(so the frame's outer corner lands at exactly 12px; EC adds the border width and derives inner corners from the same base), customcodeFontFamily/uiFontFamily,frames.shadowColor, themes pinned togithub-dark/github-light, andshiki.langAlias(env→bash,dns→ini).syntaxHighlight.excludeLangs: ["mermaid"]; remark[remarkMath, mermaid.remarkInjectClassdefs]; rehype[mermaid.rehypeMermaid, rehypeKatex]. Mermaid is the in-houseastro-themed-mermaidintegration (build-time SVG via headless Chromium + a per-diagram disk render cache), themed entirely from CSS variables.starlight-image-zoom,starlight-llm-tools, andstarlight-links-validator(conditionally dropped whenWAVEHOUSE_DOCS_WATCHis set, so the dev rebuild loop doesn't fail on a mid-edit dangling link).trailingSlash: "never"; explicit favicon set + full Open Graph / Twitter image meta that Starlight doesn't emit;customCss= the design system + KaTeX; customsidebar;lastUpdated; Tailwind 4 via@tailwindcss/viteinside a Starlight site.CSS-level Starlight overrides (
global.css) — remapped--sl-*tokens (--sl-color-text-accent, the responsive--sl-content-width,--sl-sidebar-width); branded pagination/tables/blockquotes/search-trigger/sidebar;.main-frameultrawide cap; and thebody:has(.wh-hero){background:transparent}workaround for Starlight's layered opaque body background.Deploy — the site is not built by Cloudflare Workers Builds (no headless browser there for Mermaid); it ships from the CI tail steps (
wrangler deployon main,wrangler versions uploadper PR), and acloudflare-md-routerWorker provides.mdcontent negotiation. Workers Builds must stay disconnected from the Worker or it double-deploys.The docs gate surfaced a pre-existing auth-forgery vulnerability: a secretless deployment (the default compose quickstart, no
WH_AUTH_JWT_SECRET) verifies HMAC tokens against[]byte(""), so anyone can forge{"role":"admin"}signed with the empty string and reach/v1/admin/query. Verified against golang-jwt v5.3.1. Per maintainer decision it's tracked in #291 for a security-reviewed fail-closed fix and kept out of this docs PR — which only removes the sentences that restated the masking "secretless ⇒ no token validates" claim.Verification
make cigreen locally;astro check+ full docs build green; markdownlint/biome/typecheck clean.Follow-ups
#v0.3.0.🤖 Generated with Claude Code