chore(docs): promotion and growth groundwork — homepage, SEO, privacy, analytics wiring#454
Conversation
fro-bot
left a comment
There was a problem hiding this comment.
Verdict: PASS
Everything looks solid. The generate-stats pipeline is cleanly architected — pure generateStats function + injected resolveGitTag dependency make it fully testable without mocking the filesystem, and the tests cover all the meaningful paths (happy path, placeholder fallback, unreleased fallback, all three throw paths, idempotence). No any, no @ts-ignore, node: protocol throughout, explicit return types on all exports. The Umami wiring is correctly hard-disabled until UMAMI_WEBSITE_ID is set — dev and CI builds emit no analytics script at all.
Blocking issues
None.
Non-blocking concerns
-
stats.jsonbuild-order coupling —StatsBanner.astroimports../data/stats.jsonat build time. If someone runsbun run --cwd docs builddirectly (skippingdocs:build), the import will fail with a missing-module error rather than a graceful "counts not generated yet" message. Thedocs:buildscript correctly gates ondocs:generatefirst, so CI is safe, but the error surface for local contributors who bypass the wrapper is poor. A note in the contributing docs or a cleaner build-time error would help. -
Privacy page retention note is incomplete —
docs/src/content/docs/privacy.mdxcontains a blockquote noting that retention and IP-handling details are "pending review of the live instance configuration and will be added here before the public launch." This is a committed public-record stub. Since analytics is hard-disabled until launch anyway, this is low urgency, but it should be resolved beforeUMAMI_WEBSITE_IDis set in the deploy environment — not treated as optional polish. -
countAgentscounts all.mdfiles recursively — this is a deliberate design choice (documented in the function JSDoc) but it means any future.mdnon-agent files dropped underagents/would silently inflate the homepage count.countSkillsuses the SKILL.md sentinel as a guard;countAgentshas no equivalent. No current problem, but worth knowing. -
CustomFooter.astrouses a hardcoded/systematic/base —<a href="/systematic/privacy/">. If the Starlight base ever changes, this breaks silently. Minor, since the base is set inastro.config.mjsand is unlikely to change.
Missing tests
None — the 9-test suite for generate-stats.ts covers the full boundary surface. The happy path, version resolution branches, and all three error throw paths are tested. serializeStats is exercised by the idempotence test.
Risk assessment: LOW
- No changes to the plugin runtime, config schema, dist output, or any published package surface. This is purely docs-site and build tooling.
- The analytics script is conditionally emitted only when
UMAMI_WEBSITE_IDis set — the current deploy environment does not set it, so merging this PR collects no data and changes no user-facing behavior beyond the homepage content rewrite. - The stats artifact is generated and gitignored; it cannot go stale in-repo, and the build will loudly fail if the registry is missing or malformed.
- Blast radius of a regression: docs site homepage shows wrong counts or build fails. Both are caught by
docs:buildin CI before deploy.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | marcusrbrown/systematic |
| Run ID | 26651850196 |
| Cache | hit |
| Session | pr-454 |
Build-time generator walking skills/, agents/, and registry/registry.jsonc to emit docs/src/data/stats.json (skills/agents/components/version), wired into docs:generate before astro build. Version resolves from the latest git tag when package.json holds the semantic-release placeholder, falling back to 'unreleased' so the homepage never renders a broken version string. Includes the tracked promotion-and-growth plan doc.
Conditional Umami script head entry in astro.config.mjs, emitted only when UMAMI_WEBSITE_ID is set so dev/preview builds and the current static site ship no tracker. Install CTAs on the home and quick-start pages carry data-umami-event=click_install_cta attributes (inert until the script loads). Emits the full deployed tag shape: defer + data-do-not-track + data-exclude-search + data-exclude-hash.
docs/src/data/stats.json is emitted by generate-stats.ts during docs:generate (which docs:build runs first), so it must not be tracked — a committed copy goes stale the moment a skill/agent is added. Matches the existing generated-reference-page and schema-mirror ignore entries.
Documents the cookie-free, self-hosted, DNT-respected, no-PII analytics posture and restates the plugin-runtime no-telemetry guarantee. CustomFooter renders Starlight's default footer and appends the Privacy link. Retention/IP specifics marked pending until reconciled against the live config.
Product-landing structure (hero, live StatsBanner, demo proof CTA, feature grid, value props, quick start, honesty section) using Systematic theme tokens — no shader, no React. StatsBanner reads live counts from the generated stats artifact; install CTA retains its activation-event attribute.
og:type, og:site_name, twitter:card/image, and a JSON-LD SoftwareSourceCode block in the docs head, plus a sharper site title and capability-focused description (no hardcoded counts). Built via JSON.stringify so the structured data can't carry trailing-comma errors.
Evergreen drafts for LinkedIn (project + post), Bluesky, and the OpenCode
Discord under docs/promotion/, plus a README documenting voice rules, the
placeholder fill step, and the permanent-record caveat. Count claims use
{{SKILLS}}/{{AGENTS}}/{{COMPONENTS}}/{{VERSION}} placeholders filled from the
generated stats artifact at post time, so numbers can't silently go stale.
generate-stats.test.ts was co-located under docs/scripts/, where the unit suite (bun test tests/unit) never picked it up. Relocate it alongside the other generator tests (mirrors generate-config-reference.test.ts, which tests a docs/scripts/ generator from tests/unit/) and repath the import. Unit count 918 -> 926.
Harden the stats emitter and tighten its tests after code review: - Fail loud on malformed registry JSONC (pass an errors array to the parser and throw), and narrow parsed JSON through an isRecord guard instead of casting to trusted shapes. - StatsBanner renders the 'unreleased' fallback verbatim instead of a bogus 'vunreleased'. - Idempotence test exercises the production serializeStats; fail-loud tests assert the specific diagnostics; add a malformed-JSONC case. - Privacy page describes Quick Start as an aggregate pageview, not a distinct named event (only click_install_cta is a custom event).
The "See the Difference" CTA pointed at a with/without comparison page that doesn't exist yet. Retarget it to the existing main-loop guide so the homepage has no dead link; the section can be upgraded to a dedicated comparison page later.
U1/U2/U4/U5/U6/U8 shipped; U3 (demo), U7 (ecosystem PR), and U9 (public launch + gate) remain deferred.
9811078 to
536ab05
Compare
fro-bot
left a comment
There was a problem hiding this comment.
Approving — the review posted in this thread (run 26651850196) is current. No new commits since that review; verdict and findings are unchanged.
Verdict: PASS
Blocking issues
None.
Non-blocking concerns
-
stats.jsonbuild-order coupling —StatsBanner.astroimports../data/stats.jsonat build time. Directbun run --cwd docs buildbypassesdocs:generateand will fail with a missing-module error. CI is safe (docs:buildgates correctly); this only affects contributors who bypass the wrapper. -
Privacy page retention note is incomplete — the blockquote in
privacy.mdxnoting retention/IP-handling details are pending must be resolved beforeUMAMI_WEBSITE_IDis set in the deploy environment. -
countAgentshas no file-type sentinel — all.mdfiles underagents/are counted; a future non-agent.mdthere would silently inflate the homepage stat.countSkillsuses SKILL.md as a guard;countAgentsdoes not. -
CustomFooter.astrohardcodes/systematic/base — breaks silently if the Starlight base changes.
Missing tests
None.
Risk assessment: LOW
No changes to the plugin runtime, config schema, dist output, or any published package surface. Analytics is hard-disabled (script omitted entirely) until UMAMI_WEBSITE_ID is set — merging collects no data. Blast radius of a regression is limited to the docs site homepage, caught by docs:build in CI before deploy.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | marcusrbrown/systematic |
| Run ID | 26652909848 |
| Cache | hit |
| Session | ses_18b3bfa20ffe2QS4rv0yTIPPOe |
Summary
Promotion-and-growth groundwork for the docs site: a product-landing homepage with live counts, structured SEO, a privacy page, privacy-respecting analytics wiring (kept disabled until launch), and drafts for the public announcement channels.
This is the docs-site and growth-instrumentation slice. The live before/after demo, the OpenCode ecosystem listing, and the public launch are intentionally held back as separate steps.
What's included
docs/scripts/generate-stats.tsreadsregistry/registry.jsoncand writesdocs/src/data/stats.jsonduringdocs:generate, so the homepage numbers never drift. The artifact is generated, not committed.SoftwareSourceCode, Open Graph, and Twitter card tags, plus a sharper site title and description.UMAMI_WEBSITE_IDis set in the deploy environment, so dev and preview builds never emit analytics.docs/promotion/holds working drafts for the ecosystem listing, LinkedIn, Bluesky, and the OpenCode Discord. Drafts only; counts are placeholders pending a pre-publish refresh.Testing
bun run typecheck— cleanbun run lint— cleanbun test tests/unit— 927 pass / 0 failbun scripts/content-integrity.ts— cleanbun run registry:drift— up to date (103 components)bun run docs:build— 111 pages, completeRelease impact
All commits are
chore(docs):/test(docs):/docs(plans):— none touch the published package surface (dist/,skills/,agents/), so per.releaserc.yamlthis does not trigger a version bump. The docs site deploys on the next release or via manualworkflow_dispatch.After merge
Analytics stays dark until the launch step sets
UMAMI_WEBSITE_IDin the deploy environment — merging this PR collects no data. The before/after demo guide, the ecosystem PR, and the public launch (with its adoption gate) follow as separate steps.