From 19e7a10ee1af7f2d3daf70d67b5dee75dd15e5e7 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 08:56:10 -0400 Subject: [PATCH 01/12] fix(docs): theme-adaptive favicon that survives Chromium's icon selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The favicon.svg has carried a prefers-color-scheme swap since the brand kit landed, but no browser ever showed it: #193 dropped the sizes="any" SVG icon link, so Chromium selected the static .ico, and Chromium only rasterizes an SVG favicon once regardless (crbug.com/1208277). - restore the explicit sizes="any" SVG icon link next to the numeric-sized .ico (the pair Chromium + Firefox both need) - generate statically-colored favicon-{light,dark}.svg in the brand kit - live-swap them from Head.astro on prefers-color-scheme changes, GitHub-style: replace the link node each time (in-place href mutation is only honored transiently) and remove the .ico from the live candidate list — Chromium's scorer otherwise commits the exact-size .ico over a sizes="any" SVG, verified via the profile Favicons DB - keep the self-styling favicon.svg + .ico SSR markup for no-JS consumers; Safari ≤18 falls back to the root /favicon.ico auto-probe Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/astro.config.mjs | 26 ++++++++++---- docs/public/branding/favicon-dark.svg | 1 + docs/public/branding/favicon-light.svg | 1 + docs/scripts/branding/generate.sh | 16 ++++++++- docs/src/components/Head.astro | 48 ++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 docs/public/branding/favicon-dark.svg create mode 100644 docs/public/branding/favicon-light.svg diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d2294fe9..e3ca0db2 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -42,11 +42,16 @@ export default defineConfig({ content: `!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n component (currentColor, theme-aware), // so Starlight's logo config would never render. The brand mark lives in // exactly one place — src/components/WaveMark.astro. - // Primary favicon (the .ico fallback + apple-touch are added via head above). + // Same SVG 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: [ diff --git a/docs/public/branding/favicon-dark.svg b/docs/public/branding/favicon-dark.svg new file mode 100644 index 00000000..4e3db8fb --- /dev/null +++ b/docs/public/branding/favicon-dark.svg @@ -0,0 +1 @@ + diff --git a/docs/public/branding/favicon-light.svg b/docs/public/branding/favicon-light.svg new file mode 100644 index 00000000..11bb46ec --- /dev/null +++ b/docs/public/branding/favicon-light.svg @@ -0,0 +1 @@ + diff --git a/docs/scripts/branding/generate.sh b/docs/scripts/branding/generate.sh index 23e97ceb..4bec9e16 100755 --- a/docs/scripts/branding/generate.sh +++ b/docs/scripts/branding/generate.sh @@ -8,6 +8,7 @@ # served at /branding/* — plus docs/public/favicon.ico at the site root for the # legacy auto-probe: # branding/favicon.svg browser favicon, CSS-swaps light/dark +# branding/favicon-{light,dark}.svg static variants (Head.astro live swap) # branding/favicon.ico 16/32/48 multi-resolution legacy fallback # branding/apple-touch-icon.png 180x180 app-icon tile (iOS) # branding/og.png 1200x630 social card @@ -148,6 +149,18 @@ cat > "$OUT_KIT/favicon.svg" <$MARK_INNER EOF +# Statically-colored variants for Head.astro's live theme-flip swap (the +# GitHub favicon.svg/favicon-dark.svg pattern): a distinct URL per scheme +# keeps Chromium's per-URL favicon cache scheme-keyed, and the fetched file +# carries no media query for the favicon rasterizer to mis-evaluate. The +# self-styling favicon.svg above remains the no-JS default. +cat > "$OUT_KIT/favicon-light.svg" <$(mark_with_color "$COLOR_LIGHT") +EOF +cat > "$OUT_KIT/favicon-dark.svg" <$(mark_with_color "$COLOR_DARK") +EOF + # --- 2. Standalone marks (copy-out) ------------------------------------------- cat > "$OUT_KIT/mark-light.svg" <$(mark_with_color "$COLOR_LIGHT") @@ -212,7 +225,8 @@ render_text_png "$OUT_KIT/lockup-dark.svg" "$OUT_KIT/lockup-dark.png" 1200 250 # --- Report ------------------------------------------------------------------- printf '%s colors%s light=%s dark=%s bg=%s ink=%s\n' \ "$CYAN" "$RESET" "$COLOR_LIGHT" "$COLOR_DARK" "$OG_BG" "$OG_INK" -for out in favicon.svg favicon.ico apple-touch-icon.png og.png \ +for out in favicon.svg favicon-light.svg favicon-dark.svg favicon.ico \ + apple-touch-icon.png og.png \ mark-light.svg mark-dark.svg lockup-dark.svg lockup-light.svg \ mark-light.png mark-dark.png lockup-light.png lockup-dark.png; do printf ' %s✓%s %s\n' "$GREEN" "$RESET" "docs/public/branding/$out" diff --git a/docs/src/components/Head.astro b/docs/src/components/Head.astro index f9549027..86dfc79d 100644 --- a/docs/src/components/Head.astro +++ b/docs/src/components/Head.astro @@ -58,3 +58,51 @@ import { ClientRouter } from "astro:transitions"; document.addEventListener("astro:after-swap", resolve); })(); + +{/* + Live theme-flip for the tab icon, the GitHub way (favicon.svg/favicon-dark + .svg + matchMedia): Chromium rasterizes an SVG favicon once, under the + scheme active at fetch time, and never re-renders it on a theme flip + (crbug.com/1208277) — so swap the SVG icon links between statically-colored + per-scheme files. A distinct URL per scheme defeats the per-URL favicon + cache, and the fetched file carries no media query for the favicon + rasterizer to mis-evaluate. The self-styling favicon.svg stays as the SSR + default for no-JS contexts (bookmarks, unloaded tabs, crawlers — where a + JS-only swap is known to fail). Tracks the OS/browser theme (what the tab + strip uses), deliberately not Starlight's page toggle. +*/} + From 201aaa286c02e80d9bd1f9fef315f2b9037dcaeb Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 09:25:16 -0400 Subject: [PATCH 02/12] =?UTF-8?q?docs:=20address=20pre-push=20review=20?= =?UTF-8?q?=E2=80=94=20changelog=20entry=20+=20stale=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG: Unreleased Fixed entry for the theme-adaptive favicon, superseding the favicon wiring described in the branding-pipeline entry - generate.sh: lockup-*.svg are outlined to paths in place — the comment claimed they still hold - Makefile: branding-docs help text pointed at the wrong mark.svg path Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ Makefile | 2 +- docs/scripts/branding/generate.sh | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b4f327..ef67cd29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- **Theme-adaptive favicon that actually displays** (`docs/astro.config.mjs`, `docs/src/components/Head.astro`, `docs/scripts/branding/generate.sh`, `docs/public/branding/favicon-{light,dark}.svg` (new)): the CSS-swap `favicon.svg` has carried a `prefers-color-scheme` flip since the branding pipeline landed, but no browser ever showed it — the docs final pass (#193) dropped the explicit `sizes="any"` SVG icon link, so Chromium selected the static `.ico`, and Chromium rasterizes an SVG favicon only once regardless (crbug.com/1208277). The head now declares the numeric-sized `.ico` + `sizes="any"` SVG pair, the brand kit gains statically-colored `favicon-{light,dark}.svg` variants (two new copy-out assets), and a `Head.astro` inline script live-swaps them on `prefers-color-scheme` changes, GitHub-style: replacing the link node each repaint (in-place `href` mutation is honored only transiently) and removing the `.ico` from the live candidate list, since Chromium's favicon scorer otherwise commits an exact-size `.ico` over a `sizes="any"` SVG (verified against the browser's profile `Favicons` DB rather than blog lore). No-JS consumers keep the full SSR markup (CSS-swap `favicon.svg` + `.ico`); Safari ≤18, which can't use SVG favicons, falls back to the root `/favicon.ico` auto-probe that the brand pipeline keeps in place. Supersedes the favicon wiring described in the branding-pipeline entry below (`favicon:` option + `.ico`/apple-touch head tags only). + ### Removed - **Gemini Code Assist integration and the on-repo Claude PR review workflow** (`.gemini/config.yaml`, `.gemini/styleguide.md`, `.github/workflows/claude-review.yml` deleted; `.dockerignore`, `AGENTS.md`, `.github/prompts/pr-review.md`, `.claude/agents/pre-push-reviewer.md`, `.claude/skills/pr-review-locally/SKILL.md`, `.claude/hooks/agent-bash-gate.sh`, `docs/src/content/docs/development.md`, `docs/src/content/docs/claude-code.md` updated). Dropped the Gemini Code Assist app configuration (`.gemini/`) and the GitHub Actions Claude PR review workflow (`claude-review.yml` — the manual `@claude` / `/review` / `workflow_dispatch` reviewer) outright. **Local Claude Code tooling under `.claude/` is unchanged**: the `pre-push-reviewer` subagent still runs the canonical `.github/prompts/pr-review.md` locally before pushes, and that prompt file is retained for it. Living docs, agent/skill/hook references, and the bot-re-trigger tables were scrubbed of both reviewers; CodeRabbit and Copilot remain the advisory PR reviewers, and `Admin approval` + the `main branch protection` ruleset remain the actual merge gate. Earlier CHANGELOG entries that mention either reviewer are left as historical record. diff --git a/Makefile b/Makefile index 9bb14e2b..91870718 100644 --- a/Makefile +++ b/Makefile @@ -539,7 +539,7 @@ build-docs: check-docs install-playwright-docs ## Build docs site → docs/dist/ # iterating on the mark does). The script self-locates via git, so it runs the # same from the repo root. .PHONY: branding-docs -branding-docs: ## Regenerate docs logo/favicon/OG assets from docs/scripts/branding/mark.svg +branding-docs: ## Regenerate docs logo/favicon/OG assets from docs/src/assets/branding/mark.svg @docs/scripts/branding/generate.sh # --- Node workspace: SDK + docs ---------------------------------------------- diff --git a/docs/scripts/branding/generate.sh b/docs/scripts/branding/generate.sh index 4bec9e16..4c5a017c 100755 --- a/docs/scripts/branding/generate.sh +++ b/docs/scripts/branding/generate.sh @@ -214,9 +214,9 @@ render_text_png "$TMP/og.svg" "$OUT_KIT/og.png" 1200 630 # --- 7. Raster marks + lockups (PNG, transparent) ----------------------------- # For non-SVG consumers: slide decks, GitHub social-preview upload, email sig, # anywhere SVG isn't accepted. Marks are pure geometry → rsvg-convert is fine. -# Lockups carry the wordmark, so they go through resvg + pinned Inter; the -# committed lockup-*.svg still hold (the SVG-outlining step is separate), -# but their PNG renders are now true Inter. +# Lockups carry the wordmark; the committed lockup-*.svg are already outlined +# to paths in place (outline_lockup_svg, section 3), so rendering their PNGs +# through resvg + the pinned Inter is belt-and-braces. rsvg-convert -w 1024 -h 1024 "$OUT_KIT/mark-light.svg" -o "$OUT_KIT/mark-light.png" rsvg-convert -w 1024 -h 1024 "$OUT_KIT/mark-dark.svg" -o "$OUT_KIT/mark-dark.png" render_text_png "$OUT_KIT/lockup-light.svg" "$OUT_KIT/lockup-light.png" 1200 250 From b1599f9d776f5896eb26a28453e7b6d593ecf7f5 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 13:56:28 -0400 Subject: [PATCH 03/12] feat(docs): prod-faithful dev-docs loop + mermaid render cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make dev-docs no longer runs `astro dev`, whose dev server skips everything the Cloudflare Worker adds in production (cloudflare-md-router .md twins, pagefind search, starlight-llm-tools outputs) — the de-facto workflow had become a manual build-docs + preview-docs cycle, restarted by hand per change. docs/scripts/dev.mjs (new) now drives the loop: a debounced full `astro build` per save into a .dev-dist/ staging dir, synced in place into dist/ only on success (plain node fs — no rsync or any other external tool), served through `wrangler dev --live-reload` on :4321 (DOCS_PORT overrides) so the browser refreshes itself per green build and a failed build keeps serving the last good site (red banner + terminal bell). Watch set: src/, public/, astro.config.mjs, tsconfig.json, package.json, .env*; wrangler watches worker/index.ts and wrangler.jsonc itself. starlight-links-validator is skipped under WAVEHOUSE_DOCS_WATCH=1 so mid-edit dangling links don't block previewing every save — DOCS_WATCH_STRICT=1 keeps it on; CI and make build-docs enforce links unchanged. The raw HMR server remains as `pnpm run start`, and the VS Code launch config gains an entry for each. Mermaid SSR re-rendered all 17 diagrams through headless Chromium on every build, diagrams changed or not. astro.config.mjs now uses astro-themed-mermaid v0.2.0's `rehypeMermaid` export — rehype-mermaid behind a content-addressed per-diagram disk cache (keyed on diagram source + render options + package versions, entries re-id'd to content-derived ids). Measured: no-diagram-change rebuilds 6-7s → ~3.7s, single-diagram edits re-render only their page, output byte-identical modulo SVG ids; save → refreshed browser is ~4s end to end. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 1 + .vscode/launch.json | 9 +- AGENTS.md | 2 +- CHANGELOG.md | 6 + Makefile | 14 +- docs/astro.config.mjs | 22 ++- docs/package.json | 4 +- docs/scripts/dev.mjs | 221 +++++++++++++++++++++++++++ docs/scripts/screenshot.mjs | 4 +- docs/src/content/docs/development.md | 2 +- pnpm-lock.yaml | 26 +++- 11 files changed, 287 insertions(+), 24 deletions(-) create mode 100644 docs/scripts/dev.mjs diff --git a/.gitignore b/.gitignore index a476cbaf..dd45df83 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ __debug_bin* # Docs site build outputs docs/.astro/ +docs/.dev-dist/ docs/.env.production docs/.wrangler/ docs/dist/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 5de4a9f4..d84c3f5f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,11 +2,18 @@ "version": "0.2.0", "configurations": [ { - "name": "Docs: Astro dev server", + "name": "Docs: dev loop (rebuild-on-save + wrangler, :4321)", "type": "node-terminal", "request": "launch", "cwd": "${workspaceFolder}/docs", "command": "pnpm dev" + }, + { + "name": "Docs: Astro HMR dev server (no Worker/search/md twins)", + "type": "node-terminal", + "request": "launch", + "cwd": "${workspaceFolder}/docs", + "command": "pnpm start" } ] } diff --git a/AGENTS.md b/AGENTS.md index f859f5ce..d510eb66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,7 +119,7 @@ make clean-tools # Installed tools and pnpm deps (.bin/, node_modules/) make clean-all # Full reset: above + data/ + docker volumes # Docs site (Astro + Starlight in docs/, driven via pnpm workspace filters) -make dev-docs # Hot-reload Astro dev server on :4321 +make dev-docs # Prod-faithful docs dev loop: rebuild-on-save + wrangler dev on :4321 make build-docs # Production build → docs/dist/ make preview-docs # Wrangler preview of the production build (auto-builds if dist/ missing) make branding-docs # Regenerate logo/favicon/OG assets from docs/src/assets/branding/mark.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index ba34415a..606dea36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Theme-adaptive favicon that actually displays** (`docs/astro.config.mjs`, `docs/src/components/Head.astro`, `docs/scripts/branding/generate.sh`, `docs/public/branding/favicon-{light,dark}.svg` (new)): the CSS-swap `favicon.svg` has carried a `prefers-color-scheme` flip since the branding pipeline landed, but no browser ever showed it — the docs final pass (#193) dropped the explicit `sizes="any"` SVG icon link, so Chromium selected the static `.ico`, and Chromium rasterizes an SVG favicon only once regardless (crbug.com/1208277). The head now declares the numeric-sized `.ico` + `sizes="any"` SVG pair, the brand kit gains statically-colored `favicon-{light,dark}.svg` variants (two new copy-out assets), and a `Head.astro` inline script live-swaps them on `prefers-color-scheme` changes, GitHub-style: replacing the link node each repaint (in-place `href` mutation is honored only transiently) and removing the `.ico` from the live candidate list, since Chromium's favicon scorer otherwise commits an exact-size `.ico` over a `sizes="any"` SVG (verified against the browser's profile `Favicons` DB rather than blog lore). No-JS consumers keep the full SSR markup (CSS-swap `favicon.svg` + `.ico`); Safari ≤18, which can't use SVG favicons, falls back to the root `/favicon.ico` auto-probe that the brand pipeline keeps in place. Supersedes the favicon wiring described in the branding-pipeline entry below (`favicon:` option + `.ico`/apple-touch head tags only). - **TS SDK no longer hardcodes `ORDER BY received_timestamp DESC` as the default query order, so `wh.from(table).fetch()` works on any schema** (`clients/ts/src/query-builder.ts`, `clients/ts/src/query-builder.test.ts`, `tests/e2e/sdk/query.test.ts`, `docs/src/content/docs/sdk.md`): closes #270. The query builder previously injected `ORDER BY received_timestamp DESC` as both the default query order (`_buildAST`) and the cursor-pagination column (`_fetchNext`), so a bare `.fetch()` against a bring-your-own-schema table lacking that column emitted invalid SQL → ClickHouse `Unknown expression identifier received_timestamp` → HTTP 500. The SDK now emits an `ORDER BY` only when the caller sets one via `.orderBy()`, and sends no implicit order otherwise. **Soft behavior change:** `.fetch()` still reports `hasMore` honestly from the row count, but cursor pagination's `result.next` is now `undefined` (rather than always defined when `hasMore` is `true`) unless the query has an explicit `.orderBy()`; callers paginating via `await result.next!()` must guard on `result.next` (the `sdk.md` / README examples already do). This also sidesteps the #175 default-cursor tie bug — there is no longer a default cursor to tie on. A backend-owned, per-table default sort order (designed together with server-side pagination, which would subsume #175) is tracked as a follow-up (#274). Stream dedup in `clients/ts/src/stream/live-query.ts` / `stream/sse.ts` still uses `received_timestamp` and is intentionally out of scope (the live ingest stream always carries that column). Builds on #199 (query-builder structure). +### Changed + +- **`make dev-docs` is now production-faithful: rebuild-on-save through the real Worker instead of `astro dev`** (`docs/scripts/dev.mjs` (new), `docs/package.json`, `docs/astro.config.mjs`, `Makefile`, `AGENTS.md`, `.gitignore`, `.vscode/launch.json`, `docs/scripts/screenshot.mjs`, `docs/src/content/docs/development.md`): the Astro dev server skips everything the Cloudflare Worker adds in production — `cloudflare-md-router` content negotiation (`.md` twins), the pagefind search index, and the `starlight-llm-tools` outputs only exist in real builds — so the de-facto docs workflow had become a manual `make build-docs && make preview-docs` cycle, restarted by hand per change. `pnpm run dev` (and therefore `make dev-docs`) now drives `docs/scripts/dev.mjs`: a debounced full `astro build` on every save to `src/`, `public/`, or a root build input (`astro.config.mjs`, `tsconfig.json`, `package.json`, `.env*` — but not `worker/index.ts`/`wrangler.jsonc`, which `wrangler dev` hot-reloads itself, and not `pnpm-lock.yaml`, which changes on `git pull` before `pnpm install` has run), with saves landing mid-build coalescing into one follow-up build, staged into a gitignored `docs/.dev-dist/` and synced in-place into `dist/` only on success (plain node fs, prune-then-copy — no rsync or any other external tool, so minimal WSL/container setups work) — a failed build prints a red banner + terminal bell and keeps serving the last good site rather than the emptied `outDir` a direct in-place build would leave — while `wrangler dev --live-reload` on :4321 (`DOCS_PORT` overrides; same default as before, so `scripts/screenshot.mjs` and muscle memory still work; one-off `preview-docs` stays on :8787) picks up the asset changes live, new routes included, and refreshes the browser over its websocket. Each rebuild is a real production build with one deliberate exception: the loop sets `WAVEHOUSE_DOCS_WATCH=1`, under which `astro.config.mjs` drops `starlight-links-validator` — mid-edit prose always has dangling links, and failing every rebuild over them would mean never seeing the change being made; rendered output is identical, CI / `make build-docs` still enforce link validity, and `DOCS_WATCH_STRICT=1` keeps the validator on in watch builds for those who want broken links to fail loudly at edit time (the development.md tooling notes now spell out that split; build errors themselves — broken Mermaid, bad frontmatter, MDX errors — always fail the watch build loudly either way). The raw HMR dev server remains available as `pnpm --filter wavehouse-docs run start` for fidelity-insensitive iteration (e.g. CSS tweaking) — the VS Code launch config gains a second entry for it, with the existing `pnpm dev` entry relabeled to match its new behavior. + +- **Docs Mermaid diagrams render through a per-diagram disk cache** (`docs/astro.config.mjs`, `docs/package.json`, `pnpm-lock.yaml`; the cache itself ships in `astro-themed-mermaid` v0.2.0): diagram SSR runs headless Chromium via `rehype-mermaid` and re-rendered all 17 diagrams on *every* docs build — including builds that touched no diagram, i.e. almost all of them. The config now uses the package's new `mermaid.rehypeMermaid` export, which wraps `rehype-mermaid` with a content-addressed cache under `node_modules/.cache/astro-themed-mermaid/` (keyed on diagram source + render options + package versions, so theme and toolchain changes self-invalidate; entries are re-id'd to content-derived `mermaid-c` ids so cross-build entries can't collide on a page). A build whose diagrams all hit skips Chromium — and even the `rehype-mermaid`/playwright import — entirely: measured warm rebuilds drop from ~6–7s to ~3.7s, a single-diagram edit re-renders only its page (~5s), and output was verified byte-identical modulo SVG ids. Cold builds (fresh clone, CI, `make clean-tools`) render once and harvest; cache I/O is best-effort and degrades to a normal render, never a failed build. + ### Removed - **Gemini Code Assist integration and the on-repo Claude PR review workflow** (`.gemini/config.yaml`, `.gemini/styleguide.md`, `.github/workflows/claude-review.yml` deleted; `.dockerignore`, `AGENTS.md`, `.github/prompts/pr-review.md`, `.claude/agents/pre-push-reviewer.md`, `.claude/skills/pr-review-locally/SKILL.md`, `.claude/hooks/agent-bash-gate.sh`, `docs/src/content/docs/development.md`, `docs/src/content/docs/claude-code.md` updated). Dropped the Gemini Code Assist app configuration (`.gemini/`) and the GitHub Actions Claude PR review workflow (`claude-review.yml` — the manual `@claude` / `/review` / `workflow_dispatch` reviewer) outright. **Local Claude Code tooling under `.claude/` is unchanged**: the `pre-push-reviewer` subagent still runs the canonical `.github/prompts/pr-review.md` locally before pushes, and that prompt file is retained for it. Living docs, agent/skill/hook references, and the bot-re-trigger tables were scrubbed of both reviewers; CodeRabbit and Copilot remain the advisory PR reviewers, and `Admin approval` + the `main branch protection` ruleset remain the actual merge gate. Earlier CHANGELOG entries that mention either reviewer are left as historical record. diff --git a/Makefile b/Makefile index 91870718..adbdca9e 100644 --- a/Makefile +++ b/Makefile @@ -244,8 +244,14 @@ dev: deps-up $(AIR) $(CONFIG_FILES) ## Hot-reload dev server: ClickHouse + WaveH dev-ts: pnpm-install ## Watch-build SDK (tsup --watch) @$(PNPM) --filter $(SDK_NAME) run dev +# dev-docs runs docs/scripts/dev.mjs: a full `astro build` on every save, +# synced into docs/dist/ and served through `wrangler dev --live-reload` — +# so the Worker (md twins, pagefind search, llm outputs) behaves exactly like +# production while you edit, and the browser refreshes itself per build. +# Slower per change than Astro HMR; the raw dev server remains available as +# `pnpm --filter wavehouse-docs run start` when fidelity doesn't matter. .PHONY: dev-docs -dev-docs: install-playwright-docs ## Hot-reload Astro dev server on :4321 +dev-docs: install-playwright-docs ## Prod-faithful docs dev loop: rebuild-on-save + wrangler dev on :4321 @$(PNPM) --filter $(DOCS_FILTER) run dev # preview-docs serves the production build through wrangler (Cloudflare Workers @@ -782,7 +788,7 @@ binary-analysis: size audit-cgo deadcode ## Combined: size + audit-cgo + deadcod .PHONY: clean clean: ## Remove build artifacts (bin/, dist/, clients/ts/dist/, docs/dist/) @echo "$(YELLOW)==> Cleaning build artifacts...$(RESET)" - @rm -rf bin/ dist/ clients/ts/dist/ docs/dist/ + @rm -rf bin/ dist/ clients/ts/dist/ docs/dist/ docs/.dev-dist/ .PHONY: clean-ts clean-ts: ## Remove SDK build artifacts only (clients/ts/dist/) @@ -790,9 +796,9 @@ clean-ts: ## Remove SDK build artifacts only (clients/ts/dist/) @rm -rf clients/ts/dist/ .PHONY: clean-docs -clean-docs: ## Remove docs build artifacts only (docs/dist/) +clean-docs: ## Remove docs build artifacts only (docs/dist/, docs/.dev-dist/) @echo "$(YELLOW)==> Cleaning docs dist/...$(RESET)" - @rm -rf $(DOCS_DIR)/dist/ + @rm -rf $(DOCS_DIR)/dist/ $(DOCS_DIR)/.dev-dist/ .PHONY: clean-test clean-test: ## Remove test artifacts (tmp/ — coverage data, logs, NATS state) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index e3ca0db2..f92e4959 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -5,7 +5,6 @@ import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "astro/config"; import { themedMermaid } from "astro-themed-mermaid"; import rehypeKatex from "rehype-katex"; -import rehypeMermaid from "rehype-mermaid"; import remarkMath from "remark-math"; import starlightImageZoom from "starlight-image-zoom"; import starlightLinksValidator from "starlight-links-validator"; @@ -28,7 +27,10 @@ export default defineConfig({ markdown: { syntaxHighlight: { excludeLangs: ["mermaid"] }, remarkPlugins: [remarkMath, mermaid.remarkInjectClassdefs], - rehypePlugins: [[rehypeMermaid, mermaid.rehypeMermaidOptions], rehypeKatex], + // mermaid.rehypeMermaid = rehype-mermaid behind the package's per-diagram + // render cache (node_modules/.cache/astro-themed-mermaid/) — rebuilds that + // don't change a diagram skip Chromium entirely (~6.5s → ~3.7s per build). + rehypePlugins: [mermaid.rehypeMermaid, rehypeKatex], }, integrations: [ starlight({ @@ -156,10 +158,18 @@ posthog.init('phc_xFG2NGQa7bFg4QjBp3MAn8kr8bAPJxM7GvKzfoNEwZwj',{api_host:'https starlightImageZoom(), // @ts-expect-error — plugin types target an older Starlight; runtime is fine. starlightLlmTools(), - starlightLinksValidator({ - errorOnInvalidHashes: true, - errorOnRelativeLinks: true, - }), + // The validator fails the whole build on a broken link. Right for CI + // and `make build-docs`; wrong for the rebuild-on-save dev loop + // (scripts/dev.mjs sets WAVEHOUSE_DOCS_WATCH=1), where a mid-edit + // dangling link would block every rebuild. Output is identical. + ...(process.env.WAVEHOUSE_DOCS_WATCH + ? [] + : [ + starlightLinksValidator({ + errorOnInvalidHashes: true, + errorOnRelativeLinks: true, + }), + ]), ], }), mermaid.integration, diff --git a/docs/package.json b/docs/package.json index 8241546b..171d70ca 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "private": true, "scripts": { - "dev": "astro dev", + "dev": "node scripts/dev.mjs", "start": "astro dev", "build": "astro build", "check": "astro check", @@ -19,7 +19,7 @@ "@fontsource-variable/jetbrains-mono": "^5.2.8", "@tailwindcss/vite": "^4.3.0", "astro": "^6.1.5", - "astro-themed-mermaid": "github:Wave-RF/astro-themed-mermaid#v0.1.1", + "astro-themed-mermaid": "github:Wave-RF/astro-themed-mermaid#v0.2.0", "cloudflare-md-router": "github:Wave-RF/cloudflare-md-router", "katex": "^0.16.11", "rehype-katex": "^7.0.1", diff --git a/docs/scripts/dev.mjs b/docs/scripts/dev.mjs new file mode 100644 index 00000000..68411d66 --- /dev/null +++ b/docs/scripts/dev.mjs @@ -0,0 +1,221 @@ +/* Production-faithful docs dev loop — what `make dev-docs` runs. + * + * `astro dev` skips everything the Cloudflare Worker adds in production: + * cloudflare-md-router content negotiation (`.md` twins), the pagefind + * search index, and the starlight-llm-tools outputs only exist in real + * builds. So instead of the dev server, this loop runs a full `astro build` + * on every save and serves the result through `wrangler dev --live-reload` + * on :4321 — the same Worker + Static Assets pipeline as wavehouse.dev, + * with the browser auto-refreshing when a build lands. + * + * Builds go to a .dev-dist/ staging dir and are synced into dist/ (plain + * node fs — no rsync or any other external tool, so WSL/minimal images work) + * only on success: a failed build, or the emptied-outDir window during one, + * never takes down the served site — you keep the last good build until a + * green one replaces it, and failures print a red banner + terminal bell. + * Trade-off vs `astro dev`: no HMR and no browser error overlay; each save + * costs a real build (mostly rehype-mermaid's Chromium), with errors landing + * in this terminal instead of the page. The raw Astro dev server remains + * available as `pnpm run start` when HMR matters more than fidelity. + * + * Knobs: + * DOCS_PORT=… serve port (default 4321) + * DOCS_WATCH_STRICT=1 keep starlight-links-validator in watch builds — + * a broken link then fails the build loudly here + * instead of waiting for CI. Off by default because + * mid-edit pages routinely link to pages that don't + * exist yet, which would block previewing every save + * (see the WAVEHOUSE_DOCS_WATCH gate in + * astro.config.mjs; rendered output is identical). */ + +import { spawn } from "node:child_process"; +import { existsSync, watch } from "node:fs"; +import { cp, readdir, rm, stat } from "node:fs/promises"; +import { join, resolve } from "node:path"; + +const ROOT = resolve(import.meta.dirname, ".."); +const BIN = join(ROOT, "node_modules", ".bin"); +const STAGING = join(ROOT, ".dev-dist"); +const DIST = join(ROOT, "dist"); +const PORT = process.env.DOCS_PORT ?? "4321"; +const DEBOUNCE_MS = 300; + +const log = (msg) => console.log(`\x1b[36m[dev-docs]\x1b[0m ${msg}`); +// Loud-failure banner: bold red + terminal bell (most terminals flash/bounce). +const fail = (msg) => console.log(`\x1b[36m[dev-docs]\x1b[0m \x1b[1;31m${msg}\x1b[0m\x07`); +const STRICT = Boolean(process.env.DOCS_WATCH_STRICT); + +let activeBuild = null; +function run(cmd, args, opts = {}) { + return new Promise((done) => { + const child = spawn(cmd, args, { cwd: ROOT, stdio: "inherit", ...opts }); + activeBuild = child; + const finish = (code) => { + activeBuild = null; + done(code); + }; + child.on("error", () => finish(127)); // e.g. binary not found + child.on("close", (code) => finish(code ?? 1)); + }); +} + +/* Sync staging → dist in place (same directory inode, so wrangler's asset + * watcher sees an incremental update rather than a directory swap): prune + * entries the new build dropped, then copy everything over. prune runs + * FIRST so a path that changed type between builds (file ↔ directory, e.g. + * a page restructure) is removed before cp would trip over it. */ +async function syncToDist() { + if (shuttingDown) return; + await prune(STAGING, DIST); + await cp(STAGING, DIST, { recursive: true, force: true }); +} + +/* Remove dist entries that are absent from staging or changed type. */ +async function prune(stagingDir, distDir) { + let entries; + try { + entries = await readdir(distDir, { withFileTypes: true }); + } catch { + return; // first build: dist/ doesn't exist yet — cp creates it + } + for (const entry of entries) { + const s = join(stagingDir, entry.name); + const d = join(distDir, entry.name); + const sStat = await stat(s).catch(() => null); + if (!sStat || sStat.isDirectory() !== entry.isDirectory()) { + await rm(d, { recursive: true, force: true }); + } else if (entry.isDirectory()) { + await prune(s, d); + } + } +} + +let building = false; +let dirty = false; +let pendingReason = "startup"; +let buildCount = 0; + +async function rebuild() { + if (shuttingDown) return; // a debounce callback can fire after the signal + if (building) { + dirty = true; + return; + } + building = true; + do { + dirty = false; + const n = ++buildCount; + const t0 = Date.now(); + log(`build #${n} (${pendingReason})…`); + const code = await run(join(BIN, "astro"), ["build", "--outDir", ".dev-dist"], { + env: { ...process.env, ...(STRICT ? {} : { WAVEHOUSE_DOCS_WATCH: "1" }) }, + }); + if (shuttingDown) break; + if (code === 0) { + try { + await syncToDist(); + log( + `build #${n} live in ${((Date.now() - t0) / 1000).toFixed(1)}s — browser reloads itself`, + ); + } catch (err) { + // A failed sync degrades like a failed build — never crash the loop + // (that would orphan wrangler on the port). + fail( + `build #${n} sync FAILED (${err?.code ?? err}) — dist/ may be partial; next green build re-syncs`, + ); + } + } else { + fail( + `build #${n} FAILED (exit ${code}) — error above; still serving the previous good build`, + ); + } + } while (dirty && !shuttingDown); + building = false; +} + +let timer; +function onChange(file) { + pendingReason = file; + clearTimeout(timer); + timer = setTimeout(rebuild, DEBOUNCE_MS); +} + +// Editor droppings: macOS Finder, vim swap/backup files, vim's fsync probe. +const IGNORED = /(^|\/)(\.DS_Store|4913|.*\.sw[px]|.*~)$/; +const watchers = []; +function startWatchers() { + for (const dir of ["src", "public"]) { + watchers.push( + watch(join(ROOT, dir), { recursive: true }, (_event, file) => { + if (!file || IGNORED.test(file)) return; + onChange(join(dir, file)); + }), + ); + } + // Root-level build inputs: Astro/TS config, the dep manifest (a `pnpm add` + // of a new plugin should rebuild), and .env* files (Astro loads them at + // build time). A non-recursive watch on the directory survives editors that + // replace files on save (watching a file itself would not). Deliberately + // absent: worker/index.ts + wrangler.jsonc (wrangler dev watches those + // itself) and pnpm-lock.yaml (changes on `git pull` before `pnpm install` + // has run — rebuilding at that moment would fail spuriously). + const rootTriggers = new Set(["astro.config.mjs", "tsconfig.json", "package.json"]); + watchers.push( + watch(ROOT, (_event, file) => { + if (file && (rootTriggers.has(file) || file.startsWith(".env"))) onChange(file); + }), + ); +} + +let wrangler; +let shuttingDown = false; +function shutdown(code) { + if (shuttingDown) return; + shuttingDown = true; + clearTimeout(timer); + for (const w of watchers) w.close(); // open watchers would keep the event loop alive + activeBuild?.kill("SIGINT"); + wrangler?.kill("SIGINT"); + process.exitCode = code; +} +process.on("SIGINT", () => shutdown(0)); +process.on("SIGTERM", () => shutdown(0)); +// Backstop for anything the loop doesn't catch (incl. fs.watch 'error' +// events): tear wrangler down with us rather than orphaning it on the port. +for (const event of ["uncaughtException", "unhandledRejection"]) { + process.on(event, (err) => { + log(`fatal (${event}): ${err?.stack ?? err}`); + shutdown(1); + }); +} + +if (existsSync(join(DIST, "index.html"))) { + log("serving the existing dist/ while a fresh build runs"); + void rebuild(); +} else { + log("no dist/ yet — first build must finish before serving starts"); + await rebuild(); + if (!existsSync(join(DIST, "index.html"))) { + log("first build failed and there is no previous dist/ to serve — exiting"); + process.exit(1); + } +} + +// A signal during the cold-start build means we're done before serving starts. +if (shuttingDown) process.exit(); + +wrangler = spawn(join(BIN, "wrangler"), ["dev", "--live-reload", "--port", PORT], { + cwd: ROOT, + stdio: "inherit", +}); +// wrangler owns the terminal UX; when it exits (its `x` hotkey, a crash), +// take the whole loop down with it. +wrangler.on("close", (code) => shutdown(code ?? 0)); + +startWatchers(); +log(`watching src/, public/, and root config — wrangler serves http://localhost:${PORT}`); +log( + STRICT + ? "strict mode: links validator ON — a broken link fails the build here" + : "links validator skipped in watch builds (CI still enforces; DOCS_WATCH_STRICT=1 to keep it on)", +); diff --git a/docs/scripts/screenshot.mjs b/docs/scripts/screenshot.mjs index 64d7f44d..27eeaa16 100644 --- a/docs/scripts/screenshot.mjs +++ b/docs/scripts/screenshot.mjs @@ -1,7 +1,7 @@ /* Visual regression / preview screenshots for the WaveHouse docs site. * Run with: pnpm exec node scripts/screenshot.mjs - * Requires the dev server to be running on http://127.0.0.1:4321 - * (the Astro default, which is also what `make docs-dev` starts). */ + * Requires the docs server to be running on http://127.0.0.1:4321 + * (what `make dev-docs` serves on; `pnpm run start` lands there too). */ import { mkdir } from "node:fs/promises"; import { resolve } from "node:path"; diff --git a/docs/src/content/docs/development.md b/docs/src/content/docs/development.md index a323c101..49aef8ee 100644 --- a/docs/src/content/docs/development.md +++ b/docs/src/content/docs/development.md @@ -28,7 +28,7 @@ Run `make tools` once after cloning to populate everything that doesn't have to - **`golangci-lint` v2.11.4** → installed to `.bin/_/` (version-pinned in the Makefile; bumping the version triggers a reinstall). Not in `go.mod` because its dependency tree conflicts with the main module. - **`air` v1.65.1** → installed to `.bin/_/` via `go install`; used by `make dev` for hot-reload. Same exclusion principle as `golangci-lint` — air's transitive deps (Hugo, Sass libs) would bloat `go.sum`. - **Go `tool` deps** (`gotestsum`, `gofumpt`, `goimports`, `govulncheck`, `go-test-coverage`, `deadcode`, `gsa`, `goda`) — pinned in `go.mod` via native `tool` directives (Go 1.24+), invoked with `go tool `. `make tools` runs `go mod download` so they're cached; they compile lazily on first invocation. -- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time) and `starlight-links-validator`. The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. +- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time) and — under `build-docs` / CI only — `starlight-links-validator`; the `dev-docs` watch loop skips the validator so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. ### Verify your setup diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92bbf846..c3b0e79d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,18 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vitest: + specifier: ^4.1.6 + version: 4.1.7 + importers: .: @@ -63,8 +75,8 @@ importers: specifier: ^6.1.5 version: 6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0) astro-themed-mermaid: - specifier: github:Wave-RF/astro-themed-mermaid#v0.1.1 - version: https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/819f1ce334253d80be7235a77a123e68472efa38(astro@6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0))(rehype-mermaid@3.0.0(playwright@1.60.0)) + specifier: github:Wave-RF/astro-themed-mermaid#v0.2.0 + version: https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/938f765559c7afa706b460c009bdfa0566d0a621(astro@6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0))(rehype-mermaid@3.0.0(playwright@1.60.0)) cloudflare-md-router: specifier: github:Wave-RF/cloudflare-md-router version: https://codeload.github.com/Wave-RF/cloudflare-md-router/tar.gz/792cfd3f34cef958cbe54d20ceb71011e2549e42(@cloudflare/workers-types@4.20260521.1) @@ -1768,9 +1780,9 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta - astro-themed-mermaid@https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/819f1ce334253d80be7235a77a123e68472efa38: - resolution: {gitHosted: true, tarball: https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/819f1ce334253d80be7235a77a123e68472efa38} - version: 0.1.1 + astro-themed-mermaid@https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/938f765559c7afa706b460c009bdfa0566d0a621: + resolution: {gitHosted: true, integrity: sha512-Qy+9g1unw6V5V6+Je6wNfiaLgUcZ/9YkCjcfXxLogBP1aOzMZNGmSVRt4KCxpSRUHOWU7zXNbb/5D4p1FTjyHw==, tarball: https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/938f765559c7afa706b460c009bdfa0566d0a621} + version: 0.2.0 engines: {node: '>=20'} peerDependencies: astro: '>=5.0.0' @@ -5496,7 +5508,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 24.12.4 + '@types/node': 25.9.1 '@types/trusted-types@2.0.7': optional: true @@ -5691,7 +5703,7 @@ snapshots: astro: 6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0) rehype-expressive-code: 0.42.0 - astro-themed-mermaid@https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/819f1ce334253d80be7235a77a123e68472efa38(astro@6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0))(rehype-mermaid@3.0.0(playwright@1.60.0)): + astro-themed-mermaid@https://codeload.github.com/Wave-RF/astro-themed-mermaid/tar.gz/938f765559c7afa706b460c009bdfa0566d0a621(astro@6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0))(rehype-mermaid@3.0.0(playwright@1.60.0)): dependencies: astro: 6.3.7(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.3)(yaml@2.9.0) rehype-mermaid: 3.0.0(playwright@1.60.0) From 0b54c502f2151717e2bcb1e6d648801b3472b767 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 14:14:27 -0400 Subject: [PATCH 04/12] docs: refresh stale CI/review-automation references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contributor docs (development.md §CI & review automation, CONTRIBUTING.md) still described the pre-consolidation pipeline: pr-title.yml and a `Validate` check, `Check`/`Build` required contexts, project-orchestrator.yml card movement, and label.yml — all replaced by housekeeping.yml / the single `CI` job / native Projects v2 board workflows (May 2026 consolidation, #115). Rewritten against the live config: required checks are CI, PR housekeeping (Conventional Commits title + 72-char cap, labeler step, reviewer assignment on open/ready), and Admin approval; the inverted "Lint/Test not required (#57)" note is dropped. Also fixes a Chromium misattribution carried in prose and two Makefile comments — starlight-links-validator needs no browser; only rehype-mermaid does — and adds docs/.dev-dist/ to make clean's help text and the development.md targets table. Found by the docs-reviewer pre-push gate (5 MUST / 1 SHOULD / 1 MAY, all addressed). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 ++ CONTRIBUTING.md | 2 +- Makefile | 11 ++++++----- docs/src/content/docs/development.md | 28 +++++++++++++--------------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 606dea36..0f9f5552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **development.md & CONTRIBUTING.md no longer describe the pre-consolidation CI / review automation** (`docs/src/content/docs/development.md` §CI & review automation, `CONTRIBUTING.md`, `Makefile` comments): the contributor docs still referenced `pr-title.yml`/`Validate`, `Check`/`Build` status checks, `project-orchestrator.yml`, and `label.yml` — all replaced or deleted by the May 2026 workflow consolidation (`housekeeping.yml`) and #115 — and described card movement and reviewer re-requests the orchestrator no longer performs. Rewritten against the live config: required checks are `CI` (one job running `make ci`), `PR housekeeping` (Conventional Commits title + the previously undocumented 72-char cap, file-path labels via the labeler step, reviewer assignment on open/ready), and `Admin approval`; board placement is native Projects v2 workflows; the stale "Lint/Test not required (#57)" note is gone (those suites run inside the required `CI` check). Also corrects a misattribution this branch had propagated: `starlight-links-validator` does not use Chromium — only `rehype-mermaid` does (fixed in the prose and two `Makefile` comments), and `make clean`'s help text/targets table now mention `docs/.dev-dist/`. + - **Theme-adaptive favicon that actually displays** (`docs/astro.config.mjs`, `docs/src/components/Head.astro`, `docs/scripts/branding/generate.sh`, `docs/public/branding/favicon-{light,dark}.svg` (new)): the CSS-swap `favicon.svg` has carried a `prefers-color-scheme` flip since the branding pipeline landed, but no browser ever showed it — the docs final pass (#193) dropped the explicit `sizes="any"` SVG icon link, so Chromium selected the static `.ico`, and Chromium rasterizes an SVG favicon only once regardless (crbug.com/1208277). The head now declares the numeric-sized `.ico` + `sizes="any"` SVG pair, the brand kit gains statically-colored `favicon-{light,dark}.svg` variants (two new copy-out assets), and a `Head.astro` inline script live-swaps them on `prefers-color-scheme` changes, GitHub-style: replacing the link node each repaint (in-place `href` mutation is honored only transiently) and removing the `.ico` from the live candidate list, since Chromium's favicon scorer otherwise commits an exact-size `.ico` over a `sizes="any"` SVG (verified against the browser's profile `Favicons` DB rather than blog lore). No-JS consumers keep the full SSR markup (CSS-swap `favicon.svg` + `.ico`); Safari ≤18, which can't use SVG favicons, falls back to the root `/favicon.ico` auto-probe that the brand pipeline keeps in place. Supersedes the favicon wiring described in the branding-pipeline entry below (`favicon:` option + `.ico`/apple-touch head tags only). - **TS SDK no longer hardcodes `ORDER BY received_timestamp DESC` as the default query order, so `wh.from(table).fetch()` works on any schema** (`clients/ts/src/query-builder.ts`, `clients/ts/src/query-builder.test.ts`, `tests/e2e/sdk/query.test.ts`, `docs/src/content/docs/sdk.md`): closes #270. The query builder previously injected `ORDER BY received_timestamp DESC` as both the default query order (`_buildAST`) and the cursor-pagination column (`_fetchNext`), so a bare `.fetch()` against a bring-your-own-schema table lacking that column emitted invalid SQL → ClickHouse `Unknown expression identifier received_timestamp` → HTTP 500. The SDK now emits an `ORDER BY` only when the caller sets one via `.orderBy()`, and sends no implicit order otherwise. **Soft behavior change:** `.fetch()` still reports `hasMore` honestly from the row count, but cursor pagination's `result.next` is now `undefined` (rather than always defined when `hasMore` is `true`) unless the query has an explicit `.orderBy()`; callers paginating via `await result.next!()` must guard on `result.next` (the `sdk.md` / README examples already do). This also sidesteps the #175 default-cursor tie bug — there is no longer a default cursor to tie on. A backend-owned, per-table default sort order (designed together with server-side pagination, which would subsume #175) is tracked as a follow-up (#274). Stream dedup in `clients/ts/src/stream/live-query.ts` / `stream/sse.ts` still uses `received_timestamp` and is intentionally out of scope (the live ingest stream always carries that column). Builds on #199 (query-builder structure). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7cdc4486..3178fce4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,7 +76,7 @@ Types: - `revert` — Reverting a previous commit - `style` — Formatting changes with no functional effect -The PR title is enforced via the `Lint PR title` workflow; it becomes the squash-merge commit message on `main`, so keep it parseable. +The PR title is enforced by the required `PR housekeeping` status check (`.github/workflows/housekeeping.yml`); it becomes the squash-merge commit message on `main`, so keep it parseable — and at most 72 characters. Examples: diff --git a/Makefile b/Makefile index adbdca9e..8d8dce84 100644 --- a/Makefile +++ b/Makefile @@ -531,9 +531,10 @@ check-docs: pnpm-install ## Type-check the docs (astro check — types + content $(call run,check-docs (astro check),NODE_OPTIONS=--no-deprecation $(PNPM) -s --filter $(DOCS_FILTER) run check,) # build-docs: Astro site → docs/dist/. Pulls in Chromium (install-playwright-docs) -# because rehype-mermaid renders diagrams via headless Chrome at build time and -# starlight-links-validator needs it too. Depends on check-docs so a type/content -# error fails fast before the (heavier) build, and the two never race on .astro. +# because rehype-mermaid renders diagrams via headless Chrome at build time +# (starlight-links-validator runs here too, but needs no browser). Depends on +# check-docs so a type/content error fails fast before the (heavier) build, +# and the two never race on .astro. .PHONY: build-docs build-docs: check-docs install-playwright-docs ## Build docs site → docs/dist/ @echo "$(CYAN)==> Building docs site...$(RESET)" @@ -587,7 +588,7 @@ pnpm-install: @$(PNPM) install --frozen-lockfile --reporter=silent # install-playwright-docs: hidden helper — fetch the Chromium build the docs -# site needs (rehype-mermaid build-time SSR + starlight-links-validator). It's +# site needs (rehype-mermaid build-time SSR; nothing else uses a browser). It's # ~130 MB, so it's lazy: only the docs build/dev/preview targets pull it in, # never plain pnpm-install, so Go-only contributors don't pay for it. The # --with-deps apt step needs sudo and only helps on CI's minimal images, so @@ -786,7 +787,7 @@ binary-analysis: size audit-cgo deadcode ## Combined: size + audit-cgo + deadcod # clean-all everything above + data/ + docker volumes (full reset) .PHONY: clean -clean: ## Remove build artifacts (bin/, dist/, clients/ts/dist/, docs/dist/) +clean: ## Remove build artifacts (bin/, dist/, clients/ts/dist/, docs/dist/, docs/.dev-dist/) @echo "$(YELLOW)==> Cleaning build artifacts...$(RESET)" @rm -rf bin/ dist/ clients/ts/dist/ docs/dist/ docs/.dev-dist/ diff --git a/docs/src/content/docs/development.md b/docs/src/content/docs/development.md index 49aef8ee..424146a1 100644 --- a/docs/src/content/docs/development.md +++ b/docs/src/content/docs/development.md @@ -28,7 +28,7 @@ Run `make tools` once after cloning to populate everything that doesn't have to - **`golangci-lint` v2.11.4** → installed to `.bin/_/` (version-pinned in the Makefile; bumping the version triggers a reinstall). Not in `go.mod` because its dependency tree conflicts with the main module. - **`air` v1.65.1** → installed to `.bin/_/` via `go install`; used by `make dev` for hot-reload. Same exclusion principle as `golangci-lint` — air's transitive deps (Hugo, Sass libs) would bloat `go.sum`. - **Go `tool` deps** (`gotestsum`, `gofumpt`, `goimports`, `govulncheck`, `go-test-coverage`, `deadcode`, `gsa`, `goda`) — pinned in `go.mod` via native `tool` directives (Go 1.24+), invoked with `go tool `. `make tools` runs `go mod download` so they're cached; they compile lazily on first invocation. -- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time) and — under `build-docs` / CI only — `starlight-links-validator`; the `dev-docs` watch loop skips the validator so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. +- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time; nothing else in the docs toolchain uses a browser). `starlight-links-validator` runs under `build-docs` / CI only — the `dev-docs` watch loop skips it so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. ### Verify your setup @@ -473,7 +473,7 @@ Run `make help` to see all targets. Key ones: | `make dep-cut` | Top cuttable deps by transitive weight (`LIMIT=N` to override) | | `make binary-analysis` | Combined: `size` + `audit-cgo` + `deadcode` | | **Cleanup** (tiered — compose explicitly for partial resets) | | -| `make clean` | Build outputs only (`bin/`, `dist/`, `clients/ts/dist/`, `docs/dist/`) | +| `make clean` | Build outputs only (`bin/`, `dist/`, `clients/ts/dist/`, `docs/dist/`, `docs/.dev-dist/`) | | `make clean-test` | Test outputs only (`tmp/` — coverage data, logs, NATS state) | | `make clean-tools` | Installed tools and pnpm deps (`.bin/`, `node_modules/`) | | `make clean-all` | Full reset: above + `data/` + Docker volumes | @@ -518,7 +518,7 @@ This repo has three tiers of AI automation sitting alongside the normal CI check ### PR title and Conventional Commits -PR titles must match Conventional Commits format (enforced by `.github/workflows/pr-title.yml` as the required `Validate` status check): +PR titles must match Conventional Commits format (enforced by `.github/workflows/housekeeping.yml` as the required `PR housekeeping` status check): ```text (optional-scope)(optional-!): @@ -526,7 +526,7 @@ PR titles must match Conventional Commits format (enforced by `.github/workflows Allowed types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `deps`, `build`, `perf`, `revert`, `style`. -The `!` before `:` marks a breaking change per Conventional Commits 1.0.0 (e.g., `feat!: remove deprecated endpoint`, `refactor(api)!: rename handlers`). +The `!` before `:` marks a breaking change per Conventional Commits 1.0.0 (e.g., `feat!: remove deprecated endpoint`, `refactor(api)!: rename handlers`). Titles are also capped at **72 characters** — they become squash-merge commit subjects (Dependabot PRs are exempt from the cap). If the title doesn't match, a sticky comment posts on the PR explaining the format; it auto-removes once the title is fixed. @@ -534,16 +534,14 @@ If the title doesn't match, a sticky comment posts on the PR explaining the form The `main branch protection` ruleset requires the following checks to pass before any PR can merge: -- `Check` — module tidiness, format verification, vulnerability scan -- `Build` — compile all binaries -- `Validate` — PR title is Conventional Commits +- `CI` — the full pipeline (`.github/workflows/ci.yml`, one job running `make ci`): static checks (tidy, format, lint, vulncheck), all builds, unit + SDK + integration + E2E suites, and the merged coverage gates +- `PR housekeeping` — Conventional Commits title (+ 72-char cap), file-path labels, reviewer assignment (`.github/workflows/housekeeping.yml`) +- `Admin approval` — at least one `APPROVED` review specifically from an admin (Eric or Taite), enforced by `.github/workflows/admin-approval.yml` -Plus 1 approving review, and the `Admin approval` check (enforced by `.github/workflows/admin-approval.yml`) requires at least one `APPROVED` review specifically from an admin (Eric or Taite). Linear history, no deletion, no force-push, squash-merge only. +Plus 1 approving review. Linear history, no deletion, no force-push, squash-merge only. Dependabot PRs bypass `Admin approval` (`dependabot-automerge.yml` handles patch/minor bumps hands-off once CI is green; majors get a comment and stay open for human review). -> **Note — temporarily relaxed**: `Lint`, `Test`, and `Integration Tests` are *not* currently required while pre-existing failures on `main` are being fixed (tracked in #57). They'll rejoin required once main is green. - ### Merge behavior Squash-only merges. The **PR title** becomes the commit subject (with `(#NN)` appended automatically), the **PR body** becomes the commit message. Keep PR bodies tight — they land in `git log` on `main`. The PR template gives the right shape (Summary / Test plan / Related Issues). @@ -563,11 +561,11 @@ Both are **advisory** — the `Admin approval` status check (admin review mandat ### Task Board is the single signal -`.github/workflows/project-orchestrator.yml` drives the Task Board (project #7) as the real "who needs to look at this next" channel. GitHub's review-request notifications are treated as noise; what matters is the position of your assigned card on the board. +The Task Board (project #7) is the real "who needs to look at this next" channel. Reviewer assignment is workflow-driven (`housekeeping.yml`); card placement and movement are handled by native Projects v2 workflows configured in the project UI — there is no on-repo workflow moving cards. -- **Coder flow**: open PR (draft or not) → address bot feedback → once all required checks pass and all review threads are resolved, the orchestrator adds the PR to the board, sets its Status to `Ready`, and assigns the non-author admin. You're done for now. -- **Reviewer flow**: PR card shows up on your board in `Ready`. You move it to `In progress` when you start reviewing (this is the one manual step). You review. Either (a) approve → `admin-approval.yml` passes, auto-merge takes over, card auto-flips to `Done`; or (b) click "Request changes" → orchestrator moves PR card to `In review`, linked issue card to `Ready` (now the coder's ball). -- **Coder addressing feedback**: push fixes, resolve threads, then click "re-request review" on your reviewer in GitHub's sidebar (this is the trigger the orchestrator listens for). Orchestrator moves PR card back to `Ready`, issue card back to `In review`. Reviewer sees the card returned to their column. +- **Coder flow**: open a PR ready-for-review (or flip a draft to ready) → `housekeeping.yml` requests review from the non-author admin and sets assignees (an admin author gets the *other* admin; otherwise reviewers round-robin by PR number; drafts and Dependabot PRs are skipped). Address bot feedback and resolve threads as you go. You're done for now. +- **Reviewer flow**: the PR card shows up on your board via the Projects v2 built-in workflows. You move it to `In progress` when you start reviewing. Either (a) approve → `admin-approval.yml` passes, auto-merge (if enabled) takes over; or (b) "Request changes" → the ball is back with the coder. +- **Coder addressing feedback**: push fixes, resolve threads, then click "re-request review" on your reviewer in GitHub's sidebar. This step is deliberately manual — `dismiss_stale_reviews_on_push` clears stale approvals, and the workflow doesn't re-request on every push so reviewers aren't re-pinged before you're actually ready. Dependabot PRs bypass `Admin approval` (`dependabot-automerge.yml` handles patch/minor bumps hands-off once CI is green; majors get a comment and stay open for human review). Dependabot PRs do not appear on the Task Board. @@ -593,7 +591,7 @@ When pushing back on a bot's suggestion, end the reply with the bot's mention (e ### Auto-labeling PRs -`.github/workflows/label.yml` uses `actions/labeler` with `.github/labeler.yml` to apply `area/*`, `dependencies`, `github_actions`, `go`, and `documentation` labels to PRs based on the files they change. Sync-mode: labels follow the current changed-file set. +A step in `.github/workflows/housekeeping.yml` uses `actions/labeler` with `.github/labeler.yml` to apply `area/*`, `dependencies`, `github_actions`, `go`, and `documentation` labels to PRs based on the files they change. Sync-mode: labels follow the current changed-file set. ### When adding a new `internal//` package From 06d0a4b6d1b41a1a11ec027c71d53360309dbaee Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 14:25:48 -0400 Subject: [PATCH 05/12] docs: scope browser claims to the build; fix renamed AGENTS.md heading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 reviewer findings: "nothing else uses a browser" overclaimed — the manual scripts/screenshot.mjs QA helper launches the same Playwright Chromium (scoped the claim to the docs *build* in development.md and the install-playwright-docs Makefile comment), and development.md still quoted AGENTS.md §"Review Response (MANDATORY)", renamed to §"Review Response" in #129. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 3 ++- docs/src/content/docs/development.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8d8dce84..c07ffbb4 100644 --- a/Makefile +++ b/Makefile @@ -588,7 +588,8 @@ pnpm-install: @$(PNPM) install --frozen-lockfile --reporter=silent # install-playwright-docs: hidden helper — fetch the Chromium build the docs -# site needs (rehype-mermaid build-time SSR; nothing else uses a browser). It's +# site needs (rehype-mermaid build-time SSR is the build's only browser use; +# the manual scripts/screenshot.mjs helper drives the same install). It's # ~130 MB, so it's lazy: only the docs build/dev/preview targets pull it in, # never plain pnpm-install, so Go-only contributors don't pay for it. The # --with-deps apt step needs sudo and only helps on CI's minimal images, so diff --git a/docs/src/content/docs/development.md b/docs/src/content/docs/development.md index 424146a1..54296d11 100644 --- a/docs/src/content/docs/development.md +++ b/docs/src/content/docs/development.md @@ -28,7 +28,7 @@ Run `make tools` once after cloning to populate everything that doesn't have to - **`golangci-lint` v2.11.4** → installed to `.bin/_/` (version-pinned in the Makefile; bumping the version triggers a reinstall). Not in `go.mod` because its dependency tree conflicts with the main module. - **`air` v1.65.1** → installed to `.bin/_/` via `go install`; used by `make dev` for hot-reload. Same exclusion principle as `golangci-lint` — air's transitive deps (Hugo, Sass libs) would bloat `go.sum`. - **Go `tool` deps** (`gotestsum`, `gofumpt`, `goimports`, `govulncheck`, `go-test-coverage`, `deadcode`, `gsa`, `goda`) — pinned in `go.mod` via native `tool` directives (Go 1.24+), invoked with `go tool `. `make tools` runs `go mod download` so they're cached; they compile lazily on first invocation. -- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time; nothing else in the docs toolchain uses a browser). `starlight-links-validator` runs under `build-docs` / CI only — the `dev-docs` watch loop skips it so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. +- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time; nothing else in the docs *build* uses a browser — the manual `scripts/screenshot.mjs` QA helper drives the same Chromium). `starlight-links-validator` runs under `build-docs` / CI only — the `dev-docs` watch loop skips it so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. ### Verify your setup @@ -576,7 +576,7 @@ Dependabot PRs bypass `Admin approval` (`dependabot-automerge.yml` handles patch ### Review-response expectations -Every review comment (human or AI) must get a substantive reply before merge — not "fixed" alone. The ruleset's `required_review_thread_resolution: true` means unresolved conversations literally block merge. Agents working on PRs follow the pattern documented in `AGENTS.md` §"Review Response (MANDATORY)": accept / push back / defer, reply with detail, resolve when settled. +Every review comment (human or AI) must get a substantive reply before merge — not "fixed" alone. The ruleset's `required_review_thread_resolution: true` means unresolved conversations literally block merge. Agents working on PRs follow the pattern documented in `AGENTS.md` §"Review Response": accept / push back / defer, reply with detail, resolve when settled. When pushing back on a bot's suggestion, end the reply with the bot's mention (e.g. `@coderabbitai`) to invite a counter-reply so the dialog actually loops. From bf19ac653e0c2c9240b47b656eb09256178a65ef Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 14:32:37 -0400 Subject: [PATCH 06/12] docs: qualify screenshot helper path as docs/scripts/screenshot.mjs Both round-3 reviewers flagged the same single remainder: the bare `scripts/screenshot.mjs` form added in 06d0a4b reads as repo-root `scripts/` (which has no such file) in the root Makefile comment, development.md's repo-root path convention, and the CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- Makefile | 2 +- docs/src/content/docs/development.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9f5552..f6b4cc2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **`make dev-docs` is now production-faithful: rebuild-on-save through the real Worker instead of `astro dev`** (`docs/scripts/dev.mjs` (new), `docs/package.json`, `docs/astro.config.mjs`, `Makefile`, `AGENTS.md`, `.gitignore`, `.vscode/launch.json`, `docs/scripts/screenshot.mjs`, `docs/src/content/docs/development.md`): the Astro dev server skips everything the Cloudflare Worker adds in production — `cloudflare-md-router` content negotiation (`.md` twins), the pagefind search index, and the `starlight-llm-tools` outputs only exist in real builds — so the de-facto docs workflow had become a manual `make build-docs && make preview-docs` cycle, restarted by hand per change. `pnpm run dev` (and therefore `make dev-docs`) now drives `docs/scripts/dev.mjs`: a debounced full `astro build` on every save to `src/`, `public/`, or a root build input (`astro.config.mjs`, `tsconfig.json`, `package.json`, `.env*` — but not `worker/index.ts`/`wrangler.jsonc`, which `wrangler dev` hot-reloads itself, and not `pnpm-lock.yaml`, which changes on `git pull` before `pnpm install` has run), with saves landing mid-build coalescing into one follow-up build, staged into a gitignored `docs/.dev-dist/` and synced in-place into `dist/` only on success (plain node fs, prune-then-copy — no rsync or any other external tool, so minimal WSL/container setups work) — a failed build prints a red banner + terminal bell and keeps serving the last good site rather than the emptied `outDir` a direct in-place build would leave — while `wrangler dev --live-reload` on :4321 (`DOCS_PORT` overrides; same default as before, so `scripts/screenshot.mjs` and muscle memory still work; one-off `preview-docs` stays on :8787) picks up the asset changes live, new routes included, and refreshes the browser over its websocket. Each rebuild is a real production build with one deliberate exception: the loop sets `WAVEHOUSE_DOCS_WATCH=1`, under which `astro.config.mjs` drops `starlight-links-validator` — mid-edit prose always has dangling links, and failing every rebuild over them would mean never seeing the change being made; rendered output is identical, CI / `make build-docs` still enforce link validity, and `DOCS_WATCH_STRICT=1` keeps the validator on in watch builds for those who want broken links to fail loudly at edit time (the development.md tooling notes now spell out that split; build errors themselves — broken Mermaid, bad frontmatter, MDX errors — always fail the watch build loudly either way). The raw HMR dev server remains available as `pnpm --filter wavehouse-docs run start` for fidelity-insensitive iteration (e.g. CSS tweaking) — the VS Code launch config gains a second entry for it, with the existing `pnpm dev` entry relabeled to match its new behavior. +- **`make dev-docs` is now production-faithful: rebuild-on-save through the real Worker instead of `astro dev`** (`docs/scripts/dev.mjs` (new), `docs/package.json`, `docs/astro.config.mjs`, `Makefile`, `AGENTS.md`, `.gitignore`, `.vscode/launch.json`, `docs/scripts/screenshot.mjs`, `docs/src/content/docs/development.md`): the Astro dev server skips everything the Cloudflare Worker adds in production — `cloudflare-md-router` content negotiation (`.md` twins), the pagefind search index, and the `starlight-llm-tools` outputs only exist in real builds — so the de-facto docs workflow had become a manual `make build-docs && make preview-docs` cycle, restarted by hand per change. `pnpm run dev` (and therefore `make dev-docs`) now drives `docs/scripts/dev.mjs`: a debounced full `astro build` on every save to `src/`, `public/`, or a root build input (`astro.config.mjs`, `tsconfig.json`, `package.json`, `.env*` — but not `worker/index.ts`/`wrangler.jsonc`, which `wrangler dev` hot-reloads itself, and not `pnpm-lock.yaml`, which changes on `git pull` before `pnpm install` has run), with saves landing mid-build coalescing into one follow-up build, staged into a gitignored `docs/.dev-dist/` and synced in-place into `dist/` only on success (plain node fs, prune-then-copy — no rsync or any other external tool, so minimal WSL/container setups work) — a failed build prints a red banner + terminal bell and keeps serving the last good site rather than the emptied `outDir` a direct in-place build would leave — while `wrangler dev --live-reload` on :4321 (`DOCS_PORT` overrides; same default as before, so `docs/scripts/screenshot.mjs` and muscle memory still work; one-off `preview-docs` stays on :8787) picks up the asset changes live, new routes included, and refreshes the browser over its websocket. Each rebuild is a real production build with one deliberate exception: the loop sets `WAVEHOUSE_DOCS_WATCH=1`, under which `astro.config.mjs` drops `starlight-links-validator` — mid-edit prose always has dangling links, and failing every rebuild over them would mean never seeing the change being made; rendered output is identical, CI / `make build-docs` still enforce link validity, and `DOCS_WATCH_STRICT=1` keeps the validator on in watch builds for those who want broken links to fail loudly at edit time (the development.md tooling notes now spell out that split; build errors themselves — broken Mermaid, bad frontmatter, MDX errors — always fail the watch build loudly either way). The raw HMR dev server remains available as `pnpm --filter wavehouse-docs run start` for fidelity-insensitive iteration (e.g. CSS tweaking) — the VS Code launch config gains a second entry for it, with the existing `pnpm dev` entry relabeled to match its new behavior. - **Docs Mermaid diagrams render through a per-diagram disk cache** (`docs/astro.config.mjs`, `docs/package.json`, `pnpm-lock.yaml`; the cache itself ships in `astro-themed-mermaid` v0.2.0): diagram SSR runs headless Chromium via `rehype-mermaid` and re-rendered all 17 diagrams on *every* docs build — including builds that touched no diagram, i.e. almost all of them. The config now uses the package's new `mermaid.rehypeMermaid` export, which wraps `rehype-mermaid` with a content-addressed cache under `node_modules/.cache/astro-themed-mermaid/` (keyed on diagram source + render options + package versions, so theme and toolchain changes self-invalidate; entries are re-id'd to content-derived `mermaid-c` ids so cross-build entries can't collide on a page). A build whose diagrams all hit skips Chromium — and even the `rehype-mermaid`/playwright import — entirely: measured warm rebuilds drop from ~6–7s to ~3.7s, a single-diagram edit re-renders only its page (~5s), and output was verified byte-identical modulo SVG ids. Cold builds (fresh clone, CI, `make clean-tools`) render once and harvest; cache I/O is best-effort and degrades to a normal render, never a failed build. diff --git a/Makefile b/Makefile index c07ffbb4..1a2d0d10 100644 --- a/Makefile +++ b/Makefile @@ -589,7 +589,7 @@ pnpm-install: # install-playwright-docs: hidden helper — fetch the Chromium build the docs # site needs (rehype-mermaid build-time SSR is the build's only browser use; -# the manual scripts/screenshot.mjs helper drives the same install). It's +# the manual docs/scripts/screenshot.mjs helper drives the same install). It's # ~130 MB, so it's lazy: only the docs build/dev/preview targets pull it in, # never plain pnpm-install, so Go-only contributors don't pay for it. The # --with-deps apt step needs sudo and only helps on CI's minimal images, so diff --git a/docs/src/content/docs/development.md b/docs/src/content/docs/development.md index 54296d11..7c5573cc 100644 --- a/docs/src/content/docs/development.md +++ b/docs/src/content/docs/development.md @@ -28,7 +28,7 @@ Run `make tools` once after cloning to populate everything that doesn't have to - **`golangci-lint` v2.11.4** → installed to `.bin/_/` (version-pinned in the Makefile; bumping the version triggers a reinstall). Not in `go.mod` because its dependency tree conflicts with the main module. - **`air` v1.65.1** → installed to `.bin/_/` via `go install`; used by `make dev` for hot-reload. Same exclusion principle as `golangci-lint` — air's transitive deps (Hugo, Sass libs) would bloat `go.sum`. - **Go `tool` deps** (`gotestsum`, `gofumpt`, `goimports`, `govulncheck`, `go-test-coverage`, `deadcode`, `gsa`, `goda`) — pinned in `go.mod` via native `tool` directives (Go 1.24+), invoked with `go tool `. `make tools` runs `go mod download` so they're cached; they compile lazily on first invocation. -- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time; nothing else in the docs *build* uses a browser — the manual `scripts/screenshot.mjs` QA helper drives the same Chromium). `starlight-links-validator` runs under `build-docs` / CI only — the `dev-docs` watch loop skips it so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. +- **pnpm deps** for `clients/ts/`, `tests/e2e/sdk/`, and `docs/` (via `pnpm install --frozen-lockfile`). `make tools` runs only the pnpm install; the Playwright Chromium binary (~130 MB) is fetched on-demand by `make build-docs` / `make dev-docs` via the internal `install-playwright-docs` target, so Go-only contributors don't pay the download cost. When you do hit `build-docs` / `dev-docs`, Chromium is required by `rehype-mermaid` (SVG diagram rendering at build time; nothing else in the docs *build* uses a browser — the manual `docs/scripts/screenshot.mjs` QA helper drives the same Chromium). `starlight-links-validator` runs under `build-docs` / CI only — the `dev-docs` watch loop skips it so a mid-edit dangling link doesn't fail every rebuild (CI still enforces link validity before merge; run `DOCS_WATCH_STRICT=1 make dev-docs` to keep the validator on locally). The `--with-deps` flag (which apt-installs Chromium's system libraries: `libnspr4`, `libnss3`, etc.) is only added when `$CI` is set, so contributor laptops don't get an unexpected `sudo` prompt. On Linux dev machines without those libs already present, run `pnpm exec playwright install-deps chromium` once manually. The docs site is a pnpm workspace package (`wavehouse-docs`); the root Makefile drives it directly via `pnpm --filter` (no sub-Makefile) — the `*-docs` targets show up in `make help`. ### Verify your setup From 2083e8aad419e058045235f519ebfdb800e521d8 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 14:42:20 -0400 Subject: [PATCH 07/12] docs: correct admin-approval ruleset comment; complete AGENTS clean list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two reviewer-flagged follow-ups from the docs-accuracy sweep: admin-approval.yml's header claimed the ruleset's required_approving_review_count is 0 — the live ruleset says 1 (any collaborator); the status check is what makes one of those approvals specifically an admin's. And AGENTS.md's `make clean` one-liner omitted the docs build outputs (docs/dist/ predating this branch, docs/.dev-dist/ added by it) that the Makefile help text lists. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/admin-approval.yml | 3 ++- AGENTS.md | 2 +- CHANGELOG.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/admin-approval.yml b/.github/workflows/admin-approval.yml index 37754016..64c28671 100644 --- a/.github/workflows/admin-approval.yml +++ b/.github/workflows/admin-approval.yml @@ -21,7 +21,8 @@ name: Admin approval # to admin review like any human PR — closed a hole where a bot's # APPROVED review (e.g. CodeRabbit) could merge a major bump (see # #130, PR #127). The ruleset's `required_approving_review_count` -# is 0; this status check is the single admin-review gate. +# is 1 (any collaborator's approval satisfies it); this status check +# is what makes one of those approvals specifically an admin's. on: pull_request: diff --git a/AGENTS.md b/AGENTS.md index d510eb66..54967d22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,7 +113,7 @@ make deps-shell # clickhouse-client REPL on the running container make deps-wipe # Stop AND destroy ClickHouse data volume (DESTRUCTIVE) # Cleanup (tiered — compose explicitly for partial resets) -make clean # Build outputs only (bin/, dist/, clients/ts/dist/) +make clean # Build outputs only (bin/, dist/, clients/ts/dist/, docs/dist/, docs/.dev-dist/) make clean-test # Test outputs only (tmp/ — coverage, logs, NATS state) make clean-tools # Installed tools and pnpm deps (.bin/, node_modules/) make clean-all # Full reset: above + data/ + docker volumes diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b4cc2e..8fb46392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- **development.md & CONTRIBUTING.md no longer describe the pre-consolidation CI / review automation** (`docs/src/content/docs/development.md` §CI & review automation, `CONTRIBUTING.md`, `Makefile` comments): the contributor docs still referenced `pr-title.yml`/`Validate`, `Check`/`Build` status checks, `project-orchestrator.yml`, and `label.yml` — all replaced or deleted by the May 2026 workflow consolidation (`housekeeping.yml`) and #115 — and described card movement and reviewer re-requests the orchestrator no longer performs. Rewritten against the live config: required checks are `CI` (one job running `make ci`), `PR housekeeping` (Conventional Commits title + the previously undocumented 72-char cap, file-path labels via the labeler step, reviewer assignment on open/ready), and `Admin approval`; board placement is native Projects v2 workflows; the stale "Lint/Test not required (#57)" note is gone (those suites run inside the required `CI` check). Also corrects a misattribution this branch had propagated: `starlight-links-validator` does not use Chromium — only `rehype-mermaid` does (fixed in the prose and two `Makefile` comments), and `make clean`'s help text/targets table now mention `docs/.dev-dist/`. +- **development.md & CONTRIBUTING.md no longer describe the pre-consolidation CI / review automation** (`docs/src/content/docs/development.md` §CI & review automation, `CONTRIBUTING.md`, `Makefile` comments): the contributor docs still referenced `pr-title.yml`/`Validate`, `Check`/`Build` status checks, `project-orchestrator.yml`, and `label.yml` — all replaced or deleted by the May 2026 workflow consolidation (`housekeeping.yml`) and #115 — and described card movement and reviewer re-requests the orchestrator no longer performs. Rewritten against the live config: required checks are `CI` (one job running `make ci`), `PR housekeeping` (Conventional Commits title + the previously undocumented 72-char cap, file-path labels via the labeler step, reviewer assignment on open/ready), and `Admin approval`; board placement is native Projects v2 workflows; the stale "Lint/Test not required (#57)" note is gone (those suites run inside the required `CI` check). Also corrects a misattribution this branch had propagated: `starlight-links-validator` does not use Chromium — only `rehype-mermaid` does (fixed in the prose and two `Makefile` comments), and `make clean`'s help text/targets table now mention `docs/.dev-dist/`. Same sweep, code-comment side: `admin-approval.yml`'s header claimed the ruleset's `required_approving_review_count` was `0` — the live ruleset says `1` (any collaborator), with the workflow's status check being what makes one of those approvals specifically an admin's — and AGENTS.md's `make clean` one-liner now lists the docs build outputs. - **Theme-adaptive favicon that actually displays** (`docs/astro.config.mjs`, `docs/src/components/Head.astro`, `docs/scripts/branding/generate.sh`, `docs/public/branding/favicon-{light,dark}.svg` (new)): the CSS-swap `favicon.svg` has carried a `prefers-color-scheme` flip since the branding pipeline landed, but no browser ever showed it — the docs final pass (#193) dropped the explicit `sizes="any"` SVG icon link, so Chromium selected the static `.ico`, and Chromium rasterizes an SVG favicon only once regardless (crbug.com/1208277). The head now declares the numeric-sized `.ico` + `sizes="any"` SVG pair, the brand kit gains statically-colored `favicon-{light,dark}.svg` variants (two new copy-out assets), and a `Head.astro` inline script live-swaps them on `prefers-color-scheme` changes, GitHub-style: replacing the link node each repaint (in-place `href` mutation is honored only transiently) and removing the `.ico` from the live candidate list, since Chromium's favicon scorer otherwise commits an exact-size `.ico` over a `sizes="any"` SVG (verified against the browser's profile `Favicons` DB rather than blog lore). No-JS consumers keep the full SSR markup (CSS-swap `favicon.svg` + `.ico`); Safari ≤18, which can't use SVG favicons, falls back to the root `/favicon.ico` auto-probe that the brand pipeline keeps in place. Supersedes the favicon wiring described in the branding-pipeline entry below (`favicon:` option + `.ico`/apple-touch head tags only). - **TS SDK no longer hardcodes `ORDER BY received_timestamp DESC` as the default query order, so `wh.from(table).fetch()` works on any schema** (`clients/ts/src/query-builder.ts`, `clients/ts/src/query-builder.test.ts`, `tests/e2e/sdk/query.test.ts`, `docs/src/content/docs/sdk.md`): closes #270. The query builder previously injected `ORDER BY received_timestamp DESC` as both the default query order (`_buildAST`) and the cursor-pagination column (`_fetchNext`), so a bare `.fetch()` against a bring-your-own-schema table lacking that column emitted invalid SQL → ClickHouse `Unknown expression identifier received_timestamp` → HTTP 500. The SDK now emits an `ORDER BY` only when the caller sets one via `.orderBy()`, and sends no implicit order otherwise. **Soft behavior change:** `.fetch()` still reports `hasMore` honestly from the row count, but cursor pagination's `result.next` is now `undefined` (rather than always defined when `hasMore` is `true`) unless the query has an explicit `.orderBy()`; callers paginating via `await result.next!()` must guard on `result.next` (the `sdk.md` / README examples already do). This also sidesteps the #175 default-cursor tie bug — there is no longer a default cursor to tie on. A backend-owned, per-table default sort order (designed together with server-side pagination, which would subsume #175) is tracked as a follow-up (#274). Stream dedup in `clients/ts/src/stream/live-query.ts` / `stream/sse.ts` still uses `received_timestamp` and is intentionally out of scope (the live ingest stream always carries that column). Builds on #199 (query-builder structure). From c598c18b523929a3eda107b0fbf1de3f57330754 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Fri, 5 Jun 2026 15:17:35 -0400 Subject: [PATCH 08/12] minor capitalization tweaks, editor warnings --- docs/src/components/Hero.astro | 6 +-- docs/src/content/docs/index.mdx | 78 ++++++++++++++++----------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/src/components/Hero.astro b/docs/src/components/Hero.astro index 816a0f77..cf17365b 100644 --- a/docs/src/components/Hero.astro +++ b/docs/src/components/Hero.astro @@ -72,7 +72,7 @@ const terminalLines: TerminalLine[] = [ The inline script below runs synchronously during HTML parse — including on view-transition swaps (`data-astro-rerun`) — and stamps `data-hero-played` on before the section paints when the in-memory flag is already set. - CSS in this file's + +``` + +--- + +## src/components/posthog.astro + +```astro +--- +// PostHog analytics snippet with View Transitions support +// Uses is:inline to prevent Astro from processing the script +// Includes initialization guard to prevent stack overflow with ClientRouter +--- + + +``` + +--- + +## src/layouts/PostHogLayout.astro + +```astro +--- +import { ClientRouter } from 'astro:transitions'; +import PostHog from '../components/posthog.astro'; +import Header from '../components/Header.astro'; +import '../styles/global.css'; + +interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + {title} + + + + +
+
+ +
+ + + +``` + +--- + +## src/lib/auth.ts + +```ts +// Client-side auth utilities for localStorage-based authentication + +export interface User { + username: string; + burritoConsiderations: number; +} + +export function getCurrentUser(): User | null { + if (typeof window === "undefined") return null; + + const username = localStorage.getItem("currentUser"); + if (!username) return null; + + const considerations = parseInt( + localStorage.getItem("burritoConsiderations") || "0", + 10, + ); + + return { + username, + burritoConsiderations: considerations, + }; +} + +export function login(username: string, password: string): boolean { + if (!username || !password) return false; + + localStorage.setItem("currentUser", username); + // Initialize burrito considerations if not set + if (!localStorage.getItem("burritoConsiderations")) { + localStorage.setItem("burritoConsiderations", "0"); + } + + return true; +} + +export function logout(): void { + localStorage.removeItem("currentUser"); + localStorage.removeItem("burritoConsiderations"); +} + +export function incrementBurritoConsiderations(): number { + const current = parseInt( + localStorage.getItem("burritoConsiderations") || "0", + 10, + ); + const newCount = current + 1; + localStorage.setItem("burritoConsiderations", newCount.toString()); + return newCount; +} + +``` + +--- + +## src/pages/burrito.astro + +```astro +--- +import PostHogLayout from '../layouts/PostHogLayout.astro'; +--- + +
+

Burrito consideration zone

+

Take a moment to truly consider the potential of burritos.

+ +
+ + + +
+ +
+

Consideration stats

+

Total considerations: 0

+
+
+
+ + + +``` + +--- + +## src/pages/index.astro + +```astro +--- +import PostHogLayout from '../layouts/PostHogLayout.astro'; +--- + +
+ + +
+

Welcome to Burrito Consideration App

+

Please sign in to begin your burrito journey

+ +
+
+ + +
+ +
+ + +
+ + + + +
+ +

+ Note: This is a demo app. Use any username and password to sign in. +

+
+
+
+ + + +``` + +--- + +## src/pages/profile.astro + +```astro +--- +import PostHogLayout from '../layouts/PostHogLayout.astro'; +--- + +
+

User Profile

+ +
+

Your Information

+

Username:

+

Burrito Considerations: 0

+
+ +
+

Your Burrito Journey

+

+
+ +
+

Error Tracking Demo

+

Click the button below to trigger a test error and send it to PostHog:

+ + +
+
+
+ + + +``` + +--- + diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/astro.md b/docs/.claude/skills/integration-astro-view-transitions/references/astro.md new file mode 100644 index 00000000..4b371961 --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/astro.md @@ -0,0 +1,165 @@ +# Astro - Docs + +PostHog makes it easy to get data about traffic and usage of your [Astro](https://astro.build/) app. Integrating PostHog into your site enables analytics about user behavior, custom events capture, session recordings, feature flags, and more. + +This guide walks you through integrating PostHog into your Astro app using the [JavaScript Web SDK](/docs/libraries/js.md). + +## Beta: integration via LLM + +Install PostHog for Astro in seconds with our wizard by running this prompt with [LLM coding agents](/blog/envoy-wizard-llm-agent.md) like Cursor and Bolt, or by running it in your terminal. + +`npx @posthog/wizard@latest` + +[Learn more](/wizard.md) + +Or, to integrate manually, continue with the rest of this guide. + +## Installation + +In your `src/components` folder, create a `posthog.astro` file: + +Terminal + +PostHog AI + +```bash +cd ./src/components +# or 'cd ./src && mkdir components && cd ./components' if your components folder doesnt exist +touch posthog.astro +``` + +In this file, add your `Web snippet` which you can find in [your project settings](https://us.posthog.com/settings/project#snippet). Be sure to include the `is:inline` directive [to prevent Astro from processing it](https://docs.astro.build/en/guides/client-side-scripts/#opting-out-of-processing), or you will get Typescript and build errors that property 'posthog' does not exist on type 'Window & typeof globalThis'. + +posthog.astro + +PostHog AI + +```javascript + +``` + +**Using with Astro's view transitions (ClientRouter)** + +If you've opted in to Astro's `` component for client-side navigation, you'll need to add an initialization guard to prevent PostHog from running multiple times during page transitions. + +Update your `posthog.astro` file to wrap the snippet with a check: + +posthog.astro + +PostHog AI + +```javascript +--- +// src/components/posthog.astro +--- + +``` + +Without this guard, `ClientRouter`'s soft navigation can re-execute the inline script during page transitions, causing a stack overflow error. The `capture_pageview: 'history_change'` option ensures pageviews are tracked automatically as users navigate. + +The next step is to a create a [Layout](https://docs.astro.build/en/core-concepts/layouts/) where we will use `posthog.astro`. Create a new file `PostHogLayout.astro` in your `src/layouts` folder: + +Terminal + +PostHog AI + +```bash +cd .. && cd .. # move back to your base directory if you're still in src/components/posthog.astro +cd ./src/layouts +# or 'cd ./src && mkdir layouts && cd ./layouts' if your layouts folder doesn't exist yet +touch PostHogLayout.astro +``` + +Add the following code to `PostHogLayout.astro`: + +PostHogLayout.astro + +PostHog AI + +```javascript +--- +import PostHog from '../components/posthog.astro' +--- + + + +``` + +Lastly, update `index.astro` to wrap your existing app components with the new Layout: + +index.astro + +PostHog AI + +```javascript +--- +import PostHogLayout from '../layouts/PostHogLayout.astro'; +--- + + + +``` + +## Identifying users + +> **Identifying users is required.** Call `posthog.identify('your-user-id')` after login to link events to a known user. This is what connects frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), and [error tracking](/docs/error-tracking.md) to the same person — and lets backend events link back too. +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +Set up a reverse proxy (recommended) + +We recommend [setting up a reverse proxy](/docs/advanced/proxy.md), so that events are less likely to be intercepted by tracking blockers. + +We have our [own managed reverse proxy service](/docs/advanced/proxy/managed-reverse-proxy.md), which is free for all PostHog Cloud users, routes through our infrastructure, and makes setting up your proxy easy. + +If you don't want to use our managed service then there are several other options for creating a reverse proxy, including using [Cloudflare](/docs/advanced/proxy/cloudflare.md), [AWS Cloudfront](/docs/advanced/proxy/cloudfront.md), and [Vercel](/docs/advanced/proxy/vercel.md). + +Grouping products in one project (recommended) + +If you have multiple customer-facing products (e.g. a marketing website + mobile app + web app), it's best to install PostHog on them all and [group them in one project](/docs/settings/projects.md). + +This makes it possible to track users across their entire journey (e.g. from visiting your marketing website to signing up for your product), or how they use your product across multiple platforms. + +Add IPs to Firewall/WAF allowlists (recommended) + +For certain features like [heatmaps](/docs/toolbar/heatmaps.md), your Web Application Firewall (WAF) may be blocking PostHog’s requests to your site. Add these IP addresses to your WAF allowlist or rules to let PostHog access your site. + +**EU**: `3.75.65.221`, `18.197.246.42`, `3.120.223.253` + +**US**: `44.205.89.55`, `52.4.194.122`, `44.208.188.173` + +These are public, stable IPs used by PostHog services (e.g., Celery tasks for snapshots). + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Astro (such as analytics, feature flags, A/B testing, surveys, etc.), have a look at our [JavaScript Web SDK docs](/docs/libraries/js/features.md). + +Alternatively, the following tutorials can help you get started: + +- [How to set up Astro analytics, feature flags, and more](/tutorials/astro-analytics.md) +- [How to set up A/B tests in Astro](/tutorials/astro-ab-tests.md) +- [How to set up surveys in Astro](/tutorials/astro-surveys.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.0-begin.md b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.0-begin.md new file mode 100644 index 00000000..49db3cb9 --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.0-begin.md @@ -0,0 +1,56 @@ +--- +title: PostHog Setup - Begin +description: Start the event tracking setup process by analyzing the project and creating an event tracking plan +--- + +We're making an event tracking plan for this project. + +This is the first of several phases — plan the events, implement them, revise and validate changes, then conclude by creating a dashboard and writing a setup report. + +## Task list + +As soon as you've read this description and have a rough sense of the work, make a single **call `TaskCreate` immediately** before reading any reference file or beginning analysis. The user is watching the task pane and shouldn't see it sit empty. + +It's fine if your first list is incomplete or imprecise. Seed it with whatever high-level items you can infer from the overview above, then call `TaskCreate` again (or `TaskUpdate` to refine existing items) every time your understanding sharpens: after a phase reveals work you didn't anticipate, after planning surfaces concrete sub-items, after you hit something new. Use `TaskUpdate` to mark items `in_progress` when you start them and `completed` when you finish. Keeping the list current matters more than getting it right on the first call. + +Keep task titles broad and job-oriented. Describe the purpose or area of work with wording like "Planning event tracking", "Identifying users", "Installing PostHog", "Capturing events", or "Creating dashboards", not the specific files, paths, or symbols involved. Adjust the task names according to the user's project and context. + +Before proceeding, find any existing `posthog.capture()` code. Make note of event name formatting. + +From the project's file list, select between 10 and 15 files that might have interesting business value for event tracking, especially conversion and churn events. Also look for additional files related to login that could be used for identifying users, along with error handling. Read the files. If a file is already well-covered by PostHog events, replace it with another option. Do not spawn subagents. + +Look for opportunities to track client-side events. + +**IMPORTANT: Server-side events are REQUIRED** if the project includes any instrumentable server-side code. If the project has API routes (e.g., `app/api/**/route.ts`) or Server Actions, you MUST include server-side events for critical business operations like: + + - Payment/checkout completion + - Webhook handlers + - Authentication endpoints + +Do not skip server-side events - they capture actions that cannot be tracked client-side. + +Create a new file with a JSON array at the root of the project: .posthog-events.json. It should include one object for each event we want to add: event name, event description, and the file path we want to place the event in. If events already exist, don't duplicate them; supplement them. + +Track actions only, not pageviews. These can be captured automatically. Exceptions can be made for "viewed"-type events that correspond to the top of a conversion funnel. + +As you review files, make an internal note of opportunities to identify users and catch errors. We'll need them for the next step. + +## Status + +Before beginning a phase of the setup, you will send a status message with the exact prefix '[STATUS]', as in: + +[STATUS] Checking project structure. + +Status to report in this phase: + +- Checking project structure +- Verifying PostHog dependencies +- Generating events based on project + +## Abort statuses + +If and only if the instructions have `[ABORT]` states specified, and you clearly match the conditions for an abort, emit the abort message. Do NOT attempt to exit or halt yourself — the wizard's middleware catches `[ABORT]` and terminates the run for you. + +--- + +**Upon completion, continue with:** [basic-integration-1.1-edit.md](basic-integration-1.1-edit.md) \ No newline at end of file diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.1-edit.md b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.1-edit.md new file mode 100644 index 00000000..ca9d70e6 --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.1-edit.md @@ -0,0 +1,37 @@ +--- +title: PostHog Setup - Edit +description: Implement PostHog event tracking in the identified files, following best practices and the example project +--- + +For each of the files and events noted in .posthog-events.json, make edits to capture events using PostHog. Make sure to set up any helper files needed. Carefully examine the included example project code: your implementation should match it as closely as possible. Do not spawn subagents. + +Use environment variables for PostHog keys. Do not hardcode PostHog keys. + +If a file already has existing integration code for other tools or services, don't overwrite or remove that code. Place PostHog code below it. + +For each event, add useful properties, and use your access to the PostHog source code to ensure correctness. You also have access to documentation about creating new events with PostHog. Consider this documentation carefully and follow it closely before adding events. Your integration should be based on documented best practices. Carefully consider how the user project's framework version may impact the correct PostHog integration approach. + +Remember that you can find the source code for any dependency in the node_modules directory. This may be necessary to properly populate property names. There are also example project code files available via the PostHog MCP; use these for reference. + +Where possible, add calls for PostHog's identify() function on the client side upon events like logins and signups. Use the contents of login and signup forms to identify users on submit. If there is server-side code, pass the client-side session and distinct ID to the server-side code to identify the user. On the server side, make sure events have a matching distinct ID where relevant. + +It's essential to do this in both client code and server code, so that user behavior from both domains is easy to correlate. + +You should also add PostHog exception capture error tracking to these files where relevant. + +Remember: Do not alter the fundamental architecture of existing files. Make your additions minimal and targeted. + +Remember the documentation and example project resources you were provided at the beginning. Read them now. + +## Status + +Status to report in this phase: + +- Inserting PostHog capture code +- A status message for each file whose edits you are planning, including a high level summary of changes +- A status message for each file you have edited + + +--- + +**Upon completion, continue with:** [basic-integration-1.2-revise.md](basic-integration-1.2-revise.md) \ No newline at end of file diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.2-revise.md b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.2-revise.md new file mode 100644 index 00000000..5ac72f06 --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.2-revise.md @@ -0,0 +1,22 @@ +--- +title: PostHog Setup - Revise +description: Review and fix any errors in the PostHog integration implementation +--- + +Check the project for errors. Read the package.json file for any type checking or build scripts that may provide input about what to fix. Remember that you can find the source code for any dependency in the node_modules directory. Do not spawn subagents. + +Ensure that any components created were actually used. + +Once all other tasks are complete, run any linter or prettier-like scripts found in the package.json, but ONLY on the files you have edited or created during this session. Do not run formatting or linting across the entire project's codebase. + +## Status + +Status to report in this phase: + +- Finding and correcting errors +- Report details of any errors you fix +- Linting, building and prettying + +--- + +**Upon completion, continue with:** [basic-integration-1.3-conclude.md](basic-integration-1.3-conclude.md) \ No newline at end of file diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md new file mode 100644 index 00000000..39339f5a --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md @@ -0,0 +1,40 @@ +--- +title: PostHog Setup - Conclusion +description: Review and fix any errors in the PostHog integration implementation +--- + +Use the PostHog MCP to create a new dashboard named "Analytics basics (wizard)" based on the events created here. Keep the `(wizard)` tag with that exact casing so anyone browsing PostHog can see the wizard created this dashboard, and so a quick search for `(wizard)` surfaces every wizard-created artifact in one go. Make sure to use the exact same event names as implemented in the code. Populate it with up to five insights, with special emphasis on things like conversion funnels, churn events, and other business critical insights. + +Search for a file called `.posthog-events.json` and read it for available events. + +Do not spawn subagents. + +Create the file posthog-setup-report.md. It should include a summary of the integration edits, a table with the event names, event descriptions, and files where events were added, along with a list of links for the dashboard and insights created. Follow this format: + + +# PostHog post-wizard report + +The wizard has completed a deep integration of your project. [Detailed summary of changes] + +[table of events/descriptions/files] + +## Next steps + +We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented: + +[links] + +### Agent skill + +We've left an agent skill folder in your project. You can use this context for further agent development when using Claude Code. This will help ensure the model provides the most up-to-date approaches for integrating PostHog. + + + +Upon completion, remove .posthog-events.json. + +## Status + +Status to report in this phase: + +- Configured dashboard: [insert PostHog dashboard URL] +- Created setup report: [insert full local file path] \ No newline at end of file diff --git a/docs/.claude/skills/integration-astro-view-transitions/references/identify-users.md b/docs/.claude/skills/integration-astro-view-transitions/references/identify-users.md new file mode 100644 index 00000000..564ead28 --- /dev/null +++ b/docs/.claude/skills/integration-astro-view-transitions/references/identify-users.md @@ -0,0 +1,271 @@ +# Identify users - Docs + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +This is straightforward to do when [capturing backend events](/docs/product-analytics/capture-events?tab=Node.js.md), as you associate events to a specific user using a `distinct_id`, which is a required argument. + +However, in the frontend of a [web](/docs/libraries/js/features.md#capturing-events) or [mobile app](/docs/libraries/ios.md#capturing-events), a `distinct_id` is not a required argument — PostHog's SDKs will generate an anonymous `distinct_id` for you automatically and you can capture events anonymously, provided you use the appropriate [configuration](/docs/libraries/js/features.md#capturing-anonymous-events). + +To link events to specific users, call `identify`: + +PostHog AI + +### Web + +```javascript +posthog.identify( + 'distinct_id', // Replace 'distinct_id' with your user's unique identifier + { email: 'max@hedgehogmail.com', name: 'Max Hedgehog' } // optional: set additional person properties +); +``` + +### Android + +```kotlin +PostHog.identify( + distinctId = distinctID, // Replace 'distinctID' with your user's unique identifier + // optional: set additional person properties + userProperties = mapOf( + "name" to "Max Hedgehog", + "email" to "max@hedgehogmail.com" + ) +) +``` + +### iOS + +```swift +PostHogSDK.shared.identify("distinct_id", // Replace "distinct_id" with your user's unique identifier + userProperties: ["name": "Max Hedgehog", "email": "max@hedgehogmail.com"]) // optional: set additional person properties +``` + +### React Native + +```jsx +posthog.identify('distinct_id', { // Replace "distinct_id" with your user's unique identifier + email: 'max@hedgehogmail.com', // optional: set additional person properties + name: 'Max Hedgehog' +}) +``` + +### Dart + +```dart +await Posthog().identify( + userId: 'distinct_id', // Replace "distinct_id" with your user's unique identifier + userProperties: { + email: "max@hedgehogmail.com", // optional: set additional person properties + name: "Max Hedgehog" +}); +``` + +Events captured after calling `identify` are identified events and this creates a person profile if one doesn't exist already. + +Due to the cost of processing them, anonymous events can be up to 4x cheaper than identified events, so it's recommended you only capture identified events when needed. + +## How identify works + +When a user starts browsing your website or app, PostHog automatically assigns them an **anonymous ID**, which is stored locally. + +Provided you've [configured persistence](/docs/libraries/js/persistence.md) to use cookies or `localStorage`, this enables us to track anonymous users – even across different sessions. + +By calling `identify` with a `distinct_id` of your choice (usually the user's ID in your database, or their email), you link the anonymous ID and distinct ID together. + +Thus, all past and future events made with that anonymous ID are now associated with the distinct ID. + +This enables you to do things like associate events with a user from before they log in for the first time, or associate their events across different devices or platforms. + +Using identify in the backend + +Although you can call `identify` using our backend SDKs, it is used most in frontends. This is because there is no concept of anonymous sessions in the backend SDKs, so calling `identify` only updates person profiles. + +## Best practices when using `identify` + +### 1\. Call `identify` as soon as you're able to + +In your frontend, you should call `identify` as soon as you're able to. + +Typically, this is every time your **app loads** for the first time, and directly after your **users log in**. + +This ensures that events sent during your users' sessions are correctly associated with them. + +You only need to call `identify` once per session, and you should avoid calling it multiple times unnecessarily. + +If you call `identify` multiple times with the same data without reloading the page in between, PostHog will ignore the subsequent calls. + +### 2\. Use unique strings for distinct IDs + +If two users have the same distinct ID, their data is merged and they are considered one user in PostHog. Two common ways this can happen are: + +- Your logic for generating IDs does not generate sufficiently strong IDs and you can end up with a clash where 2 users have the same ID. +- There's a bug, typo, or mistake in your code leading to most or all users being identified with generic IDs like `null`, `true`, or `distinctId`. + +PostHog also has built-in protections to stop the most common distinct ID mistakes. + +### 3\. Reset after logout + +If a user logs out on your frontend, you should call `reset()` to unlink any future events made on that device with that user. + +This is important if your users are sharing a computer, as otherwise all of those users are grouped together into a single user due to shared cookies between sessions. + +**We strongly recommend you call `reset` on logout even if you don't expect users to share a computer.** + +You can do that like so: + +PostHog AI + +### Web + +```javascript +posthog.reset() +``` + +### iOS + +```swift +PostHogSDK.shared.reset() +``` + +### Android + +```kotlin +PostHog.reset() +``` + +### React Native + +```jsx +posthog.reset() +``` + +### Dart + +```dart +Posthog().reset() +``` + +If you *also* want to reset the `device_id` so that the device will be considered a new device in future events, you can pass `true` as an argument: + +Web + +PostHog AI + +```javascript +posthog.reset(true) +``` + +### 4\. Person profiles and properties + +You'll notice that one of the parameters in the `identify` method is a `properties` object. + +This enables you to set [person properties](/docs/product-analytics/person-properties.md). + +Whenever possible, we recommend passing in all person properties you have available each time you call identify, as this ensures their person profile on PostHog is up to date. + +Person properties can also be set being adding a `$set` property to a event `capture` call. + +See our [person properties docs](/docs/product-analytics/person-properties.md) for more details on how to work with them and best practices. + +### 5\. Use deep links between platforms + +We recommend you call `identify` [as soon as you're able](#1-call-identify-as-soon-as-youre-able), typically when a user signs up or logs in. + +This doesn't work if one or both platforms are unauthenticated. Some examples of such cases are: + +- Onboarding and signup flows before authentication. +- Unauthenticated web pages redirecting to authenticated mobile apps. +- Authenticated web apps prompting an app download. + +In these cases, you can use a [deep link](https://developer.android.com/training/app-links/deep-linking) on Android and [universal links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) on iOS to identify users. + +1. Use `posthog.get_distinct_id()` to get the current distinct ID. Even if you cannot call identify because the user is unauthenticated, this will return an anonymous distinct ID generated by PostHog. +2. Add the distinct ID to the deep link as query parameters, along with other properties like UTM parameters. +3. When the user is redirected to the app, parse the deep link and handle the following cases: + +- The mobile app is already authenticated. In this case, call [`posthog.alias()`](/docs/libraries/js/features.md#alias) with the distinct ID from the web. This associates the two distinct IDs as a single person. +- The mobile app is unauthenticated. In this case, call [`posthog.identify()`](/docs/libraries/js/features.md#identifying-users) with the distinct ID from the web so pre-login mobile events stay connected to the web session. When the user later logs in on mobile, call `identify()` again with your canonical user ID. + +As long as you associate the distinct IDs with `posthog.identify()` or `posthog.alias()`, you can track events generated across platforms. + +Here's an example implementation for handling deep links from web to mobile: + +PostHog AI + +### iOS + +```swift +import PostHog +class DeepLinkIdentityManager { + static let shared = DeepLinkIdentityManager() + // MARK: - Deep Link Received + func handleDeepLink(_ url: URL, isAuthenticatedOnMobile: Bool) { + guard let webDistinctId = URLComponents(url: url, resolvingAgainstBaseURL: true)? + .queryItems?.first(where: { $0.name == "ph_distinct_id" })?.value else { + return + } + if isAuthenticatedOnMobile { + // The mobile app already knows the current user. + // Alias the incoming web distinct ID to that user. + PostHogSDK.shared.alias(webDistinctId) + } else { + // Reuse the web distinct ID until login on mobile. + PostHogSDK.shared.identify(webDistinctId) + } + } + // MARK: - Login/Signup + func handleLogin(canonicalUserId: String) { + // Switch from the web distinct ID (or a mobile anon ID) + // to your canonical user ID. + PostHogSDK.shared.identify(canonicalUserId) + // Set user properties, track signup event, etc. + } + func handleLogout() { + PostHogSDK.shared.reset() + } +} +``` + +### Android + +```kotlin +import android.net.Uri +import com.posthog.PostHog +object DeepLinkIdentityManager { + // Deep Link Received + fun handleDeepLink(uri: Uri, isAuthenticatedOnMobile: Boolean) { + val webDistinctId = uri.getQueryParameter("ph_distinct_id") ?: return + if (isAuthenticatedOnMobile) { + // The mobile app already knows the current user. + // Alias the incoming web distinct ID to that user. + PostHog.alias(webDistinctId) + } else { + // Reuse the web distinct ID until login on mobile. + PostHog.identify(webDistinctId) + } + } + // Login/Signup + fun handleLogin(canonicalUserId: String) { + // Switch from the web distinct ID (or a mobile anon ID) + // to your canonical user ID. + PostHog.identify(canonicalUserId) + // Set user properties, track signup event, etc. + } + fun handleLogout() { + PostHog.reset() + } +} +``` + +## Further reading + +- [Identifying users docs](/docs/product-analytics/identify.md) +- [How person processing works](/docs/how-posthog-works/ingestion-pipeline.md#2-person-processing) +- [An introductory guide to identifying users in PostHog](/tutorials/identifying-users-guide.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore index ce6a097b..132d44a0 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,2 +1,3 @@ # Visual-regression screenshots — generated locally, not tracked. screenshots/ +.env diff --git a/docs/posthog-setup-report.md b/docs/posthog-setup-report.md new file mode 100644 index 00000000..db300ee3 --- /dev/null +++ b/docs/posthog-setup-report.md @@ -0,0 +1,27 @@ + +# PostHog post-wizard report + +The wizard has completed a deep integration of PostHog analytics into the WaveHouse documentation site. PostHog is initialized via a browser snippet in a new `src/components/posthog.astro` component, imported into the existing `src/components/Head.astro` override. The initialization is wrapped in a `window.__posthog_initialized` guard to prevent stack overflow during Astro View Transitions (ClientRouter) soft navigation. Pageviews are tracked automatically via `capture_pageview: 'history_change'`. Event capture scripts in Hero.astro, Footer.astro, and index.mdx attach click listeners using the `astro:page-load` event pattern to work correctly across both hard and soft navigations. + +| Event | Description | File | +|-------|-------------|------| +| `hero_cta_clicked` | User clicked a CTA button in the homepage hero section (Get Started or View on GitHub). Properties: `cta_text`, `cta_href`, `cta_variant`. | `src/components/Hero.astro` | +| `footer_link_clicked` | User clicked an external link in the site footer (GitHub, Discussions, Changelog, Contributing, Support, Security, License). Properties: `link_name`, `link_href`. | `src/components/Footer.astro` | +| `homepage_cta_clicked` | User clicked a primary CTA in the homepage closing section (Get started in five minutes, Star on GitHub). Properties: `cta_text`, `cta_href`. | `src/content/docs/index.mdx` | + +## Next steps + +We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented: + +- [Analytics basics (wizard) — Dashboard](https://us.posthog.com/project/419725/dashboard/1675839) +- [Total CTA Clicks (Last 30 Days)](https://us.posthog.com/project/419725/insights/9PDvsQMn) +- [Hero CTA Clicks by Button](https://us.posthog.com/project/419725/insights/fN5rr2n7) +- [Footer Link Clicks by Destination](https://us.posthog.com/project/419725/insights/qPUrkXKi) +- [Homepage CTA Clicks Over Time](https://us.posthog.com/project/419725/insights/SkpKT5uj) +- [Engagement Events Compared](https://us.posthog.com/project/419725/insights/YBywb6Ze) + +### Agent skill + +We've left an agent skill folder in your project. You can use this context for further agent development when using Claude Code. This will help ensure the model provides the most up-to-date approaches for integrating PostHog. + + diff --git a/docs/src/components/Footer.astro b/docs/src/components/Footer.astro index c1287e7c..c85f28f9 100644 --- a/docs/src/components/Footer.astro +++ b/docs/src/components/Footer.astro @@ -43,16 +43,16 @@ const repo = "https://github.com/Wave-RF/WaveHouse"; open-source project @@ -103,13 +103,13 @@ const repo = "https://github.com/Wave-RF/WaveHouse"; @@ -123,6 +123,23 @@ const repo = "https://github.com/Wave-RF/WaveHouse"; ) } + +