From 54ad43998d5dd1beefb819cbe719163cface55b4 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Mon, 17 Aug 2026 13:37:49 -0400 Subject: [PATCH 01/21] trademark stuff added --- Makefile | 5 +- docs/astro.config.mjs | 6 +- docs/scripts/dev.mjs | 65 ++++- docs/src/components/CloudCta.astro | 228 ++++++++++++++++ docs/src/components/ExternalIcon.astro | 50 ++++ docs/src/components/Footer.astro | 220 +++++++++++---- docs/src/components/Header.astro | 2 +- docs/src/components/Hero.astro | 48 ++-- docs/src/components/LiveDemo.astro | 6 +- docs/src/components/Trademarks.astro | 55 ++++ docs/src/config/outbound.ts | 138 ++++++++++ docs/src/config/trademarks.ts | 331 +++++++++++++++++++++++ docs/src/content.config.ts | 28 +- docs/src/content/docs/access-control.mdx | 2 + docs/src/content/docs/architecture.md | 2 + docs/src/content/docs/configuration.mdx | 2 + docs/src/content/docs/deployment.md | 2 + docs/src/content/docs/durability.md | 2 + docs/src/content/docs/index.mdx | 30 +- docs/src/content/docs/ingest-pipeline.md | 2 + docs/src/content/docs/reverse-proxy.mdx | 2 + docs/src/content/docs/why-wavehouse.md | 3 + docs/src/plugins/rehype-trademarks.ts | 165 +++++++++++ docs/src/styles/global.css | 52 +++- 24 files changed, 1347 insertions(+), 99 deletions(-) create mode 100644 docs/src/components/CloudCta.astro create mode 100644 docs/src/components/ExternalIcon.astro create mode 100644 docs/src/components/Trademarks.astro create mode 100644 docs/src/config/outbound.ts create mode 100644 docs/src/config/trademarks.ts create mode 100644 docs/src/plugins/rehype-trademarks.ts diff --git a/Makefile b/Makefile index 3be77d2b..a6f2135e 100644 --- a/Makefile +++ b/Makefile @@ -263,8 +263,11 @@ dev-ts: pnpm-install ## Watch-build SDK (tsup --watch) # 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. +# Serves :4321, walking upward if that's taken (ports are machine-wide, so a +# dev server in another worktree or repo will claim it) — the script prints the +# port it settled on. DOCS_PORT=… moves the starting point. .PHONY: dev-docs -dev-docs: install-playwright-docs build-ts ## Prod-faithful docs dev loop: rebuild-on-save + wrangler dev on :4321 +dev-docs: install-playwright-docs build-ts ## Prod-faithful docs dev loop: rebuild-on-save + wrangler dev on :4321 (next free port if busy) @$(PNPM) --filter $(DOCS_FILTER) run dev # preview-docs serves the production build through wrangler (Cloudflare Workers diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 41318980..e712fffe 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -12,6 +12,7 @@ import starlightLinksValidator from "starlight-links-validator"; import { mermaidTheme } from "./src/config/mermaid-theme.mjs"; import { sidebar } from "./src/config/sidebar.ts"; import { diagramPng } from "./src/integrations/diagram-png.mjs"; +import { rehypeTrademarks } from "./src/plugins/rehype-trademarks.ts"; // Color-agnostic Mermaid plugin (astro-themed-mermaid pkg) + WaveHouse's palette // (src/config/mermaid-theme). Diagram colors are defined once in global.css @@ -31,7 +32,10 @@ export default defineConfig({ // 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], + // rehypeTrademarks runs last on purpose: it skips the SVG and .katex + // subtrees the two plugins before it produce, so it has to see them already + // rendered rather than as ```mermaid fences and $math$. + rehypePlugins: [mermaid.rehypeMermaid, rehypeKatex, rehypeTrademarks], }, integrations: [ starlight({ diff --git a/docs/scripts/dev.mjs b/docs/scripts/dev.mjs index 68411d66..002e0977 100644 --- a/docs/scripts/dev.mjs +++ b/docs/scripts/dev.mjs @@ -5,8 +5,8 @@ * 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. + * on :4321 (or the next free port) — 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) @@ -19,7 +19,10 @@ * available as `pnpm run start` when HMR matters more than fidelity. * * Knobs: - * DOCS_PORT=… serve port (default 4321) + * DOCS_PORT=… first port to try (default 4321). If it's taken the + * loop walks upward to the next free one and prints + * where it landed — wrangler itself would just die, + * see findFreePort() below. * 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 @@ -31,13 +34,15 @@ import { spawn } from "node:child_process"; import { existsSync, watch } from "node:fs"; import { cp, readdir, rm, stat } from "node:fs/promises"; +import { createServer as createNetServer } from "node:net"; 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 PORT_START = Number(process.env.DOCS_PORT ?? 4321); +const PORT_TRIES = 20; const DEBOUNCE_MS = 300; const log = (msg) => console.log(`\x1b[36m[dev-docs]\x1b[0m ${msg}`); @@ -45,6 +50,41 @@ const log = (msg) => console.log(`\x1b[36m[dev-docs]\x1b[0m ${msg}`); 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); +/* Pick the port BEFORE handing it to wrangler. + * + * `wrangler dev` hunts for a free port when you don't name one, but treats an + * explicit `--port` as strict — it dies with a raw kj bind exception rather + * than moving. We have to pass `--port` (the URL is logged below, and bare + * wrangler would land somewhere we couldn't announce), so the hunting is ours + * to do. `astro dev` never enters into it: this loop serves builds through the + * Worker, so Vite's own port-hunting is not in the path. + * + * Ports are machine-wide, not per-worktree or per-repo, so the usual collision + * is a dev server from an entirely different checkout. + * + * Both stacks get probed because wrangler binds 127.0.0.1 and [::1] as + * separate sockets, and the common squatter — an `astro dev` elsewhere — holds + * only [::1]. Probing IPv4 alone would call the port free and we would fail on + * the v6 bind anyway, which is exactly the failure this replaces. */ +function portFree(port, host) { + return new Promise((resolvePort) => { + const probe = createNetServer(); + // Anything but "someone already has it" counts as free: a host with IPv6 + // disabled answers EADDRNOTAVAIL for ::1, which must not veto the port. + probe.once("error", (err) => resolvePort(!["EADDRINUSE", "EACCES"].includes(err.code))); + probe.listen({ port, host, exclusive: true }, () => probe.close(() => resolvePort(true))); + }); +} + +async function findFreePort(start, tries) { + for (let port = start; port < start + tries; port++) { + if ((await portFree(port, "127.0.0.1")) && (await portFree(port, "::1"))) { + return port; + } + } + return null; +} + let activeBuild = null; function run(cmd, args, opts = {}) { return new Promise((done) => { @@ -204,7 +244,22 @@ if (existsSync(join(DIST, "index.html"))) { // 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], { +// Resolved here rather than at startup so the gap between "it was free" and +// "wrangler has it" stays as small as possible — a cold-start build is minutes +// of window during which someone else could take the port. +const PORT = await findFreePort(PORT_START, PORT_TRIES); +if (PORT === null) { + fail( + `no free port in ${PORT_START}–${PORT_START + PORT_TRIES - 1}. ` + + `Stop one of the servers holding them, or set DOCS_PORT to a clear range.`, + ); + process.exit(1); +} +if (PORT !== PORT_START) { + log(`port ${PORT_START} is busy (often a dev server from another checkout) — using ${PORT}`); +} + +wrangler = spawn(join(BIN, "wrangler"), ["dev", "--live-reload", "--port", String(PORT)], { cwd: ROOT, stdio: "inherit", }); diff --git a/docs/src/components/CloudCta.astro b/docs/src/components/CloudCta.astro new file mode 100644 index 00000000..30a9f21d --- /dev/null +++ b/docs/src/components/CloudCta.astro @@ -0,0 +1,228 @@ +--- +// "We'll run this for you" callout pointing at WaveHouse Cloud. +// +// Placed at the end of the ops-heavy pages (deployment, durability, access +// control, …) via the `cloudCta` frontmatter flag, which Footer.astro reads — +// that indirection is what lets plain-.md pages carry the CTA without being +// converted to .mdx. .mdx pages can also import it directly for inline use. +// +// The link is built through cloudLink() so it always carries UTMs and keeps its +// Referer; see src/config/outbound.ts for why both matter. +import { cloudLink, REL_KEEP_REFERRER } from "../config/outbound"; +import ExternalIcon from "./ExternalIcon.astro"; + +interface Props { + /** + * Where on the site this instance lives — "docs-deployment", + * "homepage-closer", … Rides along as utm_content and as a PostHog property, + * so it must be unique per placement or the two can't be told apart. + */ + placement: string; + /** Override the default heading. */ + title?: string; + /** Override the default body copy — use it to name what THIS page stops being your problem. */ + body?: string; + /** "panel" for in-content use, "band" for the full-width homepage treatment. */ + variant?: "panel" | "band"; +} + +const { + placement, + title = "Don't want to run this yourself?", + body = + "WaveHouse Cloud runs both halves for you — managed ClickHouse plus the WaveHouse gateway, with schema-aware ingest, SSE streaming, and tiered caching. Same open-source binary, zero ops.", + variant = "panel", +} = Astro.props; + +const href = cloudLink(placement); +--- + + + + + + diff --git a/docs/src/components/ExternalIcon.astro b/docs/src/components/ExternalIcon.astro new file mode 100644 index 00000000..f98f640b --- /dev/null +++ b/docs/src/components/ExternalIcon.astro @@ -0,0 +1,50 @@ +--- +// The ↗ "this link leaves the site" glyph. +// +// One definition, because it had drifted into three: an inline SVG in +// Hero.astro (for Starlight's `icon: external`), a second copy in +// CloudCta.astro, and a bare "↗" character in LiveDemo.astro — which is worse +// than a duplicate, since a screen reader reads that character aloud as "north +// east arrow" in the middle of the link text. +// +// Sized in em rather than px so it tracks whatever it sits beside; 0.8125em is +// the 13px the hero used, expressed against that button's 16px label. +// aria-hidden throughout: the link's own text carries the meaning. +interface Props { + class?: string; + /** + * Stroke weight. 2.5 matches the hero/button labels it was drawn for; drop it + * for large or light text, where 2.5 reads as a blob. + */ + weight?: number; +} +const { class: className, weight = 2.5 } = Astro.props; +--- + + + + diff --git a/docs/src/components/Footer.astro b/docs/src/components/Footer.astro index bfa1609f..d525d944 100644 --- a/docs/src/components/Footer.astro +++ b/docs/src/components/Footer.astro @@ -6,10 +6,19 @@ import EditLink from "virtual:starlight/components/EditLink"; import LastUpdated from "virtual:starlight/components/LastUpdated"; import Pagination from "virtual:starlight/components/Pagination"; +import { + REL_EXTERNAL, + REL_KEEP_REFERRER, + cloudLink, + waveRfLink, +} from "../config/outbound"; +import { pageText } from "../config/trademarks"; +import CloudCta from "./CloudCta.astro"; import Logo from "./Logo.astro"; import MermaidZoom from "./MermaidZoom.astro"; import ReadingProgress from "./ReadingProgress.astro"; import ScrollHints from "./ScrollHints.astro"; +import Trademarks from "./Trademarks.astro"; const year = new Date().getFullYear(); @@ -19,14 +28,34 @@ const year = new Date().getFullYear(); // - no sidebar → full-width, multi-column "marketing" footer (room to breathe) // - has sidebar → slim, content-aligned footer (a big band crammed next to a // fixed sidebar reads as broken; a slim one reads as native) -const { hasSidebar } = Astro.locals.starlightRoute; +const { hasSidebar, entry } = Astro.locals.starlightRoute; const repo = "https://github.com/Wave-RF/WaveHouse"; + +// Pages opt into the WaveHouse Cloud callout with `cloudCta` frontmatter (see +// src/content.config.ts). Rendering it here — first in the footer, i.e. right +// after the page content and before the edit-link/prev-next chrome — is what +// keeps the plain-.md ops pages able to carry it without becoming .mdx. +const cloudCta = entry.data.cloudCta; +const cloudCtaProps = cloudCta === true ? {} : cloudCta; + +// The page slug names the placement, so PostHog can tell "the deployment-page +// CTA converts, the architecture one doesn't" without any per-page wiring. +const cloudCtaPlacement = `docs-${entry.id || "index"}`; + +// Everything the page turns into visible words, for the per-page trademark +// notices. pageText() picks the frontmatter fields that actually render (hero +// tagline, title, this page's Cloud CTA copy) and leaves out the ones that +// don't — `description` is -only, sidebar labels are nav chrome. Naming +// a mark in the footer that the reader never sees on the page would make the +// notice wrong in the other direction. +const trademarkSource = pageText(entry.data, entry.body ?? ""); --- { hasSidebar ? ( @@ -73,11 +115,16 @@ const repo = "https://github.com/Wave-RF/WaveHouse";