diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcf16728..852a512b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,11 +45,6 @@ jobs: | sudo tar -xz -C /usr/local/bin vale vale --version - - name: Verify prose style - run: | - node --test scripts/openapi-prose-scope.test.mjs - bun run verify:prose - - name: Check types and templates run: bun run check @@ -59,8 +54,16 @@ jobs: - name: Build static site run: bun run build + - name: Test built docs experience and helpers + run: bun run test:docs + - name: Audit built site run: bun run audit - name: Verify routes and artifacts run: bun run verify:routes + + - name: Verify prose style + run: | + node --test scripts/openapi-prose-scope.test.mjs + bun run verify:prose diff --git a/AGENTS.md b/AGENTS.md index 84942bae..5b098208 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,13 @@ Assume every commit and every line of history will be public. flags, routes, defaults, or timelines. - Keep the voice direct, no-BS, and a little playful. Prefer the best path over an encyclopedia of alternatives. +- Be concise; lead with importance. See [STYLE_GUIDE.md](STYLE_GUIDE.md) for + the full rationale, worked examples, and the complete list of banned words, + wordiness swaps, and vocabulary rules Vale enforces. +- Preserve prerequisites, scope, and exceptions when rewriting. Check leads + against the procedure and public reference, then reconcile sibling guides, + glossary entries, and built-output tests. Shorter wording is not evidence + of factual accuracy. - Do not add navigation to a section until that section has a real page or generated source. - Authored navigation comes from `content/**` and its `meta.ts` files. @@ -60,6 +67,7 @@ bun run verify:generated bun run check bun run validate bun run build +bun run test:docs bun run audit bun run verify:public-safety bun run verify:prose @@ -68,6 +76,9 @@ bun run verify:routes ## Prose style (Vale) +See [STYLE_GUIDE.md](STYLE_GUIDE.md) for the human-readable version of every +rule below, with rationale and worked examples. + `bun run verify:prose` runs [Vale](https://vale.sh) over every docs page and over the `summary`/`description` fields of the generated OpenAPI snapshot (extracted to markdown, reported by spec + JSON Pointer). CI enforces it; zero diff --git a/DOCS_QA_COMPARISON.md b/DOCS_QA_COMPARISON.md new file mode 100644 index 00000000..c2cfc22b --- /dev/null +++ b/DOCS_QA_COMPARISON.md @@ -0,0 +1,310 @@ +# Old and new docs evaluation + +This report compares the built `main` docs with this branch using the same +questions and checks on both versions. It includes two 20-question reviewer +comparisons, the 23-question docs-only agent eval, and the 12 targeted +regression checks. The runnable checks are documented in +[DOCS_TEST_SUITE.md](DOCS_TEST_SUITE.md). + +**Conclusion:** The evidence establishes a small improvement in findability and +one repaired task prerequisite. It does not establish a broad improvement in +reader comprehension or task completion. The 20 questions were reviewed by an +evaluator, not tested with real users. The 12 new regression checks were written +for this change, so their pass-rate difference cannot stand in for an +independent outcome measure. Do not use this report to claim that the rewrite +as a whole is better for readers. The before/after writing audit is in +[DOCS_WRITING_QA.md](DOCS_WRITING_QA.md). + +- **Part 1 — can a reviewer find the answer, stated clearly, in 3 clicks or + less?** Tests navigation and answer presence, not reader comprehension or + content accuracy. +- **Part 2 — is the exact command/code shown actually correct?** Tests + content accuracy against the frozen, producer-owned reference + (`generated/cli/`, `generated/openapi/`), not navigation. + +The manual results are point-in-time observations, not a CI gate. The agent +results are single runs and can vary between runs. The 20 user questions do not +cover most of the 78 edited authored pages, so they cannot validate the rewrite +as a whole. See "Running this again" below for the method. + +To support a broader claim, ask readers unfamiliar with both versions to do +the same representative tasks on each site, with site order balanced between +readers. Record task completion without assistance, wrong turns, and time to +the correct answer. Include tasks from pages whose openings changed, not only +the Database and publishing paths. Keep the task list and scoring rules fixed +before testing; report per-task results and failures alongside any aggregate. + +The later team-benefit edits on the homepage, Quickstart, and Teams page were +not part of the 20-question comparison. Q15 checks whether an evaluator can +find invitation instructions; it does not test whether readers understand why +to use a team. Those edits have build and route verification but no measured +reader outcome yet. + +## Current main versus this branch (October 5, 2026) + +Both sites were built from the same current `main` base and tested in a browser +from their homepages. The candidate adds one authored route (Glossary); the +generated CLI and API reference trees are identical on both branches. + +| Test | Current `main` | This branch | Change | +|---|---:|---:|---| +| Part 1: reviewer finds clear answer in at most 3 clicks | 18/20 | 19/20 | +1 question | +| Part 2: command/API accuracy against generated reference | 19/20 | 19/20 | No change | +| Targeted regression checks | 0/12 | 12/12 | Checks written for this change | +| Corrected docs-only agent retrieval | 23/23 | 23/23 | No factual retrieval regression | + +Part 1 improvement comes from Q11: the Database page now tells readers how +to enable Zero before using its database (one click on both sites; the old +answer was incomplete). Q1 already passed on `main`, but the no-install Drop +path moved from Publishing → Dashboard (two actions) to the homepage (zero). +Q19 still fails on both sites because “contact support” has no linked or named +support channel. Q14 can be answered on Customization in one click; Site pages +adds detail about error-page layout in a second click. + +Part 2 Q8 is one failed question with two issues on both branches: the +generated CLI example uses a preset outside its own enum, and the authored +API-key page omits `partner_admin` while giving conflicting default-preset +guidance. The checks compare documentation with the generated reference; they +do not execute a live publish or API request. + +The same 26-test executable suite was run on both built sites. The 14 existing +corpus, index, and audit unit tests pass on each. The 12 new experience checks +fail on `main` and pass on this branch. They were written for the changed +reader journeys, so their before/after result shows that the intended edits +landed and are protected against regression. It is not independent evidence +that the edits improve the reader experience. + +## Part 1: Reviewer click-path comparison (20 user questions) + +Methodology: an evaluator starts every test fresh from the homepage +(`/docs/`), using navigation a reader could use +(nav bar, sidebar, tabs, in-page links/cards). A "click" is one navigation action. +"Stated clearly" means the answer is plain and near the top of where you +land — not something inferred from paragraphs of surrounding technical +detail. + +| # | Question | `main` | This branch | Destination / change | +|---|---|---|---|---| +| 1 | Publish without installing anything? | Pass, 2 actions | Pass, 0 actions | Homepage now states the dashboard Drop path before CLI install. | +| 2 | How much does it cost? | Pass, 1 | Pass, 1 | `/billing` | +| 3 | Add a custom domain? | Pass, 1 | Pass, 1 | `/domains`; the new lead states the capability directly. | +| 4 | Undo a bad publish? | Pass, 1 | Pass, 1 | `/versions`; the new lead says rollback does not rebuild. | +| 5 | Publish without an account — what happens? | Pass, 1 | Pass, 1 | `/anonymous-and-claim` | +| 6 | Let one specific person see my site? | Pass, 1 | Pass, 1 | `/access` | +| 7 | Put a password on my site? | Pass, 1 | Pass, 1 | `/access` | +| 8 | What exactly is a "Space"? | Pass, 1 | Pass, 1 | `/spaces`; the new lead defines it. | +| 9 | Connect Claude to publish for me? | Pass, 1 | Pass, 1 | `/agents` | +| 10 | Does it work with Next.js? | Pass, 1 | Pass, 1 | `/recipes/next` | +| 11 | Add a database to my site? | Fail, 1 | Pass, 1 | `/database` now gives the Zero setup prerequisite before query instructions. | +| 12 | My build failed — what do I do? | Pass, 1 | Pass, 1 | `/troubleshooting` now starts with the Space Overview diagnostic step. | +| 13 | See my site's traffic? | Pass, 1 | Pass, 1 | `/stats`; the new lead also states that crawler traffic is excluded. | +| 14 | Use my own logo on error pages? | Pass, 1 | Pass, 1 | `/customization`; `/site-pages` adds layout detail in a second action. | +| 15 | Invite a teammate? | Pass, 1 | Pass, 1 | `/teams` | +| 16 | Free plan limits? | Pass, 1 | Pass, 1 | `/limits` | +| 17 | Connect GitHub for auto-deploy? | Pass, 1 | Pass, 1 | `/git`; the new lead names the GitHub route. | +| 18 | Schedule something hourly? | Pass, 1 | Pass, 1 | `/crons`; the new lead names `sf.jsonc` and publish timing. | +| 19 | Something broke — where do I get help? | Fail, 1 | Fail, 1 | `/troubleshooting` still gives no support channel. | +| 20 | Can I resell this under my own brand? | Pass, 1 | Pass, 1 | `/platforms` | + +**Candidate score: 19/20 pass at ≤3 clicks, 1 unresolved failure.** Q11 +failed on the first pass and was fixed before this result was recorded. + +### Failure 1 (Q11) — fixed + +The single most predictable click for "add a database" — the page literally +titled **Database** — silently assumed Zero was already running and never +explained how to turn it on. The real instructions live on a differently +named page (**Dynamic sites with Zero**) that the question wouldn't point +you to. Fixed: added a note at the top of `content/(dynamic)/database.mdx` +("Don't have a database yet?") naming the exact `sf.jsonc` key and `sf init` +flag, cross-linking to Zero. + +### Failure 2 (Q19) — flagged, not fixed + +There is no discoverable support/contact/community surface anywhere in the +site — no footer, no "Help" nav item, no status page. "Contact support" +appears exactly 3 times in the whole corpus +(`troubleshooting.mdx`, `domains.mdx` ×2) and **never once says how** — no +email, form, or link. This is not a documentation bug I can fix with facts +I have: inventing a plausible-looking support email or link would violate +the same "document only shipped, verifiable behavior" rule this whole +project has followed, and would be actively worse than the current honest +gap. **This needs real input — an actual support channel — before anyone +can write the fix.** + +## Part 2: Manual command-accuracy comparison (20 questions) + +Methodology: every answer cross-checked against `generated/cli/index.md` and +`generated/openapi/api.json` — the frozen, producer-owned ground truth — not +executed against a live backend (none available in this environment). +"Works" means the command, flag, or endpoint shown is real, current, and +internally consistent with the generated reference. + +| # | Question | `main` | This branch | +|---|---|---|---| +| 1 | Install command (npm)? | Pass | Pass | +| 2 | Publish the current directory? | Pass | Pass | +| 3 | Force upload vs. force build? | Pass | Pass | +| 4 | List all versions? | Pass | Pass | +| 5 | Roll back to a version? | Pass | Pass | +| 6 | Add a domain as primary? | Pass | Pass | +| 7 | Check domain/DNS verification? | Pass | Pass | +| 8 | Create an API key with a preset? | Fail | Fail | +| 9 | curl to publish via HTTP API? | Pass | Pass | +| 10 | Bearer-token header? | Pass | Pass | +| 11 | Set an environment variable? | Pass | Pass | +| 12 | View build logs? | Pass | Pass | +| 13 | Connect a GitHub repository? | Pass | Pass | +| 14 | Create a team? | Pass | Pass | +| 15 | CLI exit code for auth failure? | Pass | Pass | +| 16 | MCP server URL + transport? | Pass | Pass | +| 17 | Run a cron on demand? | Pass | Pass | +| 18 | Open a SQL console? | Pass | Pass | +| 19 | Password-protect a path? | Pass | Pass | +| 20 | Webhook signature header + algorithm? | Pass | Pass | + +**Score: 19/20 questions pass.** Q8 fails because of 2 real bugs in that +one question. + +### Bug 1 — in the frozen generated reference (not fixed here) + +`generated/cli/index.md` documents `sf api-keys create --preset` with a +7-value enum (`ci_deploy`, `space_publisher`, `space_admin`, +`domain_manager`, `team_admin`, `billing_viewer`, `partner_admin`) — then its +own usage example runs `--preset full_access`, a value that isn't in that +list. Anyone who copies the example gets a validation error. This exact text +is also baked into `content/cli/reference.md` (the materialized build +overlay). Per `AGENTS.md`, generated content is fixed at its source in the +product monorepo and re-exported, never hand-edited here — **this needs a +fix in the monorepo's CLI source**, flagged, not touched, in this repo. + +### Bug 2 — in hand-authored content (not fixed here, flagging for a decision) + +`content/(account)/api-keys.mdx`'s preset table lists only 6 of the 7 real +presets (missing `partner_admin` entirely), and contradicts itself on the +default: line 34 says `sf api-keys create` defaults to `space_publisher` +(matching the generated reference); line 60 says `space_admin` is "the +default when a request names no preset." Unlike Bug 1, this one *is* +editable here — it's authored content, not generated — but fixing it needs +a judgment call on what the second "default" claim actually means (CLI vs. +a different code path), which this comparison can't resolve on its own. +Left for a deliberate follow-up rather than guessing. + +## Part 3: Built-docs reading-experience checks (12 tests) + +The same `scripts/docs-experience.test.mjs` file ran against each separately +built site. All 12 fail on `main` and pass on this branch. The old build's +existing 14 corpus, index, and audit unit tests pass, as do all 14 on this +branch, making the complete suite **14/26 versus 26/26**. This suite was +designed to guard the paths changed in this PR; its baseline failure rate +should not be generalized to the quality of every old docs page. + +| Check against the authored or built page | `main` | This branch | +|---|---|---| +| Authored pages open with their subject, not a templated page promise | Fail | Pass | +| Homepage offers no-install Drop before the CLI install | Fail | Pass | +| Quickstart offers no-install Drop before CLI steps | Fail | Pass | +| Homepage links unfamiliar terms to glossary definitions | Fail | Pass | +| Database gives the Zero prerequisite before query instructions | Fail | Pass | +| Troubleshooting gives a first diagnostic step before the error catalog | Fail | Pass | +| Versions lead says whether rollback rebuilds | Fail | Pass | +| Crons lead says where and when a schedule takes effect | Fail | Pass | +| Environment variables lead says which scope wins | Fail | Pass | +| Access lead says what makes a Space private | Fail | Pass | +| Caching lead says how to force a fresh response | Fail | Pass | +| Traffic stats lead says whether crawlers count | Fail | Pass | + +The writing comparison in [DOCS_WRITING_QA.md](DOCS_WRITING_QA.md) covers the +larger edit: 65 changed opening paragraphs across 78 edited existing pages, +with 53 templated openings reduced to zero and median lead length moving from +34 to 23 words. Those are structural and editorial signals, not a reader +comprehension score. + +## Part 4: Docs-only agent evaluation (23 questions) + +Blume gives Codex only the built docs through its search, page, and navigation +tools. For each question, a separate judge checks the answer against the +expected facts. Both builds use the same corrected `evals.yaml`, the same +Blume version, and the same agent CLI. Each result is one run, so a difference +needs a repeat before we attribute it to the writing. + +The first pass exposed four defects in the eval questions or grading key. We +corrected them before the final side-by-side run: + +- **Rate limits:** The question asked only for the credential limit while the + key also required the unauthenticated IP limit. It now asks for both. +- **Plus price:** The key said `$4.99`, but both built Billing pages say the + upcoming Plus base price is `$15` per team per month. The question now + names those upcoming terms. +- **Free file limit:** The key assigned the anonymous `50 MiB` cap to the + claimed Free plan. The docs say `1 GiB` for a claimed Free plan and + `50 MiB` for anonymous publishing. The question now asks for both. +- **Anonymous claim:** The original question conflated the serving deadline + with the later recovery period. It now asks separately when an anonymous + Space stops serving and how long the key remains valid for claiming. + +The original, uncorrected pass is excluded from the headline comparison. It +gave a false failure on both builds for the first three items. It also gave +one old-only failure for anonymous claiming that passed when rerun unchanged +on both builds. That result did not establish a docs improvement. + +The corrected full run passed **23/23 on `main` and 23/23 on this branch**, +with no errors or skips. This establishes factual retrieval on these 23 +questions in this run. It does not grade prose quality or real agent task +completion. The full agent run used the earlier builds made with Bun 1.4.2. +Both sites were then rebuilt with the repository's pinned Bun 1.3.11. Two +Vale-only wording edits changed the Zero and CLI pages; their affected agent +questions (18 and 22) were rerun against the final build and both passed. + +| # | Agent question | `main` | This branch | +|---|---|---|---| +| 1 | Minimum Node.js version for the `sf` CLI? | Pass | Pass | +| 2 | New Space hostname and single-version URL? | Pass | Pass | +| 3 | `sf login` code validity and credential lifetime? | Pass | Pass | +| 4 | Anonymous serving deadline and later claim period? | Pass | Pass | +| 5 | DNS records to connect `example.com`? | Pass | Pass | +| 6 | Does `sf domains rm` delete the domain? | Pass | Pass | +| 7 | Authentication-failure CLI exit code? | Pass | Pass | +| 8 | HTML cache header and cache busting? | Pass | Pass | +| 9 | List-page sizes and pagination? | Pass | Pass | +| 10 | Authenticated and unauthenticated API request limits? | Pass | Pass | +| 11 | `Idempotency-Key` retention? | Pass | Pass | +| 12 | Hosted Spacefast MCP server URL? | Pass | Pass | +| 13 | MCP execute sandbox limits? | Pass | Pass | +| 14 | Team roles? | Pass | Pass | +| 15 | Upcoming Plus base price? | Pass | Pass | +| 16 | Claimed Free and anonymous single-file limits? | Pass | Pass | +| 17 | Project config filename? | Pass | Pass | +| 18 | Turn on the Zero runtime? | Pass | Pass | +| 19 | `_redirects` versus `sf.jsonc` rule precedence? | Pass | Pass | +| 20 | Does `sf publish` upload `node_modules` and `.git`? | Pass | Pass | +| 21 | Spacefast API-key prefix? | Pass | Pass | +| 22 | CLI behavior with `CI=1`? | Pass | Pass | +| 23 | Does rollback rebuild? | Pass | Pass | + +## Running this again + +- Build current `main` and this branch in separate clean checkouts with + Bun 1.3.11, Node 24 or newer, and `bun run build` in each. The comparison + here used Blume 2.0.3 and Codex CLI 0.160.0 on October 5, 2026. +- **Part 1:** Start each browser check at `/docs/`. Follow only rendered + navigation, tabs, and page links; count each navigation action. Read the + destination answer, not just its title. The 20-question click-path pass is + manual. We also checked the rebuilt homepage and Database page in a browser + and inspected all 20 built destination pages side by side. +- **Part 2:** Cross-check the 20 command/API answers against the same + `generated/cli/index.md` and `generated/openapi/api.json` in each build. + `bun run verify:commands` checks examples more broadly, but this 20-question + comparison is still a manual cross-check rather than a live API test. +- **Part 3:** Run the candidate's `scripts/docs-experience.test.mjs` against + each built checkout. For the old checkout, copy that test and the Node-based + `scripts/audit-composed-site.test.mjs` into a temporary copy, then run + `node --test scripts/*.test.mjs`. On this branch, `bun run test:docs` runs + the same Node test command. The old checkout should fail the 12 new + experience checks while passing the 14 existing unit checks. +- **Part 4:** Run `./node_modules/.bin/blume eval --agent=codex + --file=evals.yaml --json` in this branch, and point the old checkout's + `--file` argument at the **same** corrected `evals.yaml`. Blume runs one + reader and one judge per question. Keep failures, errors, and skips separate; + repeat a differing result before claiming an improvement. The agent's model + was not pinned, so these single-run scores are not deterministic. diff --git a/DOCS_TEST_SUITE.md b/DOCS_TEST_SUITE.md new file mode 100644 index 00000000..10777933 --- /dev/null +++ b/DOCS_TEST_SUITE.md @@ -0,0 +1,31 @@ +# Docs regression tests + +Run the executable suite after building the site: + +```bash +bun run build +bun run test:docs +``` + +CI runs `test:docs` after the production build. It uses Node's test runner and exits nonzero on a failed assertion. The experience checks read the built Markdown in `dist/`, so a passing source edit alone cannot satisfy them. Existing unit tests for the docs corpus, LLM index, and composed-site audit run in the same command. + +## Reader-facing contracts + +`scripts/docs-experience.test.mjs` checks that: + +- Authored pages open with a subject or action instead of the repeated “After this page…” promise. +- The homepage and Quickstart offer dashboard Drop before CLI installation, and the linked Publishing page describes the Drop flow. +- The homepage links to a glossary that defines its recurring product terms. +- Database links the full Zero runtime declaration and names its required server file before teaching queries. +- Troubleshooting gives readers a first diagnostic step before listing error codes. +- The opening paragraphs on Versions, Crons, Environment variables, Access, Caching, and Traffic stats answer specific reader questions. Each test names the question and the evidence expected near the top of the built page. + +These are regression contracts for the changed entry paths and first-screen explanations. They make a future edit fail if it hides those answers again. They do not prove product behavior, grade tone, prove that shorter text is clearer, or measure whether a person can complete a real task. Check each expected answer against the public contract and the page's exceptions before encoding it in a test. + +The October 6 review corrected assertions that reinforced overbroad access and traffic claims. The checks now distinguish views from requests and unique visitors, retained ready versions from other versions, and removal of one public grant from removal of all access. Cache refresh is a request, not an unconditional freshness guarantee. + +## Other gates + +The CI build also runs `verify:generated`, command-example verification, type checking, strict link validation, the composed-site audit, public-safety verification, Vale, and route verification. The existing `evals.yaml` asks an agent factual-retrieval questions from the built docs; it is a separate evaluation and is not part of `test:docs`. + +Known content gaps remain documented in the [point-in-time QA comparison](DOCS_QA_COMPARISON.md): no confirmed support channel and conflicting API-key preset guidance. That comparison records manual observations, not test-suite results. The [writing audit](DOCS_WRITING_QA.md) records before/after editorial signals and its limits. diff --git a/DOCS_WRITING_QA.md b/DOCS_WRITING_QA.md new file mode 100644 index 00000000..6c2a3638 --- /dev/null +++ b/DOCS_WRITING_QA.md @@ -0,0 +1,93 @@ +# Authored-docs writing audit + +The October 5 audit below measured structure and wording, but missed factual overstatements. Its candidate examples are historical, not verified guidance. The [October 6 semantic review](#semantic-review-october-6-2026) records the corrections and their evidence. Repeatable checks for the current docs are in [DOCS_TEST_SUITE.md](DOCS_TEST_SUITE.md). + +## Method + +Run `node scripts/compare-writing.mjs origin/main` from the repository root before this branch merges. It selects existing authored MDX pages changed from that baseline, extracts each page's first prose paragraph after frontmatter, and counts words. A templated opening starts with “After this page” or “By the end of this page.” The 30-word threshold is a review signal, not a readability guarantee. + +I also read all 65 changed opening paragraphs side by side with their page titles. For each, I checked whether the first paragraph gives a practical fact or action or repeats a generic promise about the page. That review found five leads starting with a narrow implementation detail; they were revised to lead with the page's main job. I checked that each displaced detail remains elsewhere on its page. + +## Results + +| Signal | Current `main` | This branch | Scope | +|---|---:|---:|---| +| Repeated “After this page…” or “By the end of this page…” openings | 53 | 0 | 78 changed existing MDX pages | +| Opening paragraphs over 30 words | 43 | 13 | 65 changed opening paragraphs | +| Median opening words | 34 | 23 | 65 changed opening paragraphs | + +Of the 65 changed leads, 62 are shorter, one has the same word count, and two are longer. The candidate also adds a glossary and a no-install publishing route on the homepage. Those are separate navigation and comprehension aids; this word-count table does not score them. + +The edited leads now put several useful answers before the reader has to scan the page: + +| Page | Reader's question | Current `main` opening | Candidate opening | +|---|---|---|---| +| Versions | Does rollback rebuild the site? | Promises to explain rollback | Says rollback only repoints `live`; no rebuild | +| Crons | Is there a dashboard editor? | Promises to explain scheduling | Says schedules live in `sf.jsonc` and take effect on publish | +| Environment variables | Which value wins when team and Space both set a name? | Promises to explain scopes | Says the Space value wins | +| Access and sharing | What makes a Space private? | Promises to explain access | Says removing every grant makes it private | +| Caching | How do I force a fresh response? | Promises to explain cache behavior | Says republishing forces one | +| Traffic stats | Are crawlers included? | Promises to explain counts | Says crawler traffic is excluded | + +These rows record what the candidate openings claimed at the time, not an independent six-question success rate. Review later found that several claims dropped necessary qualifications, including the caching and traffic examples. + +For example, Versions opened with “After this page you know what a version holds, how it reaches `ready`, how the `live` pointer moves, and how to roll back to any earlier version in seconds.” It now opens with “A rollback doesn't rebuild anything — it just repoints `live` at a version that already exists, which is why it takes seconds, not minutes.” The new sentence answers the likely rollback question; the page still explains version states below it. + +## Review findings and limits + +The manual review caught five candidate leads that were shorter but spent the first sentence on a less useful detail: slug validation on Spaces, polling mechanics on Logs, archive flags on Frameworks and builds, CLI naming on Publish from Git, and remote WP-CLI output on WordPress. Each now leads with the page's main task or mental model. The removed detail was checked elsewhere on the same page and retained or moved into the body. + +This audit establishes changes in structure and in which facts appear first. It does not show that real readers complete tasks faster or understand the docs better. Shorter openings could also lose useful context for some readers. That requires reader testing. Vale now passes after two existing technical plurals (`GETs` and `TTYs`) were written as plain explanations; its rules do not detect repeated sentence structures or judge whether a page leads with the right fact. + +## Semantic review, October 6, 2026 + +Reviewed every changed MDX hunk in the PR: 78 existing pages plus the new glossary. Compared each rewrite with its original wording, then checked broader claims against the relevant procedures, exceptions, sibling guides, and producer-owned public reference snapshot. This was a documentation consistency audit, not a live product test or a reread of every unchanged paragraph. + +The first review fixed eight findings covering stats, caching, routing, anonymous key recovery, Zero pricing, hosted MCP authentication, team plan limits, and Zero setup. The follow-up applied the same reasoning across the full rewrite and corrected related claims at other entry points. + +| Claim family | Correction | Evidence checked | +| --- | --- | --- | +| Retry safety | Name the key, matching request scope, 24-hour replay window, and unstored outcomes that execute again | `content/api/idempotency.mdx`, Send a key / What is not stored | +| Pagination | Limit the common cursor model to endpoints that use it; retain endpoint defaults, ordering, offset paging, and unpaginated lists | `generated/openapi/api.json`: `searchDocs`, `listSpaceStorageObjects`, `listSpaceDomains` | +| Publish and rollback | Preserve no-op publishes, ready/retained targets, manual promotion, and preview behavior | Public reference: `createSpaceVersion`, `promoteSpaceVersion`; `content/(concepts)/versions.mdx`; `content/(publish)/ci.mdx` | +| URL lifetime | Separate a stable version URL from retained files; distinguish domain attachment from slug rename | `content/(concepts)/spaces.mdx`, Renaming; `content/cli/versions.mdx`, sf versions rm | +| Access | Revoking one matching grant does not revoke other grants or the team's permissions | `content/(serve)/access.mdx`, scoped grants; `content/(concepts)/teams.mdx`, role and default-access tables | +| Runtime setup | Scope auto-detection to Functions layouts; preserve the Zero declaration and the Functions database alternative | `content/(dynamic)/functions.mdx`, Where the code lives / Declare it; `content/cli/db.mdx` | +| Variables and logs | A queued re-finalize can apply variables; logs have retention, ingestion delay, and static-runtime limits | `content/(dynamic)/environment-variables.mdx`; `content/(dynamic)/logs.mdx`; `listSpaceRuntimeLogs` | +| Archives and Git | Preserve prebuilt archives, branch auto-deploy controls, and the GitHub App prerequisite | `generated/cli/index.md`, sf publish `--prebuilt`; `content/(publish)/git.mdx`; `content/cli/git.mdx` | +| CLI helpers | Linking selects a Space rather than removing all publish options; apply and continuation helpers mutate state; continuation needs claim approval | `content/cli/project.mdx`; `content/cli/agent-commands.mdx`; `content/(publish)/anonymous-and-claim.mdx` | +| WP-CLI and storage | Remote WP-CLI returns no printed output, even for read commands; object IDs do not replace read keys | `content/(dynamic)/wordpress.mdx`, Local versus remote; `content/(dynamic)/storage.mdx`, returned URL | +| Agent reach | Keep supported-client detection, skill installation, permission ceilings, human-only actions, and team-automation revocation exceptions | `content/cli/agents.mdx`; `content/agents/skills.mdx`; `content/agents/permissions.mdx` | +| Ownership and billing | Distinguish self-serve teams from partner customer ownership; billing reads differ from plan changes; key rotation depends on switching consumers first | `createSpace` public reference; `content/platforms/partner-api/customers.mdx`; `content/(account)/billing.mdx`; `content/(account)/api-keys.mdx` | +| Cache and schedule application | Repeat public-cache exceptions in troubleshooting; crons follow the live version | `content/(serve)/caching.mdx`; `listSpaceCrons` public reference | +| Sentence splitting | Limit missing generated CSS to the dynamic class instead of declaring the whole app unstyled | `content/(dynamic)/zero-runtime.mdx`, Styling | + +Coverage by original PR section: + +| Section | Pages compared | +| --- | --- | +| Account | 3: api-keys, authentication, billing | +| Concepts | 3: spaces, teams, versions | +| Dynamic | 8: crons, database, environment-variables, functions, logs, storage, wordpress, zero-runtime | +| Publishing | 8: anonymous-and-claim, ci, frameworks, git, publish, recipes/html, recipes/next, wordpress-data-sources | +| Reference | 3: config-file, glossary, limits | +| Serving | 8: access, caching, customization, domains, routing, site-pages, stats, urls | +| Agents | 9: claude-code, claude-desktop, codex, cursor, mcp-server, other-clients, permissions, sf-setup, skills | +| API | 9: authentication, errors, idempotency, index, operations, pagination, rate-limits, sdk, webhooks | +| CLI | 20: agent-commands, agents, api-keys, api, builds, db, domains, env, git, index, login, project, publish, share, source, spaces, storage, teams, versions, zero | +| Entry pages | 3: index, quickstart, troubleshooting | +| Platforms | 5: partner-api/configuration, customers, go-live, index, tokens | + +The style guide and contributor instructions now require this meaning check. Existing built-output assertions were updated where they reinforced a misleading claim. No new regex suite is presented as independent proof of product behavior. Generated references remain producer-owned and unchanged. The previously recorded support-contact and API-key preset gaps remain outside these corrections. + +Verification with Bun 1.3.11 and Node 24 passed: frozen dependency install, generated-reference and command-example checks, type check, strict link validation, production build, all 26 docs tests, composed-site audit, public-safety check, Vale, route verification, and `git diff --check`. + +## Review follow-through, October 6, 2026 + +The next review caught two places the semantic pass had not reconciled: the `sf teams` lead contradicted its command examples, and the pagination eval still assumed a universal default. The team CLI guide now names the documented credential restriction beside all five affected commands, including invitation acceptance, and replaces success examples with dashboard instructions. The Teams guide now recommends the CLI only for listing invitations and states the credential restriction beside its role table. + +These corrections follow the authored authentication and permissions contract in `/authentication`, `/api-keys`, and `/agents/permissions`. The generated CLI snapshot lists command syntax but does not establish that a CLI credential can execute the action. No team membership was changed to test authorization, and the producer-owned snapshot was not edited. + +The pagination eval now expects endpoint-specific defaults, offset paging, and unpaginated lists. Checking the adjacent eval expectations also found an overly broad cache-purge answer; that case now names public static HTML, best-effort purge, and immutable version-hostname exceptions. These are corrected evaluation expectations, not a claim that the agent-based eval has run. + +Verification passed with Bun 1.3.11 and Node 24: the full repository check sequence, all 26 docs tests, and `git diff --check`. All 23 eval cases parsed and passed a structural check; no agent-based eval result is claimed. diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md new file mode 100644 index 00000000..dd2fa3b5 --- /dev/null +++ b/STYLE_GUIDE.md @@ -0,0 +1,267 @@ +# Spacefast docs style guide + +This is the human-readable version of the rules `bun run verify:prose` +enforces by machine, via [Vale](https://vale.sh) and the 20 rule files in +`styles/Spacefast/`. If you're writing or editing a page and want to know +*why* something got flagged, or just want to write it right the first time, +start here. The Vale files are the source of truth for exact patterns; this +document explains the reasoning and gives the full picture in one place. + +## Where this comes from + +Spacefast didn't have a documentation style guide — it had Vale rules and a +few bullets in `AGENTS.md`, but nothing a person could read start to finish. +This guide fills that gap. The concision principle below is adapted from +WooCommerce's (Automattic-owned) developer documentation guides: + +- [Technical Documentation Style Guide](https://developer.woocommerce.com/docs/contribution/contributing-docs/style-guide/) +- [Grammar, Punctuation, and Capitalization guide](https://developer.woocommerce.com/docs/best-practices/coding-standards/grammar-punctuation-capitalization/) + +One deliberate deviation: WooCommerce's guide writes in 3rd-person/imperative +voice ("Add an embed block to your page"). Spacefast's docs are 2nd-person +("you") throughout, and that doesn't change here — only the concision and +mechanical rules are adopted, not the voice. + +## Voice + +Direct, no-BS, and a little playful. Second person — "you," not "the user" +or "one." Prefer the best path over an encyclopedia of alternatives: one +good way to do a thing beats three options with trade-off tables, unless the +trade-off is the point. + +## Be concise, lead with importance + +This is the rule behind the biggest cleanup pass this corpus has had: state +the key fact first, in the sentence, the paragraph, and the page. Don't +make a reader walk through three subordinate clauses to find the one thing +that matters. + +In practice: + +- **One idea per sentence, where reasonable.** A sentence chaining three or + more unrelated clauses ("...which X, when Y, how Z, and where W...") is a + sign to split it or cut the least important clause, not a sign you've + covered the topic thoroughly. The page's own headings carry the rest — + an opening sentence or a summary line doesn't need to be a table of + contents. +- **`styles/Spacefast/SentenceLength.yml` flags sentences over 30 words**, + at suggestion level — it doesn't block CI. Treat it as a floor, not the + definition of "wordy." A 22-word sentence that chains four clauses is + still a problem this rule won't catch; use judgment, not just the word + count. +- **Cut filler, preserve conditions.** Remove a phrase only if the shorter + sentence remains true for the same readers and situations. A plan limit, + required flag, credential type, runtime, or exception is not filler. + +## Preserve meaning when shortening + +Read a rewritten lead beside the full procedure and its exceptions. Check +the original wording and the public reference before treating the shorter +sentence as equivalent. A fact moved out of a subsection must keep that +subsection's scope: a hosted MCP guarantee does not describe local stdio. + +- Keep prerequisites next to the action: a ready version for rollback, + an idempotency key for replay, or the plan required to add members. +- Keep distinct states distinct: available versus free, ready versus live, + a permanent URL versus retained files, and configuration saved versus applied. +- Check words such as "every," "always," "never," "only," and "any" against + counterexamples. Name the supported case instead of broadening a claim. +- After splitting a sentence, make sure its condition still governs every + sentence that depends on it. Repeat the condition when necessary. +- Check sibling guides, CLI pages, descriptions, glossary entries, and tests + for the same claim. A correction is incomplete if another entry point + teaches the old rule. + +For example, "Every list uses cursors" drops offset-based and unpaginated +endpoints. "Cursor-paginated endpoints return `pagination.nextCursor`; check +the endpoint's limits and ordering" preserves the useful rule and its scope. + +Tests of built docs can check that a prerequisite or exception stays visible. +They do not prove product behavior. A regex matching confident wording is +not evidence that the wording is true, and shorter copy is not a pass criterion. + +## Concision examples + +Worked example, from the page-opener cleanup: + +> Before: "After this page you know which directory your framework +> produces, when to publish that directory yourself versus letting +> Spacefast build, how detection picks commands, and where to read a +> failing build." (31 words, 4 clauses) +> +> After: "Publish your own build output directly, or hand Spacefast the +> source and let it build — the logs tell you which one went wrong if it +> fails." (27 words, 2 ideas joined by an em dash) + +Another: + +> Before: "After this page you can mint an API key with the right +> permissions, use it against the API, rotate it without downtime, and +> recognize every other credential Spacefast hands you by its prefix." (33 +> words, 4 clauses) +> +> After: "Team owners and admins can mint scoped API keys. To rotate without +> interrupting an integration, create a replacement, switch the integration +> to it, verify a request, and only then revoke the old key." + +This version keeps the role prerequisite and the order that makes rotation +safe. The credential-prefix table can stay below; the rotation condition cannot. + +(These two "after" versions also show the fix from the next section — no +"after this page" framing. The intermediate step, where the sentence was +merely shorter but still templated, is history now; don't resurrect it.) + +## Don't template the opening sentence + +The example above still has a problem, caught in a later pass: "After this +page you can/know X" and "By the end of this page you have Y" are +templates — the same framing device, repeated verbatim as the literal first +move on every page. That's a recognizable AI-writing tell, not a style +choice: it's the generic "learning objectives" scaffold a model defaults to +when it has to open a doc page without judging what's actually most +interesting about *that* page. A human varies the opening move page to +page. No Vale rule catches this — `AISpeak.yml` bans specific filler words, +not repeated sentence-level structures, so a templated opener can pass +every mechanical check and still read as generic. + +Open with the fact itself, not a sentence announcing that a fact is coming: + +> Templated: "After this page you can mint a scoped API key and rotate it +> without downtime." +> +> Direct: "To rotate a key without interrupting an integration, switch to +> its replacement and verify a request before revoking the old key." + +> Templated: "After this page you know whether to publish your own build +> output or let Spacefast build it." +> +> Direct: "Publish your own build output directly, or hand Spacefast the +> source and let it build — the logs tell you which one went wrong if it +> fails." + +Vary the construction — "you can," the mechanism stated as fact, a gerund +opener ("Attaching a custom domain is..."), whatever fits that page — and +don't let the opener become a word-for-word echo of the page's frontmatter +`description` either; say the same thing in different words, or pick a +different angle entirely. + +## Banned words and phrases + +Vale errors on these. They're not style preferences — they're specific, +named failure modes. + +**Hype** (`Hype.yml`) — say what the thing does instead: seamless(ly), +delve, robust, cutting-edge, state-of-the-art, best-in-class, world-class, +game-changing/changer, revolutionize, supercharged, effortless(ly), +hassle-free, empowers, streamlines, blazing(ly) fast, tapestry, symphony, "a +beacon of," "a testament to," transformative, groundbreaking, pivotal, +multifaceted, holistic, "shed light on," "paves the way for," "at its +core," "in essence," "that being said," "in today's." + +**Condescension** (`Condescension.yml`) — assumes it's easy for the reader; +the instruction works without it: simply, easily, "just click," "just run," +obviously, "of course." + +**Weasel words** (`WeaselWords.yml`) — hedges instead of committing: "helps +ensure," "may be able to," "can potentially," "could potentially." Commit +to what the thing does, or state the real condition. + +**Intensifiers** (`Intensifiers.yml`) — intensity without information. Show +the number or cut it: extremely, dramatically, exceptionally, incredibly, +remarkably, truly, undoubtedly, significantly. + +**AI-speak** (`AISpeak.yml`) — corporate filler: "leverage" → use, +"utilize" → use, "facilitate" → help, "in order to" → to, +"furthermore"/"moreover" → also. Strip "it is important to note that" and +"please note that" entirely — they add nothing. + +## Tighten these phrases + +`Wordiness.yml` swaps wordy constructions for plain ones: + +| Instead of | Use | +|---|---| +| due to the fact that | because | +| in the event that | if | +| at this point in time | now | +| has/have the ability to | can | +| is/are able to | can | +| in a timely manner | promptly | +| a number of | several | +| the majority of | most | +| on a regular basis | regularly | +| make use of | use | +| in the process of | *(cut it)* | +| a wide range of | many | +| a variety of | many | + +`PhrasalVerbs.yml` — the verb is two words, the noun is one: "login to" → +log in to, "logout of" → log out of, "setup a/the/your" → set up a/the/your, +"backup your" → back up your, "sign into" → sign in to. + +`LatinAbbreviations.yml` — spell it out: "e.g." → for example, "i.e." → +that is. + +## Brand and vocabulary + +- **GitHub, JavaScript, TypeScript, npm, Node.js, macOS, OpenAPI, email, + website** — exact casing, every time (`Branding.yml`). +- **Spacefast** is capitalized in prose. Identifiers stay lowercase and get + backticked: `@spacefast/sdk`, `/spacefast`, `spacefast.com` + (`ProductName.yml`). +- **WP Cloud** — exact casing, never "wp cloud," "wp.cloud," or hyphenated + (`WpCloudBrand.yml`). Prefer "infra" generally; name WP Cloud only when + the external reference is genuinely necessary. Never say "the hosting + provider" for Spacefast's own infra (`HostingProvider.yml`) — generic + references to *other* hosting providers are fine. +- **"API key," never "access token"** (`ApiKey.yml`) — "OAuth access + token" is the one exempted phrase, since that's the protocol's own term. + +## What never appears in public copy + +This repo is public, including its history. Two rule files exist +specifically to keep internal infrastructure and vendor names out of it +(`Internals.yml`, `Providers.yml`): + +- Internal infra names: batcache, PlanetScale, pgbouncer, nginx, Caddy, + "web server," "runtime engine," "monorepo," local filesystem paths + (`/Users/...`, `/home/...`). Say "CDN," "edge," or name the user-facing + behavior instead. +- Internal vendor names: E2B, Pierre, `code.storage`. Say "Spacefast + Builds," "Spacefast CI," or "infra" instead. + +If you're not sure whether a name is safe to publish, assume it isn't and +ask — `bun run verify:public-safety` catches a lot of this mechanically, +but not everything. + +## Mechanics + +- **Oxford comma** — use it. Advisory only (`OxfordComma.yml`); the + checker can't tell a serial list from a compound verb phrase, so it never + blocks, but the house style is it belongs there. +- **Link text names the destination.** Never "[click here]" or "[this]" — + Vale errors on both (`LinkText.yml`). +- **Unknown technical words** go in `styles/Spacefast/spelling-exceptions.txt` + (sorted, case matters for proper nouns) rather than getting flagged as + typos. +- **Identifiers in prose** — commands, enum values, claim names, package + names — get backticks, not prose styling. Vale skips code spans, so this + also sidesteps most false-positive spelling/casing alerts. + +## What this guide doesn't cover + +The changelog (`content/changelog/**`) is a historical record — only +public-safety and brand-casing rules apply there; shipped release notes +keep their original wording. Generated reference content +(`generated/openapi/**`, `generated/errors/**`, `generated/cli/**`, etc.) is +producer-owned and fixed at its source in the product monorepo, not edited +here — but its prose (OpenAPI `summary`/`description` fields) is still +linted by the same Vale rules. + +## Before you open a PR + +```bash +bun run verify:prose +``` + +Zero alerts is the bar. See `AGENTS.md` for the full verification chain. diff --git a/content/(account)/api-keys.mdx b/content/(account)/api-keys.mdx index c91d9264..1a2fa46b 100644 --- a/content/(account)/api-keys.mdx +++ b/content/(account)/api-keys.mdx @@ -5,7 +5,7 @@ sidebar: order: 2 --- -After this page you can mint an API key with the right permissions, use it against the API, rotate it without downtime, and recognize every other credential Spacefast hands you by its prefix. +Team owners and admins can mint scoped API keys. To rotate without interrupting an integration, create a replacement, switch the integration to it, verify a request, and only then revoke the old key. ## Create an API key @@ -57,7 +57,7 @@ A preset is a named bundle of permissions. Pick the narrowest one that does the | `billing_viewer` | Billing viewer | Read team and billing information, nothing else | | `team_admin` | Team admin | Everything this team can do | -`space_publisher` and `ci_deploy` carry the same publish permissions. `space_admin` is the default when a request names no preset, and it deliberately stops short of `team_admin`: it can never mint keys, manage members, or touch billing. +`space_publisher` and `ci_deploy` carry the same publish permissions. `space_admin` is the default when a request names no preset. It deliberately stops short of `team_admin`: it can never mint keys, manage members, or touch billing. ## How permissions compile @@ -91,9 +91,9 @@ A credential that tries gets a `403` with `authorization_level_not_allowed`. Do ## Rotate a key -A secret is immutable, so rotation means a new key. In the dashboard, **Replace key…** mints a fresh key with the same policy and then offers to revoke the old one once your integrations have moved over. +A secret is immutable, so rotation means a new key. In the dashboard, **Replace key…** mints a fresh key with the same policy. It then offers to revoke the old one once your integrations have moved over. -Revoking is immediate. Anything using that key fails on its next request, and the row stays in the list marked revoked so the audit trail survives. +Revoking is immediate. Anything using that key fails on its next request. The row stays in the list marked revoked, so the audit trail survives. ## Every credential, by prefix diff --git a/content/(account)/authentication.mdx b/content/(account)/authentication.mdx index 4d95890f..841f1f49 100644 --- a/content/(account)/authentication.mdx +++ b/content/(account)/authentication.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -After this page you can create an account, sign in the way that suits you, lock the account down with two-factor, see every signed-in device, and log the `sf` CLI in from a terminal. +Creating a Spacefast account takes nothing but an email address and a six-digit code — no password required until you add one later. ## Create an account @@ -15,7 +15,7 @@ There is no password signup. Passwords are something you add later, from setting Two other ways in create an account for you: -- A social provider. Spacefast supports WordPress.com, Google, and GitHub, and the sign-in page shows the ones that are turned on. The provider must have verified your email address, otherwise Spacefast refuses the sign-in and tells you to verify it with the provider or use a mailed code instead. +- A social provider. Spacefast supports WordPress.com, Google, and GitHub, and the sign-in page shows the ones that are turned on. The provider must have verified your email address. Otherwise Spacefast refuses the sign-in and tells you to verify it with the provider or use a mailed code instead. - A team invitation link. The link already proves the inbox, so the accept page offers **Create account & join** with one optional **Name (optional)** field and no code to type. ## Sign in diff --git a/content/(account)/billing.mdx b/content/(account)/billing.mdx index ba4229ce..acf8d429 100644 --- a/content/(account)/billing.mdx +++ b/content/(account)/billing.mdx @@ -86,6 +86,6 @@ Reading is never blocked in either state. Settle the payment from the billing po ## Billing is dashboard-only -Plans, checkout, and cancellation live in the dashboard. There is no `sf` command and no API endpoint for changing a plan, and API keys and agent credentials cannot reach billing at all. +Plans, checkout, and cancellation live in the dashboard. There is no `sf` command and no API endpoint for changing a plan. API keys and agent credentials cannot make those changes. Reading is a different story. An agent can pull the resolved plan and limits from `GET /v1/teams/{teamId}/entitlements`, current counters from `GET /v1/teams/{teamId}/usage`, and the limits as they are enforced at publish time from `GET /v1/teams/{teamId}/plan-policy`. See [Usage](/usage) for what those numbers mean. diff --git a/content/(concepts)/spaces.mdx b/content/(concepts)/spaces.mdx index edb2d496..0734f010 100644 --- a/content/(concepts)/spaces.mdx +++ b/content/(concepts)/spaces.mdx @@ -5,11 +5,11 @@ sidebar: order: 1 --- -After this page you can pick a slug the API will accept, find any Space from the CLI or the dashboard, and predict exactly what a rename or a delete does to links you already shared. +A Space is one site with a stable hostname. Publishing creates versions inside it, and the Space decides which one visitors see. ## What a Space is -A Space is one site. It owns a slug, a hostname, an owner, and one pointer that says which version visitors get. Everything you publish lands in a Space as a new immutable version, and the Space decides which of them is live. +A Space is one site. It owns a slug, a hostname, an owner, and one pointer that says which version visitors get. A publish can create a new immutable version or report no changes, and the Space decides which version is live. Ids are `spc_` plus 32 hex characters: @@ -58,12 +58,13 @@ New Spaces use `https://