Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7cc9584
feat(docs): override Starlight header with a top-level site nav
taitelee Jul 3, 2026
7c86cc0
fix(docs): reduce header nav to a single Docs link and align the logo…
taitelee Jul 3, 2026
4b304fc
feat(docs): redesign header — centered splash / full-width docs, comp…
taitelee Jul 3, 2026
518e35e
fix(docs): gate expanded header search to >=50rem to avoid mobile ove…
taitelee Jul 3, 2026
35ee725
style(docs): size the header nav link smaller than the WaveHouse word…
taitelee Jul 3, 2026
30b2739
refactor(docs): remove the Get Started CTA from the site header
taitelee Jul 6, 2026
da82e98
Merge remote-tracking branch 'origin/main' into page-header
taitelee Jul 6, 2026
add31c3
fix(docs): expand splash search, align and restyle the header Docs link
taitelee Jul 6, 2026
c7a1371
Merge remote-tracking branch 'origin/main' into page-header
taitelee Jul 6, 2026
390859f
fix(docs): sit the header Docs link flush on the wordmark baseline
taitelee Jul 6, 2026
eb8bccb
Merge origin/main into page-header: changelog _in wording fix (#376)
taitelee Jul 6, 2026
58d7197
fix(docs): align header Docs link to wordmark baseline
taitelee Jul 7, 2026
d077ffd
fix(docs): use branding lockup image for the header logo, bottom-alig…
taitelee Jul 7, 2026
8029e3b
fix(docs): center the header lockup in the bar and enlarge the Docs link
taitelee Jul 7, 2026
1c7efd0
fix(docs): drop the header Docs link onto the logo wordmark baseline
taitelee Jul 7, 2026
d709f6f
fix(docs): align the header Docs link to the logo wordmark baseline
taitelee Jul 7, 2026
cd47715
fix(docs): center + underline the Docs link, drop the header width-swap
taitelee Jul 8, 2026
8d19c33
fix(docs): track header nav aria-current across view transitions
taitelee Jul 8, 2026
627444a
Merge remote-tracking branch 'origin/main' into page-header
taitelee Jul 8, 2026
8228864
fix(docs): center the splash header, drop the Docs link underline
taitelee Jul 8, 2026
65cf495
refactor(docs): inline-SVG Logo, robust Docs alignment, smooth width
taitelee Jul 8, 2026
4283201
refactor(docs): inline-SVG Logo component, magic-number-free Docs ali…
taitelee Jul 8, 2026
c120d60
refactor(docs): inline-SVG Logo, robust Docs alignment, smooth width
taitelee Jul 8, 2026
99737e3
fix(docs): reuse Logo in footer, hide lockup a11y dupe, harden aria-c…
taitelee Jul 9, 2026
bc7e15a
fixed footer misalignment
taitelee Jul 9, 2026
305951c
fix(docs): valid standalone lockup SVG, full lockup + Docs nav on mobile
taitelee Jul 9, 2026
fb4ba11
fix(docs): valid standalone lockup SVG, full lockup on mobile
taitelee Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Mermaid diagrams export as PNG, with Copy/Download in the zoom lightbox** (`docs/src/integrations/diagram-png.mjs` (new), `docs/src/components/MermaidZoom.astro`, `docs/astro.config.mjs`, `docs/src/content/docs/development.md`): diagrams render as inline SVG (great for reading — selectable text, screen-reader semantics, theme-reactive colors) but can't be right-click-copied or dropped into a slide deck. A new `astro:build:done` integration now also rasterizes every diagram to `dist/diagrams/<slug>/<index>-<theme>[-transparent].png` — light **and** dark, 2× DPI, in two variants per theme (a WYSIWYG surface card and a transparent-background version for slide decks) — reusing the Playwright Chromium `rehype-mermaid` already needs, with a content-hash cache (`node_modules/.cache/wh-diagram-png`) so unchanged diagrams skip Chromium and the whole pass is wrapped so it can never fail the build. The zoom lightbox (open by clicking any diagram) gains **Copy** (async-clipboard image, falling back to download where unsupported) and **Download** buttons that act on the PNG for the current theme, plus a **background toggle** (checkerboard icon) that flips Copy/Download between the solid and transparent variant and previews transparency as a checkerboard on the zoom stage; inline diagrams stay button-free. The upstream `@wave-rf/astro-themed-mermaid` plugin is intentionally untouched — it's color-agnostic (CSS-var placeholders resolved at runtime), so a WYSIWYG light/dark PNG must be rasterized post-build where `global.css` + a theme apply. PNGs are gitignored build artifacts, regenerated in CI/deploy.

- **Site header override — a top-level "Docs" nav link, the search box kept expanded, and a splash-width header** (`docs/src/components/Header.astro` (new), `docs/src/components/Logo.astro` (new), `docs/src/components/WaveMark.astro` (deleted), `docs/src/components/SiteTitle.astro`, `docs/src/components/Footer.astro`, `docs/src/assets/branding/lockup-themed.svg` (new), `docs/src/config/sidebar.ts`, `docs/src/env.d.ts`, `docs/src/styles/global.css`, `docs/scripts/branding/generate.sh`, `docs/astro.config.mjs`): Starlight has no config hook for header links, so we override its `Header` component through the sanctioned `components` map. The custom header re-renders every built-in control (SiteTitle, Search, ThemeSelect, SocialIcons, LanguageSelect) through its `virtual:starlight/components/*` import — so their overrides and Pagefind search keep working untouched — and adds a `<nav>` with a single top-level **Docs** link (→ `/getting-started`), the way into the doc tree from the sidebar-less splash homepage. The link target lives in a new `headerNav` in `sidebar.ts`, colocated with the sidebar as one navigation source of truth, and the nav is hidden below the `md` (50rem) breakpoint where, on doc pages, the sidebar drawer already carries navigation (on the drawerless splash, the hero CTAs carry it) — the mobile bar keeps the full lockup + search button so it doesn't read as empty. Search keeps Starlight's expanded "Search … ⌘K" box on every page at ≥50rem (including the splash) so the keyboard shortcut stays discoverable, and the primary-nav "Docs" link is baseline-aligned to the WaveHouse wordmark. The logo is a reusable `Logo.astro` that inlines ONE themeable lockup SVG (`lockup-themed.svg`, derived from the brand kit by `generate.sh`): the mark takes `currentColor`/`--wh-accent` and the wordmark `--wh-ink`, so it themes light↔dark purely from CSS — no pre-baked image variants and no four-image download — rendering the full lockup at every width (the mark-only collapse below 30rem left the mobile header and footer looking bare; the lockup is ~8.3rem wide at header size, fitting even a 320px bar), with `SiteTitle.astro` reduced to a thin home-link wrapper and the footer brand block reusing the same `<Logo/>` (labelled via a `title` prop) so header and footer render one wordmark; the lockup now covers every logo use, so the standalone-mark `WaveMark.astro` component is deleted. The generated `lockup-themed.svg` ships without its former leading comment: XML forbids `--` inside comments (as in `--wh-ink`), so the comment made browsers/GitHub reject the standalone file (PR #368 review) — the generated/do-not-hand-edit note lives in `Logo.astro` instead. Because the wordmark is now real inline SVG, `Header.astro` aligns "Docs" to it with `align-items: baseline` plus one documented geometry ratio instead of a hand-tuned pixel nudge, so alignment can't drift when fonts or sizes change. On the splash homepage the header's inner content is held in a centered 68rem column (deliberately narrower than the hero's 80rem band — the inset look settled in review), while doc pages stay full-bleed; since the header is preserved across view-transitions, that width now tweens between the two states (a global `.wh-header` rule in `global.css`, since the selector needs `body:has(.wh-hero)` — outside any component's scope — and must stay applied to the persisted header to tween) rather than snapping. `env.d.ts` gains type declarations for the built-in component modules the override re-renders so `astro check` passes.

### Changed

- **Live SSE events are now projected and serialized once per role instead of once per subscriber** (`internal/stream/hub.go` (new), `internal/stream/{subscriber,bucket,heartbeat,metrics,doc}.go`, `internal/api/stream.go`, `internal/api/hub.go` + `internal/api/transform.go` (both removed — the broadcast hub moves to `internal/stream`, and the orphaned test-only `transformForClient` is dropped), `cmd/wavehouse/main.go`, `docs/src/content/docs/architecture.md`, `AGENTS.md`, plus tests in `internal/stream/{hub,filter,subscriber,bucket,heartbeat}_test.go` and `internal/api/{stream,transform,router,errors}_test.go`): the first PR of the SSE delivery-path throughput epic ([#294](https://github.com/Wave-RF/WaveHouse/issues/294)), building on the `internal/stream` primitives from #346. The broadcast hub moves into `internal/stream` as `Hub`: subscribers register under `(topic, role)`, and `Broadcast` decodes each event **once**, applies each subscribed role's column policy **once**, builds one SSE frame per role, and fans it to every member of that role's `Bucket`. Previously every connection independently ran `json.Unmarshal → policy.Evaluate → filterEventColumns → json.Marshal` (plus a second unmarshal just to read the `id:` timestamp) on the *same* event in its own read loop — byte-identical work repeated N times. For a single-role audience (the public dashboard, every viewer `public`) that collapses N re-projections to 1, moving the measured ~2 270 deliveries/s ceiling toward an events/s ceiling. The `(topic, role)` key is sufficient and claims-independent: column visibility derives only from the role+table policy entry, and the stream path applies no row-level filter (a documented invariant — if row-level filtering is ever added to streaming, the key must take claims into account). The handler's two `select` cases (keepalive vs. per-subscriber event) collapse into one byte-pump over a single `Subscriber.Frames()` queue carrying typed `Frame`s; the subscriber queue grows from cap 1 (keepalive-only) to 64 so live events buffer while the handler is mid-write. Gap-fill replay and `Last-Event-ID`/`?since=` resumption are unchanged (replay stays per-connection via the shared `stream.ReplayFrame`; live frames carry the same `id: <received_timestamp>`). Slow-consumer drops, silent before, now increment `wavehouse_sse_dropped_frames_total`; an inert `Subscriber.Evicted()` seam is wired for the eviction follow-up. The per-delivery OpenTelemetry span (another #294 item) was already removed in #346. **Deferred to follow-ups:** active slow-consumer eviction (#94) and right-sizing the subscriber buffer + broadcast lock cost (#152).
Expand All @@ -29,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Dependabot npm updates moved to the pnpm workspace root, fixing recurring `ERR_PNPM_OUTDATED_LOCKFILE` CI failures on dependency PRs** (`.github/dependabot.yml`, `docs/src/content/docs/development.md`, `SECURITY.md`): the three per-member npm update configs (`directory: /docs`, `/clients/ts`, `/tests/e2e/sdk`) are replaced by a single config at the workspace root (`directory: /`, group `npm-deps`, prefix `deps`). The repo has one root `pnpm-lock.yaml` (the #190 consolidation), and Dependabot only updates a lockfile co-located with the manifest it targets — so a per-member config bumped a member's `package.json` without regenerating the root lockfile, and every such PR (e.g. #211, #337) then failed CI's `pnpm install --frozen-lockfile` with `ERR_PNPM_OUTDATED_LOCKFILE`; no rebase could fix it, because a rebase never regenerates the lockfile. Pointing at the root lets Dependabot read `pnpm-workspace.yaml`, walk every member, and update the one lockfile within the PR, and as a bonus brings the root `package.json`'s own devDeps (biome, markdownlint, nyc) under Dependabot. Trade-off: one combined weekly npm PR with a single `deps:` prefix instead of three per-area PRs (`docs:` / `deps(sdk):` / `deps(tests):`).

- **Go toolchain requirement bumped to 1.26.5** (`go.mod`): the `go` directive moves from `1.26.4` to `1.26.5` so local builds (via `GOTOOLCHAIN=auto`) and CI's `setup-go` (which reads `go.mod` via `go-version-file`) install a Go whose standard library clears the `govulncheck` findings GO-2026-5856 (`crypto/tls`) and GO-2026-4970 (`os`), both fixed in 1.26.5 — those findings were failing `make verify`'s `vulncheck` leaf (and with it the pre-commit hook) on every tree. Patch-level toolchain bump only — no source changes — and the released binaries pick up the patched stdlib too.

### Security

- **Policy `_in` is now enforced on both the row-`filter` and insert-`check` paths, closing a fail-open row-security gap** (`internal/policy/policy.go`, `internal/api/ingest.go`, `docs/src/content/docs/access-control.mdx`, plus tests in `internal/policy/policy_test.go`, `internal/api/ingest_test.go`): closes #224. The `Filter` schema accepted `_in` but the engine never read it: on the row-`filter`/SELECT path `resolveFilters` had no `_in` branch, so a row-security filter like `tenant_id: { _in: … }` produced **no `WHERE` predicate** and the role saw every row instead of its tenant subset (a fail-open, same family as #223); on the `check`/INSERT path only `_eq` was honored, silently dropping any other operator. `_in` now takes a single claim that resolves to a JSON **array** (the multi-tenant case — a token's `tenant_ids` list) and emits `col IN (?, …)` with one bound param per element; a scalar claim is a one-element set, and an empty/absent claim matches **no rows** (fail-closed) rather than widening to all of them. On the insert path an `_in` check requires the column be present and one of the set — there is no single value to auto-inject as `_eq` does, so an omitted column is rejected (`403 check failed`). The comparison operators are enforced on `filter` (`_eq`/`_neq`/`_gt`/`_lt`/`_in` all produce predicates now, so nothing is rejected there) and, on `check`, `_neq`/`_gt`/`_lt` become a loud config-load rejection (no insert-time semantics; `check` honors `_eq` + `_in`). The `_in` value stays a single templated string in the wire schema (Go `Filter.In`, SDK `PolicyFilter._in`), matching the established "set = array" shape of the caller-query `in` operator.
Expand Down
10 changes: 6 additions & 4 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,11 @@ export default defineConfig({
},
],
// No `logo` here on purpose: the SiteTitle override renders the brand
// mark via the shared <WaveMark/> component (currentColor, theme-aware),
// so Starlight's logo config would never render. The brand mark lives in
// exactly one place — src/components/WaveMark.astro.
// Same SVG as the head[] icon entry — see the icon-set comment there.
// lockup via the shared <Logo/> component (CSS-themed inline SVG), so
// Starlight's logo config would never render. The in-app logo lives in
// exactly one place — src/components/Logo.astro.
// The favicon is the same mark as the head[] icon entry — see the
// icon-set comment there.
favicon: "/branding/favicon.svg",
customCss: ["./src/styles/global.css", "katex/dist/katex.min.css"],
social: [
Expand Down Expand Up @@ -146,6 +147,7 @@ export default defineConfig({
},
},
components: {
Header: "./src/components/Header.astro",
Hero: "./src/components/Hero.astro",
SiteTitle: "./src/components/SiteTitle.astro",
Footer: "./src/components/Footer.astro",
Expand Down
13 changes: 13 additions & 0 deletions docs/scripts/branding/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# branding/mark-{light,dark}.png 1024x1024 transparent raster marks
# branding/lockup-{light,dark}.png 1200-wide transparent raster lockups
# favicon.ico (site root, for /favicon.ico auto-probe)
# docs/src/assets/branding/lockup-themed.svg CSS-var-themed lockup, inlined by Logo.astro
#
# To change the brand: edit a hue in docs/src/styles/global.css (--brand-*) or
# a source SVG, then run `make branding-docs` from the repo root. The live site
Expand Down Expand Up @@ -202,6 +203,17 @@ cat > "$OUT_KIT/lockup-light.svg" <<EOF
EOF
outline_lockup_svg "$OUT_KIT/lockup-light.svg"

# Themeable inline lockup for in-app components (Logo.astro inlines it via
# set:html). Same OUTLINED art as lockup-dark, but colours swapped for CSS hooks
# so ONE asset themes itself with no light/dark variants: mark → currentColor
# (the consumer sets color: var(--wh-accent)), wordmark → var(--wh-ink). The
# substitution is case-insensitive because usvg may lower-case the hex on outline.
# No provenance comment in the file itself: XML forbids `--` inside comments, so
# naming var(--wh-ink) there makes browsers/GitHub reject the whole SVG (PR #368
# review). The generated/do-not-hand-edit note lives in Logo.astro instead.
sed -E -e "s|$COLOR_DARK|currentColor|Ig" -e "s|fill=\"$OG_INK\"|style=\"fill:var(--wh-ink)\"|Ig" \
"$OUT_KIT/lockup-dark.svg" > "$SRC/lockup-themed.svg"

# --- 4. favicon.ico (16/32/48) → kit + site root ------------------------------
rsvg-convert -w 16 -h 16 "$OUT_KIT/favicon.svg" -o "$TMP/fav-16.png"
rsvg-convert -w 32 -h 32 "$OUT_KIT/favicon.svg" -o "$TMP/fav-32.png"
Expand Down Expand Up @@ -261,3 +273,4 @@ for out in favicon.svg favicon-light.svg favicon-dark.svg favicon.ico \
printf ' %s✓%s %s\n' "$GREEN" "$RESET" "docs/public/branding/$out"
done
printf ' %s✓%s %s\n' "$GREEN" "$RESET" "docs/public/favicon.ico (site root)"
printf ' %s✓%s %s\n' "$GREEN" "$RESET" "docs/src/assets/branding/lockup-themed.svg (inlined by Logo.astro)"
Loading
Loading