Skip to content

Release published docs to main - #306

Open
lucas-tortora wants to merge 86 commits into
mainfrom
published
Open

lucas-tortora wants to merge 86 commits into
mainfrom
published

Conversation

@lucas-tortora

Copy link
Copy Markdown

Summary

  • Promotes the published branch to main, replacing the legacy docs site with the current published Pear docs platform.
  • Merged main into published with all conflicts resolved in favor of published.
  • This PR is intended to entirely overwrite main with published content.

Conflict resolution

Three modify/delete conflicts were resolved in favor of published (legacy files kept deleted; content lives under the new content/ IA):

  • guide/making-a-bare-mobile-app.md
  • guide/making-a-pear-desktop-app.md
  • guide/starting-a-pear-desktop-project.md

What's included

  • Complete information architecture reorg (getting-started, how-to, reference, explanation)
  • Pear book integration and route aliases
  • @tetherto/docs-seo-* end-to-end (metadata, sitemap, robots, JSON-LD, OG)
  • Docs lint CI (internal links, Vale prose linting)
  • Link checking, review fixes, and example annotation tooling
  • Publish and preview GitHub workflows

Test plan

  • Verify PR shows as mergeable into main
  • Confirm CI checks pass (docs lint, build)
  • Spot-check key pages after merge: index, getting-started, reference sidebar

Made with Cursor

Didericis and others added 8 commits January 9, 2026 13:12
- modify youtube links
- modify gitignore
- modify featured table
* adjust pear init for GUI apps (#270)

* adjust other instance of pear init description

* feat: add pear book

- modify youtube links
- modify gitignore
- modify featured table

* Add pear book action

* Add publish and preview workflows

* Refactor preview workflow

* add route aliases for pear book

* reorder reference sidebar links

* move all pear-book config to file

* add guide alias/change prefix

* Publish

* Feat/rebase upstream deprecated run (#278)

* deprecate run

* cascading implications

* missing cmds, runtime tweaks, deployment stub

* add fumadocs
replace GB syntax

* fix: update logo and fav icon

* fix : update file to .mdx and update imege to use react component

* fix: replace raw HTML syntax with markdown in reference docs to md syntax

* chore: remove guide sectionto match deprecate-run upstream

* fix: remove refrence guides to match refernce on depercate-run upstream

* fix: restore mark syntax and rename reference files to .mdx

* fix: add pearpass.svg

* fix: center align showcase image

* fix: soften stable and deprecated badge colors for dark mode readability

* fix: align DEPRECATED marks with upstream, use plain bold syntax

* fix: update source map to reflect .mdx renames and remove guide directory

* fix: remove dead links from index.mdx

* fix: align system column icons horizontally

* fix: link to use url path approach

* fix: refine stability badge styles across reference docs

* fix: orama search

* fix: revert anchor syntax from [#id] to <a name> in mdx/md files

* fix: replace deprecated mark badges with **DEPRECATED** and fix anchor syntax across docs

* fix: update README

* fix: convert relative .md links to url paths and fix nested anchor hydration error

* feat: link checkers

---------

Co-authored-by: davidmarkclements <david.mark.clements@gmail.com>
Co-authored-by: Lucas Tortora <lucas.tortora@tether.io>

* feat(docs): information architecture reorg + cross-linking quality gates (#281)

* content: rename .md to .mdx and add Diátaxis docType / schemaType frontmatter

Fumadocs MDX needs the .mdx extension to compile JSX/MDX content; the .md
loader path doesn't run the MDX pipeline, which silently broke things like
JSX comments and component embeds.

While renaming, add the @tetherto/docs-seo-schema frontmatter so JSON-LD
@type values, the sitemap, and the Diátaxis classification land correctly
without warnings:

  - docType:    explanation | how-to | reference | faq
  - schemaType: WebPage | TechArticle | APIReference

The schemaType per page follows the docType -> @type mapping the schema
package documents (reference -> APIReference for upstream API surfaces,
how-to -> TechArticle for procedural docs, etc.). Four reference docs
that are actually procedural (deployment, migration, recommended-practices,
troubleshooting) were reclassified to docType: how-to.

deployment.mdx also gets its trailing HTML comment converted to a JSX
comment so MDX compilation succeeds.

Made-with: Cursor

* build: install @tetherto/docs-seo-* from GitHub Packages

The @tetherto/docs-seo-* SEO toolkit is published to GitHub Packages
under the npm.pkg.github.com registry rather than the public registry.
This adds the consumer-side wiring needed to install it cleanly.

  .npmrc          - point the @tetherto scope at npm.pkg.github.com and
                    read the auth token from \${GITHUB_TOKEN} at install
                    time (matches the convention already used across the
                    org, e.g. qvac's run-lint-and-unit-tests action).

  .env.example    - document GITHUB_TOKEN (required for npm install) and
                    every SEO env var the @tetherto/docs-seo-* packages
                    consume (NEXT_PUBLIC_DOCS_ORIGIN, SKIP_OG_BUILD,
                    DOCS_OG_*, DOCS_SEO_QUIET_GENERATED, DOCS_SEO_SILENT).

  README.md       - explain why npm install needs GITHUB_TOKEN exported
                    in the shell (npm reads .npmrc with process env, not
                    .env files) and link to docs-template's README for
                    the full PAT flow (scopes / expiration / SSO).

Made-with: Cursor

* feat(seo): wire @tetherto/docs-seo-* end-to-end

Replace the previous ad-hoc OG handler / hand-rolled metadata with the
@tetherto/docs-seo-* packages so the docs ship full structured SEO:
canonical URLs, Open Graph + Twitter cards, JSON-LD per page, sitemap
with <lastmod>, robots.txt with bot allow/deny lists, and pre-rendered
Takumi OG images for every MDX page.

Build / deps
  package.json         - add @tetherto/docs-seo-{schema,core,next,og},
                         @takumi-rs/image-response, zod, @fontsource/poppins,
                         tsx, serve. Add prebuild + build:og scripts that
                         run the Takumi prebuild before next build, and
                         keep tsx in NODE_OPTIONS for next build/dev so
                         source.config.ts can import @tetherto packages
                         shipped as raw .ts (Node's native type stripping
                         refuses to strip files under node_modules).
                         Switch to "type": "module".
  next.config.mjs      - transpilePackages for the four @tetherto/docs-seo-*
                         packages (Next has to compile their TS sources).

Schema / source
  source.config.ts     - extend Fumadocs frontmatterSchema with
                         tetherSeoFrontmatterSchema; install
                         fumadocs-mdx/plugins/last-modified with a custom
                         git resolver that --follows .md -> .mdx renames
                         and falls back to HEAD on shallow clones (Sevalla
                         and other managed CIs do shallow checkouts).
  src/lib/source.ts    - drop local getPageImage; OG image URLs now come
                         from @tetherto/docs-seo-og.

App routes / metadata
  src/lib/seo-config.ts                       - centralized DocsSeoConfig
                                                (origin, siteName, publisher,
                                                trailingSlash). Default
                                                origin is localhost:8080 to
                                                match the serve script.
  src/app/(docs)/[[...slug]]/page.tsx         - generateMetadata via
                                                buildDocsMetadata + JSON-LD
                                                via DocsJsonLd; OG image
                                                from getPageImage (or
                                                staticOgImagePath when
                                                SKIP_OG_BUILD=1).
  src/app/sitemap.ts                          - buildDocsSitemap (new).
  src/app/robots.ts                           - buildDocsRobots (new).
  src/app/layout.tsx                          - bump default origin to
                                                localhost:8080 to match
                                                seo-config.
  src/app/og/docs/[...slug]/route.tsx         - removed; replaced by the
                                                static prebuild below.

Takumi OG prebuild
  scripts/generate-takumi-og.tsx              - precomputeTakumiOgImages
                                                with a Pear-branded
                                                ImageResponse template
                                                (logo + title on one row,
                                                description below, brand
                                                colors and Poppins fonts
                                                matching the docs site).
                                                Loads pear-1.svg as a base64
                                                data URL and Poppins woffs
                                                from @fontsource so renders
                                                are deterministic (no
                                                network fetch at build).

Frontmatter
  content/index.mdx                           - docType: explanation,
                                                schemaType: WebPage
  content/reference/{api,cli,configuration}.mdx
                                              - docType: reference,
                                                schemaType: APIReference

Verified live (npm run build && npm run serve):
  - /sitemap.xml: 33 urls, all with <lastmod>
  - /robots.txt: well-formed, GPTBot / Google-Extended disallowed
  - JSON-LD on every page (Organization publisher + page schemaType)
  - 33 Takumi OG webp images at /og/docs/<slug>/image.webp
  - 0 [@tetherto/docs-seo] warnings under DOCS_SEO_QUIET_GENERATED=1

Made-with: Cursor

* chore: drop legacy Pear Book artifacts and untrack generated Fumadocs caches

  .gitignore                          - replace `.docusaurus` (legacy from
                                        the Docusaurus era) with the actual
                                        Fumadocs / Next caches: `.out`,
                                        `.source`, `.next`. Add
                                        `/public/og/docs/` so Takumi
                                        prebuild output stays out of git.
  .source/{browser,dynamic,server,
           source.config.mjs}         - untrack; fumadocs-mdx regenerates
                                        these on postinstall, so keeping
                                        them tracked just produced perpetual
                                        diff noise.
  .pear-book.json                     - remove; the Pear Book HTML doc
                                        builder is fully superseded by the
                                        Fumadocs site.
  .github/workflows/preview.yml       - remove; only consumed .pear-book.json
                                        / `pear-book build` and was already
                                        producing dead `preview` branches.
  next-env.d.ts                       - update routes types path
                                        (out/dev/types -> .next/types) to
                                        match Next 16's typegen output.

Made-with: Cursor

* chore(seo): address PR #280 review feedback

- Rename `scripts/generate-takumi-og.tsx` -> `scripts/generate-og.tsx` so the
  OG prebuild entrypoint is engine-agnostic; Takumi remains the current
  backend but the script / npm scripts no longer need to be renamed if it
  is swapped. Update `package.json` (`prebuild`, `build:og`) and the
  `.env.example` reference.

- `src/app/layout.tsx`: read `metadataBase` via `getDocsSeoConfig()`
  instead of duplicating the `NEXT_PUBLIC_DOCS_ORIGIN ?? NEXT_PUBLIC_SITE_URL
  ?? localhost:8080` fallback. Now consistent with `sitemap.ts`, `robots.ts`,
  and the docs page route, which all use the helper.

- `.env.example`: document `NEXT_PUBLIC_SITE_URL` as an accepted fallback
  alongside the preferred `NEXT_PUBLIC_DOCS_ORIGIN`.

Made-with: Cursor

* docs(adr): adopt Diátaxis IA — quadrant decisions + FAQ resolution

Records the design rationale for the IA reorganization landing in this
branch. The actual file moves, redirects, validators, and content
rewrites follow in subsequent commits, each mapped 1:1 to an Asana
subtask under https://app.asana.com/1/1204330682799323/task/1214311360861492.

Key decisions captured:

* Top-level structure collapses to the four canonical Diátaxis
  directories under content/: tutorials/, how-to/, reference/,
  explanation/. The current building-blocks/, helpers/, tools/ become
  reference/{building-blocks,helpers,tools}/. The four how-to-tagged
  pages physically misfiled in reference/ (deployment, troubleshooting,
  recommended-practices, migration) move out into how-to/.

* The FAQ is deconstructed, not relocated. Each Q&A is rewritten into
  the quadrant it actually belongs to; reference/faq.mdx is then
  deleted. This is the most editorially expensive option but the only
  one that fixes the underlying mixed-quadrant problem (Diátaxis treats
  FAQ as an anti-pattern).

* URL stability uses a belt-and-suspenders approach because
  next.config.mjs has output: 'export' (which means redirects() does
  not run at request time): static HTML redirect stubs at every old
  path AND a Sevalla-side _redirects file shipped in this PR. Stubs
  cover any host immediately; hosting-layer 308s take over once the
  next deploy lands. An E2E redirect test prevents regression.

* A new scripts/check-doctypes.ts enforces the dir <-> docType
  invariant in CI (e.g. content/how-to/** must have docType: how-to)
  so the IA can't drift back. Wires into the existing
  npm run check:internal-links flow.

* SUMMARY.md (root) is dropped in subtask 8 — GitBook-era artifact
  pointing at .md paths that no longer exist; Fumadocs ignores it.

Naming follows Diátaxis canon: directories are tutorials/ (plural),
how-to/, reference/, explanation/. The schema docType enum uses the
singular tutorial; the validator knows the dir<->docType mapping.

Out of scope deliberately: the optional Diátaxis quadrant badge in
page headers (defer to a follow-up; this PR is already large), and
URL changes for reference/{api,cli,configuration,runtime} (they stay
put).

Stored in decisions/ rather than content/ so the ADR doesn't get
published to the docs site.

Made-with: Cursor

* content: rename howto -> how-to and move misfiled how-tos out of reference

Subtask 2 of the Diátaxis IA reorganization (decisions/0001).

Renames the canonical how-to directory to its hyphenated form
(content/howto -> content/how-to) and moves the four reference docs
that were already tagged docType: how-to into it. The frontmatter is
unchanged; only the paths move. After this commit, every page tagged
docType: how-to lives under content/how-to/.

Moves (10 files):
  howto/connect-two-peers-by-key-with-hyperdht.mdx          -> how-to/...
  howto/connect-to-many-peers-by-topic-with-hyperswarm.mdx  -> how-to/...
  howto/replicate-and-persist-with-hypercore.mdx            -> how-to/...
  howto/work-with-many-hypercores-using-corestore.mdx       -> how-to/...
  howto/share-append-only-databases-with-hyperbee.mdx       -> how-to/...
  howto/create-a-full-peer-to-peer-filesystem-with-hyperdrive.mdx
                                                            -> how-to/...
  reference/deployment.mdx                                  -> how-to/...
  reference/troubleshooting.mdx                             -> how-to/...
  reference/recommended-practices.mdx                       -> how-to/...
  reference/migration.mdx                                   -> how-to/...

Internal-link rewrites in the same commit so the build stays green
and `npm run check:internal-links` passes between commits (no broken-
build window before redirects land in subtask 6):
  content/index.mdx                                  9 links updated
  content/how-to/connect-to-many-peers-...mdx        1 link
  content/how-to/replicate-and-persist-...mdx        2 links (in-quadrant)
  content/how-to/work-with-many-hypercores-...mdx    1 link
  content/reference/runtime.mdx                      1 link (-> /how-to/deployment)
  content/reference/api.mdx                          1 link (-> /how-to/migration)

Verified:
  - npm run check:internal-links: ✅ 33 files, all internal links valid
  - npm run build: ✅ static export clean, 0 warnings
  - sitemap.xml: 33 urls, 10 under /how-to/, 0 stale /reference/(deployment|
    troubleshooting|recommended-practices|migration) entries.
  - lastmod: falls back to HEAD commit time today; after this commit lands,
    fumadocs-mdx/plugins/last-modified --follow will resolve to each
    page's original commit time (the resolver in source.config.ts walks
    .md -> .mdx -> howto -> how-to renames).

Old paths are intentionally NOT yet redirected; that lands in subtask 6
(`feat(redirects): 308s for legacy paths via static stubs and hosting
rules`). External links will 404 in the interim, which is expected on a
local-only / preview-branch build.

src/lib/tree.ts still references /howto/ urls but is dead code (never
imported; sidebar comes from source.getPageTree()). It's deleted in
subtask 8 alongside the legacy SUMMARY.md.

Made-with: Cursor

* content: nest building-blocks/helpers/tools under reference/

Subtask 3 of the Diátaxis IA reorganization (decisions/0001).

Three top-level directories that were always reference content move
to where they belong:

  content/building-blocks/  ->  content/reference/building-blocks/   (6 files)
  content/helpers/          ->  content/reference/helpers/           (6 files)
  content/tools/            ->  content/reference/tools/             (5 files)

After this commit, the only top-level directories under content/ are
the four Diátaxis quadrants (`how-to/`, `reference/`) plus the upcoming
`tutorials/` and `explanation/` (added in subtask 4).

Pure path move — no internal-link rewrites needed inside content/. A
precise grep (\(/building-blocks/, \(building-blocks/, href="..." etc.)
returned zero matches: every reference to a building block / helper /
tool in pear-docs prose links to its external GitHub repo
(https://github.com/holepunchto/<name>), not the internal docs page.
The homepage's giant module tables (lines 190–250+) are entirely
external links.

Verified:
  - npm run check:internal-links: ✅ 33 files, all valid
  - npm run build: ✅ static export clean, 0 warnings
  - sitemap.xml: 33 urls
      0  stale top-level /(building-blocks|helpers|tools)/ paths
      17 nested /reference/(building-blocks|helpers|tools)/ paths
      16 unaffected (homepage, /how-to/*, /reference/{api,cli,configuration,runtime,faq})

src/lib/tree.ts still has stale /building-blocks/, /helpers/, /tools/
URLs but is dead code (never imported). Deleted in subtask 8.
Old paths are not yet redirected; static-export 308 redirects + Sevalla
rules land in subtask 6.

Made-with: Cursor

* content: add quadrant landing pages for how-to / reference / explanation

Subtask 4 of the Diátaxis IA reorganization (decisions/0001).

Adds an index.mdx for three of the four quadrants. Pages are
intentionally small ("draft, polish later" per the option C
checkpoint) — just enough to:

  1. Define the quadrant in Diátaxis terms.
  2. Cross-link to the other quadrants so readers self-route.
  3. List the pages currently in each quadrant.
  4. Leave a JSX-comment template hint for future authors.

Files added (3):
  content/how-to/index.mdx        Defines how-to vs tutorial; lists all
                                  10 current pages, grouped into
                                  "Building peer-to-peer apps" (the 6
                                  P2P recipes) and "Operating a Pear
                                  application" (the 4 docs that moved
                                  out of reference/ in subtask 2).
  content/reference/index.mdx     Defines reference; lists CLI / runtime
                                  / configuration / api, then inlines
                                  links to all 17 sub-pages under
                                  building-blocks/, helpers/, tools/.
                                  Notes that the FAQ is being
                                  deconstructed (subtask 9).
  content/explanation/index.mdx   "coming soon"; lists planned topics
                                  (What is Pear?, Peer-to-peer
                                  demystified, Append-only logs are a
                                  database, The runtime model, Bare vs
                                  Node). Notes that FAQ entries
                                  answering "what is X?" / "why X?"
                                  land here as part of the FAQ
                                  deconstruction.

Tutorials quadrant is intentionally NOT created in this commit. It's
been hidden until we have actual tutorial content. The cross-links
that would point to /tutorials in the three index pages above are
JSX-commented inline, with a re-enable note next to each:

  {/* Tutorials quadrant is hidden until we have actual tutorials.
      Re-add this leading clause when content lands: ... */}

When tutorials are written (Pear For Dummies / Pear Conventions / P2P
from Scratch — see the ADR), the quadrant comes back: create
content/tutorials/index.mdx and uncomment the three references. The
ADR's long-term plan is unchanged; this commit just defers the
quadrant landing.

Frontmatter for the 3 index pages:
  - description set on all (required by docs-seo schema; build is
    free of [@tetherto/docs-seo] warnings).
  - docType matches the directory: how-to / reference / explanation.
    The validator added in subtask 7 will enforce this.
  - schemaType: WebPage on how-to + explanation; APIReference on
    reference/ since it's the gateway to the API descriptions.

Two iterations during write-up:

1. An early draft of reference/index.mdx linked to
   /reference/building-blocks, /reference/helpers, /reference/tools as
   parent-pages, but those directories have no index.mdx. The link
   checker caught it; switched to inline listings only. Adding sub-
   directory landing pages is a one-liner if any of those subdirs grow
   large enough to warrant one (e.g. when each has 20+ entries).

2. The link checker (scripts/check-internal-links.ts via
   scripts/helpers.ts) text-scans for [text](/url) markdown patterns,
   so "commented-out" links inside {/* ... */} were still flagged as
   broken. Added a one-line strip of JSX comments before regex matching
   in extractLinks(). JSX comments don't render anyway, so links
   inside them shouldn't fail the check — this is a generally useful
   improvement for any "re-enable later" pattern in our docs.

Verified: - npm run check:internal-links: ✅ 36 files, all valid.
  - npm run build: ✅ static export clean, 0 warnings.
  - sitemap.xml: 36 urls (33 content + 3 new index pages, no /tutorials).
Made-with: Cursor

* content(index): rewrite homepage as four-quadrant launcher; move module tables to reference/

Subtask 5 of the Diátaxis IA reorganization (decisions/0001).

The old content/index.mdx (288 lines) was a hybrid: a short intro,
nine link bullets, and ~270 lines of giant module tables (Pear app /
UI / common / dev / integration libraries, P2P building blocks,
~50 bare-* modules, and CLI tools — roughly 100 external-GitHub-link
table rows in total). The result was a homepage where the catalog
swallowed the orientation; readers had to scroll through five tables
to find a Diátaxis-style entry point.

New homepage (87 lines, target was 150):

  Sections, in order:
    - Title + tagline + intro (intent unchanged from before).
    - Showcase (Keet, PearPass) — kept verbatim, useful.
    - "Where do you want to go?" — the four-quadrant launcher.
      Three cards visible (How-to / Reference / Explanation), each a
      heading + ~2 sentence quadrant definition + bold "Browse →"
      link to the quadrant's index page.
    - Stability legend — kept (used across reference pages).
    - Glossary — replaces the old "Terms" block; same content, name
      change for clarity.
    - Examples — kept (4 GitHub links to runnable examples).
    - Module catalog pointer — short paragraph that points readers at
      /reference/modules and /reference/bare-modules instead of inlining
      the tables here.

Tutorials quadrant card sits in a JSX comment (option C from the
earlier checkpoint). When the first tutorials land, uncomment 8 lines
and the four-card launcher restores. The card is fully populated
(definition, link to /tutorials, "learn by doing" subtitle) so the
re-enable is a no-edit copy-out-of-comment.

New file content/reference/modules.mdx (95 lines):
  - Pear application libraries (8 modules)
  - User interface libraries (2 modules)
  - Common libraries (10 modules)
  - Developer libraries (2 modules)
  - Integration libraries (15 modules)
  Plus a "Related" cross-link block at the bottom (bare-modules,
  building-blocks, helpers, tools).

New file content/reference/bare-modules.mdx (79 lines):
  Single big bare-* table (~50 entries) — too large for the homepage
  AND conceptually distinct from pear-* (Bare is a separate runtime).
  Lives on its own page per the option C scope decision; reachable
  from the reference index, the modules page, and the homepage's
  module-catalog pointer.

Cross-link from content/reference/index.mdx — added a "Module catalog"
sub-section pointing at the two new pages.

Cleanup applied to the extracted tables:
  - Stripped 7 stale <a name="..."></a> HTML anchors (GitBook-era
    in-page TOC links the old homepage relied on; Fumadocs auto-
    generates anchors from headings, so they were dead weight).
  - Promoted the bare-modules.mdx top heading from ### to # (it's a
    standalone page now, not a section of the homepage).
  - Renamed the modules.mdx top section from "### Pear Modules" to
    "# Modules" (page title comes from frontmatter; no "Pear" prefix
    needed since the URL says /reference/modules).
  - Demoted "#### Application Libraries" etc. to "## Application
    libraries" (one heading level less; page-title-cased).
  - Removed a duplicate intro paragraph that survived the extraction
    in bare-modules.mdx.

Tools section from the old homepage (lines 277-288) was NOT migrated.
The 5 tools (Hypershell, Hypertele, Hyperbeam, Hyperssh, Drives) all
have full reference pages under /reference/tools/, and the reference
index already lists them; the homepage table was redundant.

Frontmatter on both new pages:
  - description set (required by docs-seo schema; build runs clean).
  - docType: reference; schemaType: APIReference (these are catalogs
    of API-shaped artifacts, even if each row links externally).
  - The validator added in subtask 7 will accept both since they're
    under content/reference/.

Verified:
  - npm run check:internal-links: ✅ 38 files, all internal links valid.
  - npm run build: ✅ static export clean, 0 warnings.
  - sitemap.xml: 38 urls (was 36; +2 for /reference/modules and
    /reference/bare-modules). The homepage URL ('/') is unchanged.
  - Homepage: 87 lines, down from 288. No /tutorials links rendered.

Old paths NOT yet redirected:
  /#pear-modules, /#bare-modules, /#tools etc. (homepage in-page
  anchors). These were used by external links and the old TOC at the
  top of the homepage. Subtask 6 wires up redirects for legacy paths
  generally; in-page anchors on the same URL are a separate concern
  and probably best handled by adding a heading-level anchor on the
  homepage that mirrors the old #showcase / #legend names. Adding a
  TODO note for subtask 6.

Made-with: Cursor

* feat(redirects): 308s for legacy IA paths via static stubs and hosting rules

Subtask 6 of the Diátaxis IA reorganization (decisions/0001).

Reader bookmarks and external links to the old paths
  /howto/...                  /reference/{deployment,migration,...}
  /building-blocks/...        /helpers/...           /tools/...
need to keep working after the IA shape change. Pear-docs builds with
`output: 'export'` in next.config.mjs, which means Next.js's redirects()
config is silently ignored at request time (no Node runtime to honor it
on; it only fires during `next dev`). So we use a belt-and-suspenders
approach (option C in the decision checkpoint).

Architecture (scripts/redirects.ts is the single source of truth):

  1. Static HTML stubs at every legacy path. A postbuild step emits
     out/<from>/index.html with `<meta http-equiv="refresh">` +
     `<link rel="canonical">` to the new URL, plus a `<meta name=
     "robots" content="noindex">` so search engines don't index the
     duplicate. Browsers redirect immediately. Search engines treat
     sustained meta-refresh redirects as 301-equivalent. Works on any
     static host, zero deploy-side config.

  2. Hosting-layer 308 rules in out/_redirects. Sevalla / Netlify /
     Cloudflare-Pages compatible 3-column format
     ("<from> <to> <status>"), one line per redirect. Once the next
     deploy lands, the host returns true 308s and the HTML stubs become
     a defensive fallback rather than the primary mechanism.

  3. next.config.mjs redirects() listing the same patterns for `next
     dev` and as in-source documentation. `output: 'export'` ignores
     them at build time (this is fine; the postbuild stubs +
     _redirects file cover production).

The redirect list is DERIVED FROM THE FILESYSTEM rather than hardcoded.
buildRedirects() in scripts/redirects.ts walks content/how-to,
content/reference/{building-blocks,helpers,tools}, and explicitly
enumerates the 4 misfiled how-tos that moved out of content/reference/.
Net effect: when a new how-to lands at content/how-to/<slug>.mdx, a
/howto/<slug>/ -> /how-to/<slug>/ stub is emitted automatically next
build, no script edit needed. Same for building-blocks/helpers/tools.

Today the build emits 27 redirects:
  6  /howto/<slug>/                  -> /how-to/<slug>/
  4  /reference/<misfiled>/          -> /how-to/<misfiled>/
       (deployment, troubleshooting, recommended-practices, migration)
  6  /building-blocks/<slug>/        -> /reference/building-blocks/<slug>/
  6  /helpers/<slug>/                -> /reference/helpers/<slug>/
  5  /tools/<slug>/                  -> /reference/tools/<slug>/

E2E test (scripts/check-redirects.ts, run via `npm run check:redirects`).
For each redirect derived from buildRedirects(), asserts:

  1. out/<from>/index.html exists.
  2. The HTML contains `meta http-equiv="refresh" content="0; url=<to>"`.
  3. The HTML contains `link rel="canonical" href="<to>"`.
  4. out/_redirects contains the line `<from> <to> 308`.

Failure mode: if the postbuild step regresses or if scripts/redirects.ts
silently produces fewer entries than expected, this checker flags it.
Designed to slot into CI alongside check:internal-links so the IA
can't drift back.

URLs always carry a trailing slash to match next.config.mjs's
`trailingSlash: true` (which is what next-export emits in out/).

Files:
  scripts/redirects.ts                  - shared map builder + stub HTML
  scripts/generate-redirect-stubs.ts    - postbuild emitter
  scripts/check-redirects.ts            - CI-friendly E2E verifier
  next.config.mjs                       - +redirects(), +intent comment
  package.json                          - +postbuild, +check:redirects

Verified:
  - npm run build: ✅ static export clean; postbuild emits "Wrote 27
    redirect stubs and out/_redirects (27 rules)".
  - npm run check:redirects: ✅ 27 redirects, 54 assertions per row + 1
    _redirects file presence check; all pass.
  - npm run check:internal-links: ✅ 38 files, all internal links valid
    (the redirect stubs are in out/, not content/, so the checker
    correctly ignores them — internal links should always point at the
    NEW paths).
  - Sample stub /howto/connect-two-peers-by-key-with-hyperdht/index.html
    inspected: meta-refresh, canonical link, noindex, trailing slash on
    target URL — all correct.

Out of scope here:
  - Homepage in-page anchors (#pear-modules, #bare-modules, etc.) that
    moved to /reference/modules and /reference/bare-modules in subtask
    5. Those are URL-fragment changes on the same path, which neither
    HTML stubs nor _redirects can target. If we want to honor them,
    add heading IDs on the homepage that mirror the old anchor names.
    Punted to subtask 8 / a follow-up.
  - Bare modules table inside the old homepage was duplicated by
    /reference/bare-modules already; that's covered by the homepage
    rewrite, not by a redirect.

Made-with: Cursor

* content: hide Explanations quadrant until conceptual content lands

The Explanations quadrant has no pages yet — the index was a "Coming
soon" stub listing planned topics (What is Pear?, P2P demystified,
Append-only logs as a database, etc.). Per the user's call, hide the
quadrant entirely so it doesn't show in the sidebar, sitemap, or any
cross-references until real content exists.

What's hidden:
- content/explanation/index.mdx removed; the directory is gone.
- content/index.mdx homepage card for Explanations is wrapped in a JSX
  comment, mirroring the existing Tutorials hide. The replacement copy
  uses the plural "Explanations" so the form is right whenever the
  quadrant returns.
- content/how-to/index.mdx: the trailing "For the conceptual 'why,'
  see [Explanation]" clause moves into a JSX comment with restoration
  text. The leading Tutorials clause was already hidden.
- content/reference/index.mdx: the trailing "For the design rationale,
  see [Explanation]" clause same treatment. Also tightens the FAQ
  deconstruction note so it doesn't promise links to a quadrant that
  doesn't exist yet.

Why JSX comments instead of deletion: every hidden block carries the
exact prose to restore when the quadrant ships, so re-enabling is a
mechanical "uncomment and remove the wrapper" — no archeology.

Verification:
- sitemap.xml: 37 URLs (down from 38), zero /explanation/ entries.
- check:internal-links: clean.
- check:redirects: clean.

See decisions/0001-adopt-diataxis-ia.md §4 ("Quadrant content scope")
for the rule: a quadrant is only visible when it has at least one
substantive page beyond the index.

Made-with: Cursor

* content: rename "How-to guides" -> "How To", drop redundant prefix from titles, regroup by topic

Three threads in one commit because they're a single coherent IA
naming pass:

1) Section label rename: "How-to guides" -> "How To".
   Shorter, scans cleaner in nav, and matches the user's preferred
   form. Touched in: content/index.mdx (homepage card heading + browse
   link), content/how-to/index.mdx (frontmatter title), and
   content/reference/index.mdx (cross-reference). The body intro on
   how-to/index.mdx becomes "How-tos are goal-oriented recipes" — the
   "how-to" lowercase descriptor stays everywhere it isn't acting as a
   section name.

2) Page titles become imperative-only.
   Diátaxis style for how-tos is "do X", not "how to do X" — the
   "how-to" framing is the section, not the title. So:
     "How to connect two Peers by key with Hyperdht"
       -> "Connect two peers by key with HyperDHT"
     "How to connect to many peers by topic with Hyperswarm"
       -> "Connect to many peers by topic with Hyperswarm"
     "How to replicate and persist with Hypercore"
       -> "Replicate and persist with Hypercore"
     "How to work with many Hypercores using Corestore"
       -> "Work with many Hypercores using Corestore"
     "How to share Append-Only Databases with Hyperbee"
       -> "Share append-only databases with Hyperbee"
     "How to create a full peer-to-peer filesystem with Hyperdrive"
       -> "Create a full peer-to-peer filesystem with Hyperdrive"
   Also fixes module-name capitalization that was inconsistent
   ("Hyperdht" -> "HyperDHT", "Append-Only Databases" -> "append-only
   databases", etc.).

   The four operational how-tos had bare-noun titles which read like
   reference pages, not actions. Per the user's call (asym_recommen-
   dations option):
     "Deployment"           -> "Deploy your application"
     "Migration"            -> "Migrate to a new release"
     "Recommended Practices"-> "Apply recommended practices"
     "Troubleshooting"      -> "Troubleshoot common issues"

3) Listing on /how-to/ regrouped by topic, not by audience.
   The previous split was "Building peer-to-peer apps" vs "Operating
   a Pear application", which matched what the pages cover but
   collapsed all six core-primitive how-tos into one bucket. The new
   grouping (by_module option) makes the table of contents teach the
   shape of the building blocks too:
     - Connecting peers      (HyperDHT, Hyperswarm)
     - Storage and replication (Hypercore, Corestore, Hyperbee, Hyperdrive)
     - Operating an app      (deployment, migration, recommended-
                              practices, troubleshooting)
   The listing still uses flat /how-to/<slug>/ URLs in this commit;
   the topic prefix becomes part of the URL in the next commit.

What's intentionally NOT in this commit:
- File moves into topic subdirs and the redirect rewire — that's a
  cleanly separable change with its own test surface, so it lives in
  the next commit.
- Any prose changes inside the 10 how-to bodies — frontmatter only.

Verification:
- check:internal-links: clean (37 files).
- build: clean, 37 sitemap URLs, no warnings.
- check:redirects: clean (27 redirects, destinations unchanged).

See decisions/0001-adopt-diataxis-ia.md §4 ("Quadrant content scope")
and §7 ("Page-title style: imperative for how-tos") for the rules.

Made-with: Cursor

* content: nest how-to pages under topic folders, rewire redirects single-hop

The /how-to/ quadrant grew flat: 10 sibling pages spanning two
unrelated areas (peer connection, storage replication) plus four
operations recipes. With the regroup landing in the previous commit
("Connecting peers" / "Storage and replication" / "Operating an app"),
the URL ought to mirror the sidebar grouping. So the pages move into
topic subdirs and the URLs deepen by one level.

What moved:
  content/how-to/connect-two-peers-by-key-with-hyperdht.mdx
    -> content/how-to/connect-to-peers/connect-two-peers-by-key-with-hyperdht.mdx
  content/how-to/connect-to-many-peers-by-topic-with-hyperswarm.mdx
    -> content/how-to/connect-to-peers/connect-to-many-peers-by-topic-with-hyperswarm.mdx
  content/how-to/replicate-and-persist-with-hypercore.mdx
    -> content/how-to/store-and-replicate/replicate-and-persist-with-hypercore.mdx
  content/how-to/work-with-many-hypercores-using-corestore.mdx
    -> content/how-to/store-and-replicate/work-with-many-hypercores-using-corestore.mdx
  content/how-to/share-append-only-databases-with-hyperbee.mdx
    -> content/how-to/store-and-replicate/share-append-only-databases-with-hyperbee.mdx
  content/how-to/create-a-full-peer-to-peer-filesystem-with-hyperdrive.mdx
    -> content/how-to/store-and-replicate/create-a-full-peer-to-peer-filesystem-with-hyperdrive.mdx
  content/how-to/deployment.mdx
    -> content/how-to/operate-an-app/deployment.mdx
  content/how-to/migration.mdx
    -> content/how-to/operate-an-app/migration.mdx
  content/how-to/recommended-practices.mdx
    -> content/how-to/operate-an-app/recommended-practices.mdx
  content/how-to/troubleshooting.mdx
    -> content/how-to/operate-an-app/troubleshooting.mdx

All moves are git-tracked renames (status R / RM), so blame follows.

Folder naming: imperative-verb-phrase to match the page titles' style
("connect-to-peers/" mirrors "Connect two peers by key with HyperDHT").
The nouns-form ("connecting-peers/", "operating-an-app/") was rejected
to keep the convention parallel.

Internal cross-links rewired (18 in 6 files):
  content/how-to/index.mdx                         (10 listing URLs)
  content/how-to/store-and-replicate/replicate-and-persist-with-hypercore.mdx (3)
  content/how-to/store-and-replicate/work-with-many-hypercores-using-corestore.mdx (1)
  content/how-to/connect-to-peers/connect-to-many-peers-by-topic-with-hyperswarm.mdx (1)
  content/reference/api.mdx                        (2 — migration#compat-mode)
  content/reference/runtime.mdx                    (1 — deployment)

Redirect strategy: single hop to the final nested destination.
Everyone with a legacy bookmark (/howto/<slug>/ or /reference/<misfiled>/)
should land at /how-to/<topic>/<slug>/ in one redirect, not two.
The "remap_legacy" option from the consult, picked over "add inter-
mediate /how-to/<slug>/ stubs too" — this branch hasn't shipped, so
no external links to the unmerged flat /how-to/<slug>/ URLs exist.

scripts/redirects.ts now walks content/how-to/<topic>/<slug>.mdx (via
buildHowToTopics) and looks the topic up per-slug at build time, so
new how-tos automatically get a redirect for free as long as a legacy
URL is meaningful for them. The MISFILED_HOWTOS set still exists
because /howto/deployment/ never existed (legacy was /reference/deploy-
ment/), so we need to know which prefix to emit.

next.config.mjs's redirects() can't use a single-wildcard rule any
more (the destination's topic prefix depends on the slug), so it
expands to ten explicit per-slug rules. Only matters in `next dev`;
production redirects come from the static stubs and _redirects file.

Verification:
- check:internal-links: 37 files, all clean.
- build: clean export, 37 sitemap URLs, 10 of them under
  /how-to/<topic>/<slug>/.
- check:redirects: 27 redirects, 54 assertions per row, all single-hop
  to the new deeply-nested destinations:
    /howto/connect-two-peers-by-key-with-hyperdht/
      -> /how-to/connect-to-peers/connect-two-peers-by-key-with-hyperdht/  (308)
    /reference/deployment/
      -> /how-to/operate-an-app/deployment/  (308)

See decisions/0001-adopt-diataxis-ia.md §6 (redirects under static
export) and §7 (page-title style: imperative for how-tos) for context.

Made-with: Cursor

* feat(check-doctypes): enforce dir <-> docType invariants

Adds scripts/check-doctypes.ts and an `npm run check:doctypes` script
that walks content/**/*.mdx and asserts each page's frontmatter
docType matches the Diátaxis quadrant it lives in. The map is the
ADR's §8 invariant:

  content/index.mdx      -> docType: explanation  (site-root special case)
  content/tutorials/**   -> docType: tutorial
  content/how-to/**      -> docType: how-to
  content/reference/**   -> docType: reference
  content/explanation/** -> docType: explanation

What this prevents: another /reference/deployment.mdx-style misfile
slipping in. The four pages that drove this rule (deployment,
migration, recommended-practices, troubleshooting) lived under
/reference/ but were tagged docType: how-to — exactly the kind of
drift CI now catches automatically.

Why a custom script vs. a content-collection plugin: pear-docs uses
fumadocs-mdx with @tetherto/docs-seo-schema's frontmatter validator,
which already validates the docType *enum*. What it can't validate is
"docType matches directory", which is a project-level rule, not a
schema rule. ~80 lines of TS is cheaper than wiring a Fumadocs plugin
for a one-off invariant.

Implementation:
- expectedDocType(file) computes the required docType from the path.
- readDocType(file) extracts the value with a forgiving regex that
  accepts the three YAML forms in the wild (bare word, single-quoted,
  double-quoted) — same set fumadocs-mdx accepts.
- TEMPORARY_EXEMPTIONS allows content/reference/faq.mdx to keep its
  docType: faq for now. Subtask 9 will deconstruct the FAQ and delete
  this entry, after which the set is empty and any future drift fails
  CI.
- Files outside the four quadrants and not the root index are reported
  as "unscoped" (warning only, no failure) — this is so a refactor
  branch with a scratch file in content/ doesn't break CI.

Verification:
  npm run check:doctypes
  -> "Checked 37 files (1 exempted, 0 unscoped) ✅"

Negative test (covered in code review, not committed):
  Flip docType: how-to -> docType: explanation in
  content/how-to/operate-an-app/deployment.mdx -> validator fails with
  exit 1 and prints expected vs actual.

Wired as a sibling of check:internal-links / check:external-links /
check:redirects, runnable via `npm run check:doctypes`. Subtask 10
(final QA) wires it into the same CI step as the other checks.

See decisions/0001-adopt-diataxis-ia.md §8.

Made-with: Cursor

* chore: delete legacy SUMMARY.md and src/lib/tree.ts; refresh README

Two artifacts from the GitBook era + pre-Fumadocs sidebar both pointed
at filesystem layouts that no longer exist. They were dead weight and
actively misleading to anyone grepping for the canonical site structure.

Deletions
---------

SUMMARY.md (root, 73 lines) — GitBook table of contents listing every
page by its old `.md` path under `howto/`, `building-blocks/`,
`helpers/`, `tools/`. Fumadocs never read it (it derives the sidebar
from content/ filesystem + meta.json), so it had been an outdated
duplicate since the migration. Every path in it is now a redirect.

src/lib/tree.ts (262 lines) — hardcoded sidebar tree mirroring
SUMMARY.md, with module URLs like `/howto/...` and `/building-blocks/...`
plus references to pages that don't exist (`/guide/getting-started`,
`/reference/bare-overview`, `/reference/templates`,
`/reference/node-compat`). A grep for any module importing it returned
zero matches — confirmed dead code.

Both deletions are referenced in decisions/0001-adopt-diataxis-ia.md §9
("Cleanup of legacy artifacts").

README.md
---------

The README hadn't picked up the four IA-era scripts. Added:

- `npm run check:redirects` — verify redirect stubs + _redirects rules.
- `npm run check:doctypes`  — enforce dir<->docType invariants
                              (added in the previous commit).
- `npm run build:og`        — regenerate Open Graph images standalone.
- `npm run serve`           — clarified, was hand-rolled `npx serve out`.

Repository layout section refreshed to reflect today's tree:
- `decisions/` (ADRs) added.
- `out/` (build output) added with the "generated; not checked in" note.
- `scripts/` description expanded beyond "link checking".
- `content/` annotated with the Diátaxis-quadrant convention.

A short "Architectural decisions" pointer to the ADR folder so future
contributors can find the IA rationale without spelunking commit logs.

Out of scope (deliberately): sitemap labels, search index, takumi-og
paths. ADR §subtask-8 listed those, but per-file inspection confirmed
they're all path-dynamic — the sitemap is generated from the
content/ tree at build time, the Fumadocs search index is populated
the same way, and scripts/generate-takumi-og.tsx points at
`path.join(root, 'content')` and walks. None of them needed editing.

Verification
------------

  npm run check:doctypes        Checked 37 files (1 exempted) ✅
  npm run check:internal-links  Checked 37 files                 ✅
  npm run build                 clean static export, 37 sitemap URLs
  npm run check:redirects       27 redirects, 54 assertions      ✅

No `tree.ts` import anywhere (verified with rg before delete). No
SUMMARY.md reference outside the ADR (which mentions the deletion).

See decisions/0001-adopt-diataxis-ia.md §9.

Made-with: Cursor

* chore(faq): deconstruct /reference/faq into how-to / explanation per ADR

The FAQ was the last single page mixing all four Diátaxis quadrants —
the anti-pattern called out in decisions/0001-adopt-diataxis-ia.md §5
and in [Diátaxis itself](https://diataxis.fr/how-to-use-diataxis/#what-about-faqs).
Deconstruct it: each Q&A is rewritten and routed into the quadrant it
actually belongs to, the FAQ page is deleted, /reference/faq/ redirects
to the new explanation index, and the dir<->docType validator's
temporary exemption for the FAQ is removed.

Q&A -> destination map (per the consult)
----------------------------------------

How-to-shaped (2 new pages):
- "How do I get a list of installed applications?" (Q1)   ┐
- "How do I uninstall a Pear application?" (Q2)           ┘
    -> content/how-to/operate-an-app/manage-installed-applications.mdx

- "How do I distribute a binary version?" (Q8)
    -> content/how-to/operate-an-app/distribute-as-binary.mdx

Explanation-shaped (3 new thematic pages):
- "Can Pear be used with X language?" (Q4)                ┐
- "How do I write an app once that runs everywhere?" (Q5) ┘
    -> content/explanation/runtime-and-languages.mdx

- "Where is the Pear application stored?" (Q3)            ┐
- "How is my application distributed? Do I have to keep   │
   `pear seed` running?" (Q6)                             ┘
    -> content/explanation/storage-and-distribution.mdx

- "Why is NPM used for dependencies?" (Q7)                ┐
- "Can peers know my IP address?" (Q9)                    ┘
    -> content/explanation/dependencies-and-network.mdx

Bundling rationale: each page anchors a coherent theme and runs 30-50
lines, which is meatier than the FAQ entries it replaces. One-per-Q
would produce six 5-line stubs that read like FAQ in disguise; one
mega-page would lose the Diátaxis "one concept per page" guideline.
The thematic split survives review.

Pages aren't 1:1 transcriptions — each got rewritten in the voice of
its destination quadrant: how-tos became imperative recipes with code
blocks and "see also" cross-links to the CLI reference; explanations
became discursive prose with rationale, trade-offs, and explicit
"what they can / can't do" framing on the IP-exposure section.

Quadrant unhide
---------------

The Explanations quadrant was hidden in 6765e92 because it had no
content; it now has three pages, so this commit unhides it:
- content/explanation/index.mdx is recreated (was deleted in 6765e92)
  with: a Diátaxis-shaped intro, an "In this section" listing of the
  three new pages, a "More to come" preview, and a "Coming from the
  old FAQ?" lookup table mapping each old anchor to its new home.
- content/index.mdx: Explanations homepage card uncommented.
- content/how-to/index.mdx: trailing "For the conceptual 'why,' see
  Explanations" cross-reference uncommented.
- content/reference/index.mdx: trailing "For the design rationale,
  see Explanations" cross-reference uncommented; the now-stale FAQ
  section that pointed at the deleted page is removed.

The Tutorials quadrant remains hidden — that's still a real "no
content" situation (the in-flight Pear For Dummies / Pear Conventions /
P2P from Scratch tasks haven't landed yet).

Redirect: /reference/faq -> /explanation
----------------------------------------

Static HTML stubs can't dispatch on URL fragment, so per-anchor
redirects (e.g. /reference/faq#how-do-i-uninstall -> the new how-to)
aren't possible without JavaScript. Compromise: a single page-level
redirect to /explanation/, which is where most Q&As landed. The
explanation index then carries an anchor-by-anchor lookup table so
anyone arriving from a deep FAQ bookmark sees a clear pointer to the
new home of their specific question.

Both the postbuild stub generator and next.config.mjs gain the
/reference/faq -> /explanation rule. scripts/redirects.ts emits the
stub at out/reference/faq/index.html and the _redirects entry; the
new how-tos in operate-an-app/ inherit the standard /howto/<slug>/
auto-emission (two extra stubs that no one will ever hit because
those slugs never lived under /howto/, but at ~200 bytes each they're
not worth special-casing — left for subtask 10 if it bothers anyone).

Validator cleanup
-----------------

scripts/check-doctypes.ts: TEMPORARY_EXEMPTIONS goes from {faq.mdx}
to empty. Any future dir<->docType drift now fails CI immediately —
no more grandfathered files.

Verification
------------

  npm run check:doctypes          Checked 42 files (0 exempted) ✅
  npm run check:internal-links    Checked 42 files                 ✅
  npm run build                   clean, 42 sitemap URLs (was 37)
  npm run check:redirects         30 redirects, 60 assertions       ✅

Sitemap delta: -1 (faq.mdx) +6 (5 new pages + recreated explanation
index) = +5. Redirects delta: +1 (/reference/faq) +2 (auto-emitted
/howto/ stubs for the new how-tos). Stub at out/reference/faq/
index.html confirmed: meta-refresh + canonical to /explanation/.

See decisions/0001-adopt-diataxis-ia.md §5 (FAQ resolution) and §8
(dir<->docType invariant).

Made-with: Cursor

* content: align user-facing copy with PR #273 (`pear run` deprecation)

User-facing pages on this branch lead with the Pear product, not the
docs framework, and the launch flow points at `pear-runtime` / `bare`
rather than the deprecated `pear run`.

Indexes (Pear-first framing, articles linked, planned content commented)
- content/index.mdx
  - Drop the explicit Diátaxis framework framing from "Where do you want
    to go?" -> "Find what you need". Keep the four-section IA itself.
  - Inline-link the How To / Reference / Explanations descriptions to the
    articles that exist; drop unbuilt teasers (peer-to-peer in practice,
    append-only logs, the runtime model) so links don't promise content
    that isn't there.
  - Replace "Examples" block (4 mature example repos that depended on
    `pear run`) with "Boilerplates" pointing at hello-pear-electron and
    hello-pear-react-native, matching upstream PR #273.
- content/explanation/index.mdx
  - Drop the "Pages in this quadrant" / "Diátaxis FAQ recommendation"
    framing (still uses the framework, just without name-dropping it).
  - Wrap the four planned conceptual overviews in a JSX comment so the
    intent stays in source for contributors but doesn't render as a list
    of nonexistent links.

`pear run` -> `bare <app-dir>` sweep across the 5 how-tos that PR #273
hadn't gotten to yet (the corestore one was already updated upstream).
Mirrors PR #273's pattern exactly — only the launch command changes,
example bodies are left alone for a follow-up sweep.
- content/how-to/connect-to-peers/connect-two-peers-by-key-with-hyperdht.mdx
- content/how-to/connect-to-peers/connect-to-many-peers-by-topic-with-hyperswarm.mdx
- content/how-to/store-and-replicate/replicate-and-persist-with-hypercore.mdx
- content/how-to/store-and-replicate/share-append-only-databases-with-hyperbee.mdx
- content/how-to/store-and-replicate/create-a-full-peer-to-peer-filesystem-with-hyperdrive.mdx

Reference / how-to copy
- content/reference/index.mdx: drop `pear run` from the CLI bullet
  (cli.md itself stamps it deprecated); promote `pear release` instead.
- content/how-to/operate-an-app/distribute-as-binary.mdx: lead paragraph
  no longer says users `pear run pear://<your-link>`; they "open
  `pear://<your-link>` to launch", consistent with the rest of the page.

Verified: `npm run check:internal-links` passes (42 files, 0 broken),
no new lint errors.

Made-with: Cursor

* feat(sidebar): unify page tree into single src/lib/custom-tree.ts

Replace per-directory meta.json scattered across content/** with a single
hand-built Node[] fed directly to <DocsLayout tree={...}>. Matches the
pattern used in cosmic-ac-docs and gives one place to control ordering,
labels, and hierarchy. The 10 stale meta.json files were never committed
so no deletions land in this commit.

Made-with: Cursor

* docs: crosslink across quadrants, validate # fragments in link checker

Crosslinking pass (how-to / reference / explanation):
- Add inline links and `## See also` sections across content/ so readers
  can move between related how-to, reference, and explanation pages
  without bouncing through the sidebar.
- Split each multi-app how-to into named sections (`## Create the X app`,
  `## Run the writer and reader`, `## Inspect the …`) and link the
  intro's app names to those sections.
- Replace vague "in the former example" phrasings with titled internal
  links and convert "Related material" paragraphs to bulleted
  `## See also` sections in explanation pages.

Link checker:
- scripts/helpers.ts: extractLinks now returns InternalLink[] with the
  fragment split out from the path. New extractAnchors / buildAnchorMap
  build a per-page anchor set from heading slugs (via github-slugger,
  matching Fumadocs' rehype-slug), explicit <a name="…"> markers, and
  id="…" attributes; frontmatter and fenced code blocks are stripped
  first so they can't contribute false anchors.
- scripts/check-internal-links.ts: validates fragments against the
  target file's anchor set (anchor-only links resolve against the
  current file); reports broken fragments separately from broken pages
  and missing assets.

Fragment fixes the new check surfaced:
- content/reference/api.mdx: /#pear-modules → /reference/modules;
  #pear--config-checkpoint-any → #pear-app-checkpoint; drop the dead
  #compat-mode fragment on the migration link.
- content/how-to/connect-to-peers/connect-to-many-peers-by-topic-with-hyperswarm.mdx:
  #create-the-peer-app → #create-the-peer-app-project to match the
  heading slug.

Made-with: Cursor

* docs: see-also blocks; cross-link coverage + orphan checker

Adds `## See also` to the six pages still missing one:
deployment, recommended-practices, troubleshooting, reference/runtime,
reference/configuration, reference/api.

scripts/check-cross-links.ts:
- Coverage report — for 26 canonical terms (auto-derived from
  /reference/{building-blocks,helpers,tools}/* + curated CLI/runtime/api
  set), counts linked vs unlinked prose mentions across content. Ignores
  frontmatter, code fences/spans, and headings; case-sensitive to avoid
  "drives" the verb colliding with /reference/tools/drives.
- Orphan inventory — counts inbound MDX-to-MDX links per page.
  Quadrant landings exempt. Hard-fails on 0-inbound; --strict also
  fails on coverage <50% for >=3-mention terms and on 1-inbound pages.

Wires `npm run check:cross-links` into package.json.

Made-with: Cursor

* chore: ESLint flat config + content fix-ups from final QA

ESLint:
- Add eslint.config.mjs targeting the flat-config format ESLint 9
  requires. Extends `eslint-config-next` (which ships native flat config
  in v16+, including next/typescript and react/jsx-a11y/import plugins).
- Project-specific ignores: .source/ (Fumadocs MDX cache), public/og/
  (Takumi-generated), out/ (static export), pearbook/ (legacy scaffold).
- scripts/ override silences React/browser rules on Node CLIs and
  disables @next/next/no-img-element for the Takumi OG generator.
- src/mdx-components.tsx: pin the eslint-disable to the intentional
  native <img>; the surrounding component documents why.

Content:
- content/index.mdx: replace the broken hello-pear-react-native
  boilerplate link (404) with hello-pear-electron and drop the Mobile
  bullet — there's no current canonical mobile starter, and the
  Electron repo is the end-to-end Pear Hello World per its README.

Lint, internal links, external links all green.

Made-with: Cursor

* docs(readme): list check:cross-links alongside the other doc checks

Made-with: Cursor

* use admonitions

* cleanup

* docs: add cross-links and apply docs-review fixes

- Link first-mention Hypercore/Hyperdrive/Hyperbee/Corestore in explanation pages.
- Replace GitHub corestore/hyperdrive links with internal /reference targets in
  storage-and-distribution.mdx; lifts Hypercore mention coverage from 31% -> 100%.
- deployment.mdx: tighten description, add audience/outcome opener.
- configuration.mdx: rewrite ### Deprecations heading + intro, link pear-runtime
  internally, split 168-word pear.pre paragraph into four, expand LLM on first
  use, fix let's -> lets and conjuction -> conjunction.
- create-a-full-peer-to-peer-filesystem-with-hyperdrive.mdx: imperative voice in
  the lead-in paragraph.
- README: document --strict mode for check:cross-links in CI.

Made-with: Cursor

* feat(seo): integrate @tetherto/docs-seo-* end-to-end (metadata, sitemap, robots, JSON-LD, OG) (#280)

* content: rename .md to .mdx and add Diátaxis docType / schemaType frontmatter

Fumadocs MDX needs the .mdx extension to compile JSX/MDX content; the .md
loader path doesn't run the MDX pipeline, which silently broke things like
JSX comments and component embeds.

While renaming, add the @tetherto/docs-seo-schema frontmatter so JSON-LD
@type values, the sitemap, and the Diátaxis classification land correctly
without warnings:

  - docType:    explanation | how-to | reference | faq
  - schemaType: WebPage | TechArticle | APIReference

The schemaType per page follows the docType -> @type mapping the schema
package documents (reference -> APIReference for upstream API surfaces,
how-to -> TechArticle for procedural docs, etc.). Four reference docs
that are actually procedural (deployment, migration, recommended-practices,
troubleshooting) were reclassified to docType: how-to.

deployment.mdx also gets its trailing HTML comment converted to a JSX
comment so MDX compilation succeeds.

Made-with: Cursor

* build: install @tetherto/docs-seo-* from GitHub Packages

The @tetherto/docs-seo-* SEO toolkit is published to GitHub Packages
under the npm.pkg.github.com registry rather than the public registry.
This adds the consumer-side wiring needed to install it cleanly.

  .npmrc          - point the @tetherto scope at npm.pkg.github.com and
                    read the auth token from \${GITHUB_TOKEN} at install
                    time (matches the convention already used across the
                    org, e.g. qvac's run-lint-and-unit-tests action).

  .env.example    - document GITHUB_TOKEN (required for npm install) and
                    every SEO env var the @tetherto/docs-seo-* packages
                    consume (NEXT_PUBLIC_DOCS_ORIGIN, SKIP_OG_BUILD,
                    DOCS_OG_*, DOCS_SEO_QUIET_GENERATED, DOCS_SEO_SILENT).

  README.md       - explain why npm install needs GITHUB_TOKEN exported
                    in the shell (npm reads .npmrc with process env, not
                    .env files) and link to docs-template's README for
                    the full PAT flow (scopes / expiration / SSO).

Made-with: Cursor

* feat(seo): wire @tetherto/docs-seo-* end-to-end

Replace the previous ad-hoc OG handler / hand-rolled metadata with the
@tetherto/docs-seo-* packages so the docs ship full structured SEO:
canonical URLs, Open Graph + Twitter cards, JSON-LD per page, sitemap
with <lastmod>, robots.txt with bot allow/deny lists, and pre-rendered
Takumi OG images for every MDX page.

Build / deps
  package.json         - add @tetherto/docs-seo-{schema,core,next,og},
                         @takumi-rs/image-response, zod, @fontsource/poppins,
                         tsx, serve. Add prebuild + build:og scripts that
                         run the Takumi prebuild before next build, and
                         keep tsx in NODE_OPTIONS for next build/dev so
                         source.config.ts can import @tetherto packages
                         shipped as raw .ts (Node's native type stripping
                         refuses to strip files under node_modules).
                         Switch to "type": "module".
  next.config.mjs      - transpilePackages for the four @tetherto/docs-seo-*
                         packages (Next has to compile their TS sources).

Schema / source
  source.config.ts     - extend Fumadocs frontmatterSchema with
                         tetherSeoFrontmatterSchema; install
                         fumadocs-mdx/plugins/last-modified with a custom
                         git resolver that --follows .md -> .mdx renames
                         and falls back to HEAD on shallow clones (Sevalla
                         and other managed CIs do shallow checkouts).
  src/lib/source.ts    - drop local getPageImage; OG image URLs now come
                         from @tetherto/docs-seo-og.

App routes / metadata
  src/lib/seo-config.ts                       - centralized DocsSeoConfig
                                                (origin, siteName, publisher,
                                                trailingSlash). Default
                                                origin is localhost:8080 to
                                                match the serve script.
  src/app/(docs)/[[...slug]]/page.tsx         - generateMetadata via
                                                buildDocsMetadata + JSON-LD
                                                via DocsJsonLd; OG image
                                                from getPageImage (or
                                                staticOgImagePath when
                                                SKIP_OG_BUILD=1).
  src/app/sitemap.ts                          - buildDocsSitemap (new).
  src/app/robots.ts                           - buildDocsRobots (new).
  src/app/layout.tsx                          - bump default origin to
                                                localhost:8080 to match
                                                seo-config.
  src/app/og/docs/[...slug]/route.tsx         - removed; replaced by the
                                                static prebuild below.

Takumi OG prebuild
  scripts/generate-takumi-og.tsx              - precomputeTakumiOgImages
                                                with a Pear-branded
                                                ImageResponse template
                                                (logo + title on one row,
                                                description below, brand
                                                colors and Poppins fonts
                                                matching the docs site).
                                                Loads pear-1.svg as a base64
                                                data URL and Poppins woffs
                                                from @fontsource so renders
                                                are deterministic (no
                                                network fetch at build).

Frontmatter
  content/index.mdx                           - docType: explanation,
                                                schemaType: WebPage
  content/reference/{api,cli,configuration}.mdx
                                              - docType: reference,
      …
Resolve modify/delete conflicts in favor of published by keeping legacy
guide files removed; content lives under the new content/ IA.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lucas-tortora
lucas-tortora requested review from a team June 19, 2026 18:15
@kinsta

kinsta Bot commented Jun 19, 2026 •

Copy link
Copy Markdown

Preview deployments for pear-docs preview ⚡️

Status Branch preview Commit preview
✅ Ready Visit preview Visit preview

Commit: efe249acde65d02dbfbba12ae2c161ded2defe70

Deployment ID: 9d05a5e9-e69d-4219-933b-8d4b54676fc8

Static site name: pear-docs-preview-iimt7

* fix: add catch-all 404 rule to _redirects

Add public/_redirects with a Sevalla-compatible catch-all that serves
/404.html with HTTP 404, and merge those static rules into out/_redirects
during postbuild so they survive the generated IA redirect output.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: adopt qvac _redirects strategy for 404s and markdown negotiation

Mirror qvac docs: public/_redirects is the authored source (Accept:
text/markdown rules plus catch-all 404), copied into out/ by next build.
Postbuild prepends generated IA 308 redirects ahead of that file so the
catch-all stays last.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Ports the QVAC docs Keet modal into pear-docs. A Keet icon link in the sidebar footer opens a modal with two steps: download the Keet app, and join the Pear Development Group room via QR code or copy-link.

- src/components/keet-modal.tsx: theme-aware, portal-mounted modal that intercepts clicks on the Keet nav icon
- src/components/keet-icon.tsx: navbar Keet glyph
- wires the icon link + modal mount into the docs layout
- adds qrcode.react and next-themes deps

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…g URLs (#309)

Covers three eras of missing redirects identified via git history and
Google Search Console:

- /reference/{cli,configuration,runtime}/ → /reference/pear/* (pear/ subfolder pass)
- /reference/{bare-modules,modules,desktop-release-npm-scripts}/ → new paths
- /reference/{node-compat,bare-overview,templates}/ → best-effort gitbook-era destinations
- /getting-started/ flat pages → build-a-peer-to-peer-chat/ and from-a-template/ subdirs
- /how-to/operate-an-app/ flat pages → build-and-package/ and manual-deployment/ subdirs
- /guide/* (10 gitbook-era pages) → nearest current equivalents
- /pear-runtime/*, /guides/best-practices/, /examples/react-app-using-pear/,
  /reference/api/ → stale Google-indexed paths from pre-IA structure

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…cs (#310)

Readers cloning examples need the published branch, not main.

Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: add Bare reference, explanations, and how-tos; integrate into IA

Documents Bare as a first-class part of the Pear stack:

- Reference: Bare runtime API, the `bare` CLI, Bare Kit (native
  embedding), and a 36-page `bare-*` module catalog.
- Explanation: the Pears stack, inside the Bare runtime, one core /
  many platforms, and using Bare standalone.
- How-to: run-on-native guides (bundle a Bare app, embed in React
  Native, type a native RPC bridge).
- Integration: landing page, explanation/reference indexes, the
  `bare-modules` catalog, and the sidebar tree now surface Bare; Vale
  config + Prose/Headings style aligned for the new terms.

Collapsed onto published: the Diátaxis IA reorganization this branch
originally carried is already upstream via #305, so only the net Bare
contribution lands here. Published's keet modal (#308), redirects
(#307/#309), and getting-started clone fix (#310) are preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: retire legacy Pearbook pipeline

Removes the committed pearbook/ gitbook-era HTML (122 files) and both
workflows of the old book pipeline: Preview (`pear run --output pearbook`
→ preview branch) and Publish (promote preview → published). The content/
Diátaxis IA supersedes pearbook, the Next.js app never built or served it,
and legacy URLs are covered by the redirects in scripts/redirects.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: gitignore vale-synced styles and Obsidian config

`.github/styles/Google/` is fetched by `vale sync` (re-fetched in CI; only
.github/styles/Prose/ is committed) and `content/.obsidian/` is local editor
state — neither should be committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bare): fix Vale prose lint errors

Satisfies the Google style rules enforced in CI (docs-lint):
- Google.Latin: "e.g." → "for example," (12 occurrences across
  bare-addon-resolve, bare-channel, bare-mdns-discovery, bare-pipe,
  bare-semver).
- Google.EmDash: remove spaces around the em-dash in
  bare-mdns-discovery ("Android — without" → "Android—without").

`vale --minAlertLevel=error content` now reports 0 errors in 126 files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(bare): correct API mismatches in bare reference docs

- cli: remove --inspect-port flag that does not exist in bare v1.28.0
- bare-apk: document options for createAPKSet and createAPK
- bare-rpc: fix RPCIncomingRequest.reply() signature (add encoding param),
  expand property list (data, sent, received)
- bare-form-data: move isFormData and toBlob to module-level exports
- bare-semver: remove caret/tilde/X-range/hyphen range syntax from
  Range.parse — parser only supports comparison operators and ||

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(links): remove non-existent pear-base action, update moved-repo URLs

- Drop `holepunchto/actions/pear-base` table row and references — this
  action never existed in the repo; replace workflow step with
  `actions/checkout@v4` + `make-pear-app` to match the actual
  hello-pear-electron template
- Update `mafintosh/dht-rpc` → `holepunchto/dht-rpc` (repo moved)
- Update `prdn/hyper-cmd-utils` → `holepunchto/hyper-cmd-utils` (repo moved)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(bare): correct API mismatches found in doc-vs-source review

- cli: add missing --inspect-port flag
- bare-stream: clarify WHATWG streams require 'bare-stream/web' import path
- bare-ipc: remove incorrect structured-clone description; it's a raw duplex pipe
- bare-semver: document full range syntax (^, ~, X-ranges, hyphen, wildcard)
- bare-sdl: distinguish Rect (integer, Texture.update) from Rect.F (float, Renderer.texture)
- runtime: add 'wakeup' as a valid context for calling Bare.resume()
- explanation/bare-runtime: add Awake state to lifecycle diagram; fix simplified prose
- bare-broadcast-channel: note MAX_PORTS lifetime cap on channel.connect()
- bare-make: add missing color option and --no-color CLI flag to generate
- bare-sidecar: add missing 'close' event
- bare-union-bundle: document skipModules in b.add(); document cache/skipModules in b.load()
- explanation/bare-on-native: soften Swift/Kotlin native binding claims to language interop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(prose): replace e.g. with 'for example' to pass Vale lint

bare-semver.mdx had two Google.Latin violations (lines 145, 151)
that were failing the vale prose lint CI job.

Also applies doc-vs-source verification fixes from the scheduled
review pass:
- bare-sdl: remove non-existent Rect/Rect.F classes; fix renderer/texture signatures
- bare-addon-resolve: drop unexported resolve.preresolved; add resolve.constants
- bare-sqlite: rename Statement → StatementSync throughout
- bare-bluetooth-apple: reword Constants as static properties, not a named export
- bare-union-bundle: fix skipModules ([]→true) and cache (true→require.cache) defaults
- build-desktop-distributables: pear.stage.includes → pear.stage.include (singular)
- migration.mdx: global.Pear.run() → Pear.worker.run(); updating/updated events on pear.updater
- start-from-hello-pear-bare: bin.js → bin.mjs (upstream entrypoint renamed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: David Mark Clements <david.mark.clements@gmail.com>

* docs(bare): document QuickJS engine support

Bare's libjs ABI now has an ABI-compatible QuickJS backend
(holepunchto/libqjs), alongside the existing V8 default and
JavaScriptCore (libjsc). Add it to the engine list in bare-runtime.mdx:
"What Bare adds", "Engine independence", and the engine FAQ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(vale): allow spaced em-dashes (disable Google.EmDash)

Adopts the spaced em-dash style applied in code review (d39166c).
Google.EmDash flags any space around a dash, which conflicted with the
reviewer's " — " usage in bare-runtime.mdx; disable it repo-wide so the
house style and docs-lint agree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bare): add bare-timers reference and wire up app-suspension docs (#312)

* docs(bare): add bare-timers reference and wire up app-suspension docs

Add a reference page for Bare's global timer functions (setTimeout,
setInterval, setImmediate, and their clear* counterparts), verified
against the upstream bare-timers source: object timer handles,
ref()/unref()/hasRef()/refresh(), the Symbol.dispose behaviour, the
promise-based API, and the Bare-specific delay clamping and lifecycle
integration that the suspension model relies on.

Register it in the sidebar and link it from the Bare modules catalog,
then connect it to the app-suspension how-to in both directions so the
"Clear timers" section links to real anchors instead of plain code
spans. Also wires the suspension how-to into the Inside Bare and
Embed Bare in React Native pages and the navigation tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* add react native bare kit note

* docs(bare): suspend/resume Hyperswarm instead of destroying it

Per review on #312: Hyperswarm exposes suspend()/resume(), so the
app-suspension how-to no longer needs to destroy the swarm and rebuild
it on every cycle. Call swarm.suspend() on the Bare `suspend` event and
swarm.resume() on `resume`, keeping the swarm instance and its joined
topics alive across suspension.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bare): drop await/.flushed() from swarm.join in suspension example

Per review on #312: awaiting .flushed() here nudges readers toward an
unnecessary pattern. A plain swarm.join(topic, ...) is the idiomatic
fire-and-forget; the example doesn't need to block on the first announce.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bare): simplify to swarm.join(topic) in suspension example

Per review on #312: { server: true, client: true } is the default for
swarm.join(), so drop it and let the example read swarm.join(topic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: David Mark Clements <david.mark.clements@gmail.com>
Add the HTML verification token at public/googleb79b36e088621ed1.html so
it deploys to the site root (/googleb79b36e088621ed1.html). public/ is
copied verbatim into the out/ static export, and static-file resolution
runs ahead of the _redirects catch-all, so it serves with 200.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(bare): add Node.js builtin-to-bare module mapping table

Improve the Node.js compatibility story across the three Bare pages that
previously left it scattered:

- bare-modules.mdx: add a "Node.js builtin → Bare module" mapping table
  (fs→bare-fs, crypto→bare-crypto, child_process→bare-subprocess, …),
  split the shims into their own subsection, and note the WHATWG modules
  (bare-url/fetch/encoding/console) that live in functional sections.
- bare-runtime.mdx: point the npm-modules FAQ at bare-compat-napi for
  Node-API addons and link to the new mapping section.
- runtime-and-languages.mdx: link the "coming from Node.js" note to the
  new mapping table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* format fixes

* fix formatting

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add Status component for API stability badges

Replaces inline <mark style={{...}}> badges with a reusable <Status level="..." />
component registered in getMDXComponents, keeping the MDX authoring surface clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: replace inline mark badges with <Status> component

Swaps all <mark style={{...}}> stability badges across reference and index
pages to use the new <Status level="..." /> component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(refgen): JSDoc-first reference generation pipeline

Generate curated API reference pages from upstream source: AST + JSDoc
(types, @param/@returns/@typedef/@type) merged into a structured model and
rendered to curated MDX via per-repo editorial manifests. Adds the JSDoc gap
report + coverage dashboard (refs:jsdoc), the README->JSDoc seeder
(refs:seed-jsdoc), the local-iteration regenerator (regen-local), a
cross-module type registry, and the agent runbook/handoff, JSDoc convention,
and shareable eslint-jsdoc config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(refgen): generated models, curated previews, and JSDoc gap reports

Per-repo api-model.json, curated-preview.mdx, jsdoc-gaps.md and
improvement-plan.md for all 12 modules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(refgen): curated-manifest gate, JSDoc dashboard, and weekly regeneration

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(refgen): JSDoc-first reference generation pipeline

Introduces a JSDoc-extraction pipeline that generates rich API reference
pages for all 12 Holepunch modules directly from JSDoc in their source.

Pipeline overview:
- scripts/refgen/extract-ast.ts — extracts JSDoc from upstream source into
  a structured api-model.json per module (descriptions, typed params,
  returns, throws, typedefs with defaults)
- scripts/refgen/emit-jsdoc.ts — reconstructs per-repo JSDoc diffs in
  ../jsdoc-upstream/<slug> from the committed models (powers upstream PRs)
- scripts/refgen/prose.ts — shared description cleaning (destrand,
  cleanParamDesc) used by both renderer and emitter so they stay in sync
- scripts/refgen/layouts/<slug>.ts — per-module manifests controlling
  member order, description/throws overrides, keepCurated flag
- scripts/gen-curated.ts — renders api-model.json → curated-preview.mdx
  and content/reference/* pages; honours keepCurated so the hyperswarm
  page stays hand-curated
- .github/workflows/regenerate-references.yml — weekly regen watcher
  (cache-gated on upstream stable tags; auto-triggers commented out until
  JSDoc ships in upstream releases)

Content changes:
- All 12 reference pages now render typed params, returns, throws, typedef
  option tables, and a generated ## Errors section
- generated/refs/<slug>/api-model.json — committed models (source of truth)
- generated/refs/<slug>/curated-preview.mdx — rendered previews

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: align docs with Pear v3 — CLI updates, Pear OTA rename, deprecations

Align the CLI reference with the v3 command set on holepunchto/pear main:
- pear run, release, presets, shift, drop marked removed
  ("deprecated in v2.6.5, removed as of v3") with migration pointers
- pear install promoted to a real command (pear-install); pear build
  re-sourced to pear-build; source links repointed to main until v3 tags
- flag/subcommand drift fixed against cmd/*.js (no more --no-ask/--compact/
  --mem; data=dht+multisig; gc=sidecars+cores; sidecar adds shutdown/inspect;
  multisig adds keys paths and --peer-update-timeout; provision takes
  verlinks; info takes [dir])

Rebrand the embeddable OTA library as "Pear OTA" (branding only — the
package remains pear-runtime everywhere upstream, so code identifiers,
npm/GitHub links, class names, and the page slug are unchanged).

Rewrite pages that instructed removed commands: manage-installed-applications
now covers OS app-folder installs, pear info, and pear gc cores; the release
pointer flows through pear provision + pear multisig quorum cosigning.

Verify every API claim against source: pear-runtime v1.3.1 (deep links
re-pinned from v1.1.4), pear-runtime-updater options/events, and the
hello-pear-electron template; add a verified Options section to the OTA page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: add docs changelog with upstream-release watcher

Add a top-level /changelog — a single rolling, hand-curated record of
breaking changes across Pear and its modules, seeded with the Pear v3
entry (CLI removals, pear install, global Pear API removal, Pear OTA).

Wire-up: nav entry in custom-tree.ts, homepage and reference-index
sections, changelog→page in the check-doctypes quadrant map, and
inbound links so the page passes the orphan audit.

Automation: scripts/check-upstream-releases.ts (stdlib-only, so CI can
run it with just tsx) polls the repos in scripts/upstream-releases.json,
falls back to version tags for repos that don't publish GitHub releases
(pear, pear-electron, pear-build), and inserts TODO(curate)-marked draft
entries under the changelog:insert marker, advancing
upstream-releases-state.json. The upstream-releases workflow runs it
daily and force-pushes one rolling review PR (changelog/upstream-drafts)
with a curation checklist; baselines only land on published when the
curated PR merges.

Verified: doctypes/internal-links/cross-links/Vale pass; /changelog and
sidebar render; watcher dry-run (rewound bare tag) inserts correctly
formatted drafts and re-advances state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: harden release watcher and document store submissions

Watcher hardening:
- Resume the open changelog/upstream-drafts branch instead of
  force-pushing from published — scheduled reruns now stack new drafts
  on top of human curation commits instead of clobbering them; the PR
  body lists every draft on the branch, not just the latest run's
- Tripwire: when a pear (CLI) v3 tag is detected, the drafts PR body
  flags re-pinning the CLI reference source links from main to the tag
- Widen the watch list from 7 to 19 repos: add the building blocks and
  helpers with reference pages (hypercore, hyperbee, hyperdrive,
  autobase, hyperswarm, hyperdht, corestore, localdrive, mirror-drive,
  hyperswarm-secret-stream, compact-encoding, protomux), slugs verified
  and baselined

Store submissions:
- New how-to: Submit to app stores (Flathub + Snap), adapted from the
  hello-pear-electron README store-submissions section — manifest prep,
  local testing, review submission, and release automation for both
- Deployment guide gains step 8 "Store submissions (optional)"; wired
  into the sidebar, how-to index, and build-desktop-distributables

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(v3): rename Changelog→Release Overview; finalize removed CLI/API status

- Rename the changelog page title and nav label to "Release Overview"
  (route stays /changelog; automation marker and `pear changelog` CLI
  command untouched); update the links that point to it.
- Document `pear install` as an available v3 command (no status badge).
- Mark the ambient `global.Pear` API surface as REMOVED (removed in v3);
  the still-current `global.Bare` API is unchanged.
- Mark the `pear-api` module as removed and relabel the reference index
  entry "Pear API (removed)".
- Assorted in-progress v3 doc updates (store submissions, migration,
  distribution, upstream-release watcher tweaks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(v3): move changelog to /release-overview; correct config field statuses

- Rename content/changelog/ -> content/release-overview/ and move the route
  /changelog -> /release-overview (new route, no redirect needed); update the
  inbound links, nav, and the upstream-release automation path (script +
  workflow).
- configuration.mdx: verified each deprecated field against pear-state and the
  pear repo (v3). Still-present fields (name, routes, unrouted, links, assets,
  gui) stay DEPRECATED; fields no longer read by pear v3 are marked REMOVED:
  pear.pre, pear.userAgent, pear.stage.prefetch, and the (previously
  documented-as-current) pear.stage.include and pear.stage.defer, which went
  away with the pear stage --compact/warmup static-analysis phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(config): document pear.stage.only and pear.stage.purge

Both are read by `pear stage` in v3 (subsystems/sidecar/ops/stage.js) but were
undocumented: `pear.stage.only` restricts staging to given path prefixes
(merged with `--only`); `pear.stage.purge` removes now-ignored entries from a
prior stage (config equivalent of `--purge`). Cross-linked from the removed
pear.stage.include note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(refs): regenerate reference models for 5 modules to latest releases

Bring the JSDoc-first generated API models up to date with upstream:
compact-encoding v3.2.0→v3.3.0, corestore v7.10.1→v7.11.0,
hypercore v11.33.2→v11.34.0, hyperdht v6.32.0→v6.33.0,
hyperdrive v13.3.2→v13.3.3. Re-extracted api-model.json + reference.mdx +
improvement-plan.md per module, bumped scripts/refgen/.cache.json, and synced
the GitHub source links on the matching content/reference pages to the new
tags. The other 7 modules were already current and were skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(hyperdht): document defaultKeyPair, findPeer, suspend, resume

Add the genuinely-public methods flagged by the refgen completeness pass but
missing from the reference page, sourced from holepunchto/hyperdht@v6.33.0:
node.defaultKeyPair, node.findPeer(), and node.suspend()/node.resume() (the
device-sleep / network-change lifecycle pair). Internal symbols surfaced by the
scorer (onrequest, pool, plugins, register, rawStreams, createRawStream, …) are
intentionally left out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(refs): document real public gaps in hyperdrive, hypercore, corestore

Add genuinely-public methods flagged by the refgen completeness pass but missing
from the reference pages, each verified against upstream source:
- hyperdrive@v13.3.3: Hyperdrive.getDriveKey(), drive.blobs, drive.monitor()
- hypercore@v11.34.0: core.byteLength, core.manifest, core.purge(), the
  'migrate' event (skipped core.contiguousByteLength — it's a stub returning 0)
- corestore@v7.11.0: store.opened / store.closed

Internal symbols, EventEmitter passthroughs, and entries already documented on
the pages (findingPeers, audit, getBlobs, …) were left as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(compact-encoding): add complete bundled-codec reference

The page detailed a representative codec per family but omitted the size and
variant codecs. Add a grouped "Complete codec reference" catalog covering every
bundled encoder in compact-encoding v3.3.0 — the full uint/int families
(uint8…uint64be, int8…int64), bigints, float32/64, buffers (incl. optionalBuffer),
all typed arrays, every string encoding (ascii/hex/base64/utf16le with .fixed(n)),
fixed buffers, structured/special values, network addresses (ipv4/ipv6 + Address
variants), and the cenc.raw.* non-prefixed variants. Descriptions taken from the
upstream README; each links back to the encoder contract and composition helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(cli): add canonical "Install & upgrade" section (npx pear, v2→v3)

`npx pear` / `npm i -g pear` were only mentioned in passing across getting-started
and how-to pages, and there was no documented path for moving the CLI itself from
v2 to v3 (the migration page only covers the pear run → Pear OTA code migration).

Add an "Install & upgrade" section at the top of the CLI reference: `npx pear`
bootstraps the platform (per the upstream README) and prints PATH setup, with a
"Moving from Pear v2 to v3" note explaining the v3 CLI self-updates over the air
via Pear OTA. Cross-linked from the migration how-to so v2 users find it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: fix Vale Google.Latin lint — replace "e.g." with "for example"

Two occurrences introduced in the recent reference additions
(hyperdrive drive.monitor param, compact-encoding strings note).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix heading

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
lucas-tortora and others added 2 commits September 24, 2026 09:03
* docs: add Integrate Pear OTA into an existing app guides

* docs: document Pear Mobile OTA deployment and architecture

Add the mobile release flow (build the OTA payload, release lines,
store submissions, troubleshooting) that previously punted entirely to
an external hello-pear-react-native README section, plus an
explanation page giving mobile's dual-runtime architecture the same
treatment the desktop Electron architecture page already has.

Sourced from the hello-pear-react-native README and package.json
scripts, not the pears.com blog post that prompted this, since the
post oversimplifies details like the dist/ deployment-directory shape
and the Metro-vs-Bare-bundle distinction.

Also extends Submit to app stores with iOS/Android sections, and
fixes a pre-existing bug inherited from docs/integrate-pear-ota-existing-app
(#376): electron.mdx and mobile.mdx's <include> snippet paths were one
directory level short, silently breaking `next build` since
check:internal-links doesn't validate <include> resolution.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: fill remaining mobile-parity gaps across the how-to tree

Audited every page under content/pear/how-to/ and content/pear/explanation/
against the mobile deployment/architecture docs added in the prior commit.
Most desktop how-to content (multisig, changelogs, CI staging) turned out
to already be platform-neutral or already covered; these are the real
gaps, each verified against a primary source before writing:

- submit-to-app-stores.mdx: document the eas-build-post-install hook
  (verified in the pear-mobile README) — without it, an EAS checkout
  silently ships an app with no worklet, since the worker bundle is
  gitignored.
- publish-with-github-actions.mdx: pear-ci's staging mechanics are
  platform-neutral (it stages whatever's in `target`); note it works
  for a mobile dist/ payload too, and flag the CI worker-bundle gap.
- build-desktop-distributables.mdx, distribute-as-binary.mdx: point to
  the actual mobile equivalents instead of leaving a dead end or no
  pointer at all.
- storage-and-distribution.mdx: the dev/prod storage table and the
  --storage multi-instance flow are desktop-only; mobile's storage is
  fixed to the OS sandbox and has no multi-instance equivalent.
- publish-a-changelog.mdx: the "CHANGELOG.md isn't copied into the
  deployment directory" warning applies to mobile's dist/ output too,
  not just desktop's by-arch/ output.
- how-to/index.mdx: the quick-reference list was missing "Deploy your
  mobile application" from the prior commit.

Deliberately NOT added: a mobile CI/CD signing-and-release how-to page
paralleling build-and-sign-in-ci.mdx. Confirmed hello-pear-react-native
ships no EAS/Fastlane config to document from — writing one would mean
inventing unverified content, not documenting what exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: resolve rc/prerelease OTA contradiction in mobile release-lines table

The release-lines table listed rc's OTA source as "stage link" while the
prose directly below it said rc's upgrade points at the production
multisig link with no OTAs — contradicting itself and the desktop model
it claims to mirror (deployment.mdx's rc chain and Release pipeline's
"rc's upgrade pins the production multisig key"). The prose also
wrongly lumped prerelease into "no OTAs", when prerelease's provision
link is a real OTA source on desktop too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(pear): fix review findings on mobile deploy/architecture docs

Adversarial review of this PR turned up 13 issues, all fixed here:

- Release-line table had rc/prerelease OTA behavior backwards versus
  the upstream hello-pear-react-native README this PR claims to verify
  against (rc's OTA source is "stage link", not "multisig"; prerelease
  gets no OTA, same as rc).
- The new mobile architecture page's "no separate host process" claim
  contradicted workers.mdx and runtime-and-languages.mdx, which still
  described mobile as having an Electron-main-analogous host. Updated
  both to match.
- submit-to-app-stores.mdx's new iOS/Android sections reused H3 heading
  text already used by the Flathub/Snap sections, colliding anchor ids
  (the exact class of bug this repo's history shows was just cleaned up
  elsewhere). Disambiguated all 8 headings.
- scripts/check-includes.ts already exists and would have caught this
  PR's own <include>-path bug, but was never wired into CI. Added an
  `includes` job to docs-lint.yml.
- The new mobile `pear build` example used all-relative paths with no
  "run outside the application folder" caveat, reproducing the exact
  nested-target anti-pattern desktop's docs warn against. Added the
  caveat.
- Android's "Prepare the submission" section omitted the `--clean`
  regeneration caveat iOS has, though mobile-boot-control.mdx says it
  applies to both platforms' generated files.
- Two anchor links promised content ("different payload shape", "the
  platform table") that lived in a different section than the one
  linked. Retargeted/reworded both.
- The page intro still claimed "two Linux store flows" after this PR
  added two more (iOS/Android).
- The new architecture page's "Related documentation" callout fully
  duplicated its own "Where to go next" list; removed it and folded
  the one non-duplicate link in.
- Node/Xcode/iOS/Android version floors were restated with a citation
  chain that didn't terminate at the actual source; pointed it there
  directly.
- Noted the dev-vs-release distinction between the two mobile guides'
  differently-scoped `bare-pack --host` invocations.
- Simplified mobile.mdx's step-number-to-name mapping to match the
  simpler phrasing its own sibling callout in deployment.mdx already
  uses.

Verified after: check:internal-links, check:cross-links,
check:includes, check:doctypes, check:examples:lint, and types:check
all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Adds GTM (GTM-M78ZHFTG) via the official @next/third-parties
GoogleTagManager component, plus the <noscript> iframe fallback
right after <body>.

Raw inline <script> children don't execute under this Next/React
version's client rendering (RSC re-render treats inline script text
as inert) — the official component avoids that by using
dangerouslySetInnerHTML / src-scripts internally.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ages (#391)

* docs(release-notes): split P2P content out of Bare and Pear release pages

The Bare release-overview page documented only Hypercore, Hyperdrive,
HyperDHT, and Corestore changes — all P2P-product packages, none of them
Bare. The Pear release-overview page mixed the same P2P packages plus
bare-* deps into its Pear 3.3.0 section. Neither page had any content
that actually belonged to its own product.

- Add content/p2p/release-overview/index.mdx (nav entry already existed
  in src/lib/p2p-tree.ts) with the relocated P2P package history.
- Strip the P2P-only section from content/bare/release-overview/index.mdx,
  leaving a placeholder pending the ongoing Bare/P2P/Pear release-notes
  backfill.
- Remove the P2P and bare-* entries from the Pear 3.3.0 section of
  content/pear/release-overview/index.mdx, keeping only Pear's own
  changes, and fix a cross-link that pointed to /bare/release-overview
  instead of /p2p/release-overview.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(pear): add Pear 3.5.0 to the release overview

Pear 3.5.0 shipped 2026-09-22 with a new `pear identity` command and
a `pear seed` stats rework (Verlink replaces Drive Key, Blobs Length
added). Sourced from the pear repo's CHANGELOG.md at v3.5.0, since
this release has no GitHub Release entry yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat(docs): page-feedback signal and last-updated badge

Surfaces two pieces of data the site already computes but never
rendered.

- Last-updated badge: renders fumadocs' own PageLastUpdate in the page
  footer using page.data.lastModified, which the lastModified plugin
  (source.config.ts) already computes per page from git history.
- Reader feedback: new PageFeedback component (thumbs up/down, "Was
  this helpful?"). Fires a GTM dataLayer event (doc_feedback) rather
  than standing up a new backend, since this is a static export with no
  API routes - GTM is already wired in (src/app/layout.tsx).
  localStorage remembers this browser's vote per page so it doesn't
  re-ask.

Split out of #392, which bundled this with the accordion work, to
isolate a Kinsta preview-deploy failure on that PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(docs): use @next/third-parties' sendGTMEvent instead of a conflicting global

feedback.tsx redeclared `Window.dataLayer` with a different element
type (`Record<string, unknown>[]`) than the one @next/third-parties
already declares globally (`Object[]`, via layout.tsx's
GoogleTagManager import) - TypeScript rejects incompatible merged
global interface declarations. This was a real 'next build' type
error, not a Kinsta infra issue: reproduced locally with the full
'npm run build' lifecycle (prebuild+build+postbuild), which is what
caught it - a bare 'next build' run earlier had a stale incremental
type-check cache that masked it.

Fix: use the package's own sendGTMEvent(data, dataLayerName?) helper
(next/third-parties/dist/google/gtm.js) instead of touching
window.dataLayer directly - no global redeclaration needed, and it's
the sanctioned API for this exact case.

Verified: eslint clean; full 'npm run build' (prebuild -> build ->
postbuild) completes with no errors, 211 pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(ci): route upstream release drafts to per-product changelogs

The release-notes split (#391) moved the changelog to
content/{pear,bare,p2p}/release-overview/index.mdx, but the upstream
release watcher still wrote to content/release-overview/index.mdx and
crashed with ENOENT after advancing nothing.

- Each watched repo now declares a `section` (pear, bare, p2p) and its
  drafts land under that product's `changelog:insert` marker. Markers
  are added to the Bare and P2P release pages.
- The checker validates sections and markers up front, before any
  network call or write.
- The workflow diffs, stages and summarizes all three changelogs, and
  the pear v3 tripwire points at content/pear/reference/pear/cli.mdx.
- regenerate-references.yml stages content/*/reference instead of the
  removed content/reference, which would have failed `git add`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): look back 100 releases for the upstream baseline

The checker fetched only the latest 10 releases per repo. Bare's
baseline (v1.30.3) had already fallen off that page, so the next run
would have drafted the newest 10 and silently dropped v1.31.0 and
v1.31.1. Fetch GitHub's per-page maximum instead, and warn (capping the
draft at 10) only when a full page still misses the baseline. A short
page is the whole history, so a missing baseline there just predates it
and everything listed is new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lucas-tortora and others added 4 commits October 1, 2026 10:40
…les.yml (#401)

PR #372 registered both scenarios in scripts/test-examples.ts's manifest
but never touched .github/workflows/examples.yml — its own commit
message lists what it touched and this workflow isn't among them. Since
then both scenarios have silently never run in CI, exactly the failure
mode the workflow's header comment warns a forgotten scenario causes.

Verified both pass locally (npm run test:examples -- --filter=<id>)
before adding them: same real-swarm/DHT networking pattern as
hyperdht-chat and hyperswarm-chat, which already run in this same
matrix, so no new CI capability is needed.

Also corrected the header's scenario count (9 -> 12, matching
scripts/test-examples.ts's actual SCENARIOS array) and the per-job
comment's "remaining six" -> "eight" now that these two are included.
The "16 terminal Bare apps" figure was already accurate and untouched.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… sidebar search (#387)

* feat(docs): add an MCP install button under the sidebar search

Adds an "Add to your AI tool" button that hands a reader the docs MCP
server in whatever form their client wants: a copyable `claude mcp add`
command for Claude Code, a copyable mcpServers JSON block for Claude
Desktop, one-click `cursor://` and `vscode:mcp/install` deeplinks, and
raw URL / JSON fallbacks for everything else (Windsurf, Zed, Cline).

Rendered through DocsLayout's `sidebar.banner`, which Fumadocs places
immediately after the sidebar's LargeSearchToggle — search first, then
"take these docs with you". The popover follows the existing
ai/page-actions.tsx conventions (fumadocs Popover, buttonVariants,
lucide icons, a copy-state tick that resets after 2s).

The endpoint comes from NEXT_PUBLIC_MCP_URL. When it is unset the button
does not render at all, so a deploy that has not been pointed at a
server never ships a dead install link.

Cursor's deeplink wants the inner server config as base64 and VS Code
wants it as URL-encoded JSON, so both hrefs are built at click time
rather than at render time — `btoa` does not exist during Next's server
prerender of a client component.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(docs): publish the MCP retrieval corpus with the site

Adds a postbuild step that writes out/mcp/corpus.json (every page's
markdown plus heading-anchored chunks) and out/mcp/manifest.json (counts,
timestamps and corpusHash). Both ship with the ordinary static deploy of
`published`, so the docs site becomes the source of truth for what the
search service indexes.

Until now the search service kept its own vendored copy of this
extraction logic, cloned this repo to run it, and committed the ~12MB
result into its own git history. Only this repo knows how a content file
becomes a URL — getFiles, fileToSlug and stripInlineMarkdown all live in
scripts/helpers.ts — so the copy had to be kept byte-compatible by hand,
and a drift would have pointed every citation at a URL that does not
exist. The generator now imports those directly; verified against the
service's extractor over the same tree, 207 pages and 4146 chunks come
out field-for-field identical.

Deliberately produces no vectors: embedding needs the QVAC native addon
and runs 10-40 minutes on CPU, far too heavy for a site build. corpusHash
is what makes that cheap — it is computed exactly the way the service's
index builder computes it, so the service re-embeds only when content
actually moved, and polls a few hundred bytes to find out.

stripInlineMarkdown is exported for this, github-slugger is declared now
that it is load-bearing for a published artifact rather than only for
checkers, and docs-lint gains a job so a hard failure — two content files
slugging to one URL — surfaces on the PR instead of taking down a deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ci: ping the index builder when published moves

Adds a workflow that fires a repository_dispatch at the search service as
soon as `published` moves, so a docs change is searchable in minutes
rather than waiting out that repo's 30-minute poll.

The ping carries the corpus hash it expects. Merging to `published` starts
the site deploy, but the corpus is not live until that deploy finishes, so
a bare notification would make the builder embed the previous corpus and
then go idle believing it was current. This job regenerates the corpus
from the merge commit — deterministic, ~0.1s — and sends that hash so the
builder can wait for the site to actually serve it.

Path-filtered to content, examples and the generator itself: nothing else
in a deploy can move the hash, and the builder would only no-op. A missing
INDEX_BUILD_DISPATCH_TOKEN warns rather than failing, since a red X on
every merge is worse than an index that updates on the slower schedule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ci: drop the index-builder ping

The search index build is manual-dispatch only now, so this workflow had
nothing to notify. Without the PAT it existed to use, it ran on every
content merge only to warn that the token was missing and exit.

The corpus itself is still published by postbuild on every deploy of
`published` — only the ping is gone. Restore this file and the matching
repository_dispatch trigger if the build is ever automated; it carried
the expected corpus hash so the builder could wait out the site deploy
instead of embedding a stale corpus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(docs): make the MCP copy buttons actually report what happened

Every option was wrapped in PopoverClose, so a click dismissed the
popover immediately. The tick rendered into a component that had already
unmounted, which made the copied state, the icon swap, the reset timer
and its cleanup effect all dead code — and, worse, meant a clipboard
rejection (non-secure context, denied permission) closed the popover with
an empty clipboard and no hint that the one thing the button exists to do
had not happened.

Copy options no longer close the popover: they show a tick and "copied",
or a cross and "copy failed", for two seconds. The deeplink options still
close, since they hand off to another app.

Verified in the browser both ways — the failure path was reproduced for
real, because the clipboard write genuinely rejects under automation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(docs): build and publish the MCP search index from this repo

Moves the embedding step out of the search service and into this repo, so
that the finished vector index is published as release assets here.

Why here. The service's host times out building its own index — ~4k
chunks is 10-40 minutes of CPU — so the work has to happen on a runner
either way. Putting that runner in the service's own repo meant the index
landed in a PRIVATE release, which every deployed box then needed a read
PAT to fetch, and which forced ~80 lines of REST-API asset resolution
because `releases/download/` URLs are browser-session-authenticated and
answer 404 to a Bearer token. Publishing from the service INTO this repo
would instead have needed a cross-repo write PAT. Running the job here
needs neither: this repo is public, so the assets download anonymously,
and GITHUB_TOKEN can create the release. No secret exists anywhere in the
pipeline now.

`build-mcp-index.ts` reads the corpus `generate-mcp-corpus.ts` already
produces and writes `out/mcp-index/{index,pages}.json`. The workflow
regenerates the corpus from the checked-out content, so it does not have
to wait for a site deploy — it indexes what the branch says, which is
what the deploy will publish. It is manual and hash-gated: a run with no
content change costs about ten seconds.

`@qvac/sdk` is deliberately NOT a declared dependency. It pulls ~176
packages including native binaries, for a script no docs contributor
runs, and this repo's CI cannot `npm ci` regardless (the `@tetherto/*`
packages are token-gated), so every job already installs what it needs
into a throwaway prefix. `scripts/qvac-sdk.d.ts` keeps `types:check`
green without it; local use is documented in the script header.

Verified the format contract rather than assuming it: embedded a 20-chunk
slice for real, loaded the result with the service's own DocStore, and
ran a semantic query against it — 1024 dims, correct model name, sensible
ranking, anchors intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(docs): stop publishing the MCP corpus — nothing reads it

The corpus was written into `out/mcp/` by postbuild, so every deploy
shipped 6.7MB of it to the docs CDN. That made sense when the search
service lived in another repo and polled
`docs.pears.com/mcp/manifest.json` over HTTP to decide whether to
re-embed. Once the embed job moved here it started regenerating the
corpus from the checkout instead, which left the published copy with no
reader at all — not the workflow, not the service.

Output moves to `.mcp-build/`, which is gitignored and outside the
static export, and the generator comes out of the postbuild chain. Its
two real callers both invoke it explicitly: the index workflow, and the
`mcp-corpus` docs-lint job that fails a PR where two content files slug
to the same URL. `build-mcp-index.ts` reads and writes there too, since
its output is uploaded as release assets and was never served either.

Verified a full `next build` still produces the site with nothing
mcp-related in `out/`, and that generate → embed still works end to end
from the new paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ci(docs): harden the MCP index workflow — runnable, gated on content, split by privilege

Review of the index pipeline found it could not be dispatched (workflow_dispatch
files are invisible off the default branch), missed most docs edits, and ran an
unpinned npm tree next to a write token.

- Run on push to `published` as well as by hand, and refuse to run elsewhere.
- Gate on a build key: a new `contentHash` covering every chunk field and page
  (code samples, anchors, descriptions), plus the builder scripts, model
  checksum and SDK lockfile. `corpusHash` only covers embedded prose.
- Split build (read-only token, no persisted credentials) from publish (write
  token; runs only gh/jq, re-verifies digests from the artifact bytes).
- Pin @qvac/sdk and its embedder in scripts/mcp-index/package-lock.json to the
  versions the search service locks; install with scripts disabled.
- Publish under per-build asset names, keep the previous build, prune the rest,
  so a bad publish is a manifest re-upload away from rolled back.
- Tell "release not found" apart from other gh failures; tag the release at
  the pushed commit; record sourceSha, sdkVersion and buildKey in the manifest.
- Re-verify the cached model checksum every run and key the cache on the fetch
  script; add `format` to index.json and reject NaN/zero/wrong-length vectors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(docs): make the MCP install button work for a public endpoint

- "Claude Desktop" copied a remote `mcpServers` JSON block, but
  claude_desktop_config.json holds local (command/args) servers only; a remote
  server is added under Settings → Connectors → Add custom connector. The option
  now copies the bare URL, says "add in Connectors", and carries a tooltip with
  the steps. The raw JSON stays as a fallback for clients that take an
  mcpServers map.
- Strip trailing slashes from NEXT_PUBLIC_MCP_URL. The endpoint is exactly
  /mcp; /mcp/ answers 404, and an install that 404s looks like a broken docs
  site rather than a typo in an env var.
- .env.example no longer ships a live Sevalla preview hostname: copied to .env
  it would have rendered a button that installs a URL that will churn. The
  variable is commented out, and the comment says what the service needs (the
  installed config carries no credential, so /mcp must be public).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci(docs): install @qvac/sdk only after the gate, not before it

The build job ran `npm ci --prefix scripts/mcp-index` (~5.6GB unpacked,
every @qvac/* plugin's native prebuilds for every platform) before the
gate had a chance to say "nothing changed" — so a push that touched no
docs still paid for the whole install. The gate now runs against a
throwaway ~20MB install of just tsx + github-slugger (the corpus
generator's actual deps, same pattern docs-lint's mcp-corpus job
already uses), and the SDK installs in its own step, gated on
`steps.gate.outputs.build == 'true'`.

That step also moves the tree to scripts/node_modules with `mv`
instead of `cp -R` into the repo root: build-mcp-index.ts and
mcp-embedder.ts both live under scripts/, so Node's own resolution
finds it there without a second copy. Updated the two `node_modules/`
paths the packaging step reads sdkVersion/embedderVersion from, and
the stale `cp -R` instructions in build-mcp-index.ts's local-run
docstring.

Dropped `cache: npm` from the build job's setup-node: it would
restore/save the ~2GB compressed SDK tree on every run, including the
ones that now skip the install entirely, and it competes with the
model cache for the repo's shared 10GB Actions cache quota.

fetch-embed-model.sh's model download had no retry: one transient
HuggingFace error failed a whole cache-miss run. Added
--retry-all-errors (a plain --retry misses a reset mid-transfer, the
likeliest failure on a 670MB download) plus --connect-timeout and
--max-time so a stalled connection can't hang the job instead of
failing it.

Verified locally by extracting and running the workflow's `run:`
blocks against a fake `gh` and a real gzipped release: an unchanged
push now does only the light install (build=false, ~20MB); a changed
push does the light install, then the gated heavy install (mv into
scripts/node_modules, sdkVersion/embedderVersion read correctly),
then a real embed and publish, byte-identical to the pre-change
layout. curl's retry flags recover from injected 503s against a local
flaky server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci(docs): check the index's text against a trusted corpus before publishing

The publish job only re-derived digests from the build job's own output, so a
compromised package in the @qvac/* tree could rewrite chunk text, hash it
consistently, and have it served to readers' AI tools. The corpus is now
generated in its own job with only tsx and github-slugger, and publish checks
every chunk field, every page and every trusted manifest field against it; only
the vectors come from the untrusted build job. Artifacts are fetched by id and
checked against the corpus job's digest, so the build job cannot swap them.

Also:
- pin every action to a commit SHA, since publish holds contents: write
- add this workflow file to the build key, so a packaging change rebuilds
- raise the build timeout to 180 minutes: the measured embed is 53 minutes
  for 4146 chunks, and 120 left only ~2x headroom
- pin docs-lint's mcp-corpus install to the versions the index workflow uses
- replace the "10-40 minutes" / "20-minute embed" estimates with the measured
  runner number

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): treat an empty index release as unpublished, and document manual runs

gh release download reports a release that exists but holds no assets
as "no assets to download", not "no assets match". Publish creates the
release before uploading anything, so a first publish that failed
between the two left an empty release, and both manifest reads then
failed on every later run instead of treating it as a first publish.
Both now accept that message too; a real failure (rate limit, outage,
auth) still fails the step.

The header also said to "tick `force`", but the Actions UI never offers
a "Run workflow" button here: the default branch, main, carries no
workflows. It now gives the gh workflow run commands, says a failed run
is retried with "Re-run jobs", and says a manifest rollback only holds
until the next push to published unless the workflow is disabled.

Every edit to this file changes the build key and costs a full rebuild
after merge, so these land together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(docs): stop describing the MCP producers against the old service contract

Comments in generate-mcp-corpus.ts and build-mcp-index.ts still said the
search service polls the corpus manifest and compares corpusHash to
decide whether to re-embed. It does neither now: the service polls the
release's index-manifest.json and tells builds apart by payload digest,
and the workflow gates on its build key, which is built on contentHash.
corpusHash is an integrity check on corpus.json and provenance in the
index. Comments only; no behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): build the MCP index with @qvac/sdk 0.20.1 and embed-llamacpp 0.41.3

Moves the index toolchain to the latest SDK, in lockstep with the search
service (tetherto/qvac-docs-search), since index and query vectors must
come from the same llama.cpp build. embed-llamacpp stays pinned by
override, now at 0.41.3, the version SDK 0.20.1 resolves; 0.43.0 is
outside its range. bare-runtime moves to 1.34.0, which @qvac/inference
0.20 requires.

The lockfile is part of the build key, so the first run after merge
rebuilds the index even with no docs changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): tell a missing index release from a failed lookup by its HTTP status

Both jobs decided "nothing is published yet" from gh release's error
text. That text cannot carry the distinction any more: since gh 2.96
(the runners ship 2.101), FetchRelease runs a GraphQL draft lookup next
to the REST lookup and reports "release not found" when both fail and
either one missed. The draft lookup always misses for this non-draft
release, so any 5xx, secondary rate limit or timeout on the real lookup
came out as "release not found".

In publish that emptied PREV_ID, and once the API recovered the upload
went ahead and prune deleted the previous build, which is the rollback
target. In corpus it started an hour-long embed that was not needed.

Both steps now ask GET releases/tags/<tag> with gh api, and only its
HTTP 404 means there is no release. The asset list it returns then
decides between a live manifest and none, and any other failure stops
the step before the release is touched. Publish's create-or-reuse
decision takes the same lookup's answer instead of grepping gh release
view's error, which had the same flaw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): pin @qvac/fabric and record its version in the index manifest

Since the move to embed-llamacpp 0.41.3, llama.cpp no longer lives in
the embedder. Its linux-x64 addon is 670KB and links qvac__fabric@0.bare
from @qvac/fabric, which it takes with ^0.16.1. So the lockstep guards,
which cover only @qvac/sdk and embed-llamacpp, no longer pin the
llama.cpp build: fabric could drift apart between this lockfile and the
service's, giving index and query vectors from different builds while
both ends still report matching versions.

fabric is now an override at 0.16.1, the version both lockfiles already
resolve, so a lockfile that moves it fails npm ci until the override is
changed on purpose. The lockfile itself is unchanged, since npm records
no overrides there and fabric is already 0.16.1. The manifest gains
fabricVersion: the corpus job reads it from the lockfile, the build job
records what it installed, and publish checks one against the other,
like the other two versions.

The service reads the manifest with a spread, so it ignores the new
field until it compares it. Its matching override and skew check belong
in tetherto/qvac-docs-search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): refuse to publish a re-run once published has moved on

The header told readers to retry a failed run with "Re-run jobs", with
no caveat. A re-run always builds the commit the run started on, and
"Re-run failed jobs" also reuses the corpus job's gate outputs. So if
`published` had moved on and a newer run had already published,
re-running the older failed run put older docs over the newer index,
and they stayed there until the next push.

Publish now starts, on any attempt after the first, by checking that the
tip of `published` is still github.sha, and fails before it downloads or
touches anything if it is not. First attempts skip the check: runs start
in push order, so a newer tip is always built by a later run, and
checking them would only make a run that finished after, say, a
README-only push throw away its embed. The header now says when a
re-run is the right tool (only publish failed, its embed is reused) and
to dispatch against `published` otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(docs): stop telling the MCP operator to set QVAC_TRUST_XFF unconditionally

Which client-IP header to trust depends on the service host's proxy, and the
service README now has the procedure for deciding it after the first deploy.
Also note that NEXT_PUBLIC_MCP_URL only takes effect after a site rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ci): name the repo in the MCP index rollback commands

Every other gh command in the workflow header passes -R
holepunchto/pear-docs, but the two rollback commands did not, so gh
resolved the repository from the current directory. Run outside a
pear-docs clone they failed, and in a fork clone they would have rolled
back the fork's release while production stayed put. The download also
gets --clobber, so a stale index-manifest.json in the working directory
does not stop it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): keep non-published runs out of the MCP index queue

The workflow-level concurrency group was one fixed name, and the
published-only guard sits on the jobs, so a run from any other ref still
queued in the group. GitHub cancels the pending run of a group whenever
another one queues, so a dispatch from a feature branch, which then does
nothing, could evict the pending run for the newest published commit,
and that commit's docs would not be indexed until some later push.

Runs from other refs now get a group keyed on their run id. Runs of
published still share one group, so a re-run or a forced dispatch can
still displace a pending push run (and the reverse). The header now says
so, and what to do when it happens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): install the MCP corpus generator's deps from a lockfile

The corpus job is the trusted side of the MCP index workflow: publish
holds every chunk and page to what it generates. Yet it installed tsx and
github-slugger with a plain `npm install` by version, with no lockfile
and with install scripts on, so tsx's `esbuild: ~0.28.0` resolved to
whatever 0.28.x was newest that day and esbuild's postinstall ran in the
one job whose output nothing re-checks. A compromised esbuild release
could have rewritten the corpus before it was hashed, and publish would
have accepted text of its choosing.

The generator's deps now have their own package, scripts/mcp-corpus,
with a committed lockfile resolved to the same versions the mcp-index
lockfile already held (esbuild 0.28.2). Both the corpus job and
docs-lint's mcp-corpus job install it with `npm ci --ignore-scripts`,
so the two still generate the same corpus. esbuild needs no postinstall:
its platform binary arrives as a locked optional dependency, which is
what the build job already relies on.

scripts/mcp-corpus is now the single source of truth for those two
versions. github-slugger is dropped from scripts/mcp-index, where only
the corpus jobs ever read it; tsx stays there because the build job runs
the builder with it. The new lockfile is deliberately not part of the
build key: whatever the generator decides ends up in the corpus, which
contentHash already covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): say "none published" when the MCP index gate has no key

With nothing published yet the gate printed "Build key changed:  →
<key>", a stray double space where the old key would be. It now names
the case instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): fail packaging when an @Qvac version cannot be read

The package step passed each @Qvac version to jq as
`--arg x "$(jq -r .version ...)"`. A command substitution that fails
inside an argument list does not trip set -e, so with @qvac/fabric
missing the step succeeded and wrote fabricVersion "". Publish rejected
that manifest later, but only after a full embed. The versions, and the
model name, which had the same shape, are now read into variables with
jq -er first, so the step itself fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): require exactly one JSON document in each publish check

`jq -e` sets its exit status from the last output only, so publish's
checks of index.json, pages.json and index-manifest.json passed a file
made of two concatenated documents whenever the last one was genuine. A
compromised build job could prepend a page of its own to pages.json, or
write a genuine index twice, and "Verify the content against the
corpus" still reported a match. The service parses strictly and would
have kept its old index, so this was an availability gap rather than an
injection, but the check claimed more than it did.

Each check now slurps its file and requires exactly one document, and
the build-output step checks the manifest that way before reading any
field from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): pin down the index fields publish took on the build's word

Publish checked the key sets of index.json and the manifest, but not
everything inside them. index.json's builtAt could be any JSON value,
the manifest's assets.index and assets.pages could carry extra keys or a
string where a size belongs (both print the same through jq -r), and the
regex checks used ^ and $, which in jq also match before a final
newline, so a builtAt or model with a trailing "\n" passed.

index.json's builtAt must now be the builder's toISOString() form, the
asset entries must have exactly path, bytes and sha256 with the right
types (checked before the build-output step reads any of them), and
every pattern is anchored with \A and \z.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): never let prune delete the MCP index rollback build

Prune kept this build and whichever build the live index-manifest.json
named, read before the flip. Two cases read no previous build there. A
re-run after the flip (the header's own advice when only publish
failed, and prune is the one step after the flip) found this build in
the live manifest. A run after an upload that lost the manifest
(`--clobber` deletes before it uploads) found none. Either way prune kept
only this build and deleted the real previous one, the only rollback
target.

When the live manifest names no other build, prune now keeps the newest
other complete build (index, pages and per-build manifest all present),
ordered by when its index-manifest-<id>.json was uploaded, and says so in
the log. It reads names and upload times from the REST release lookup.
Publish also skips the upload when the live manifest is byte-for-byte
this build's: re-uploading would only delete and re-add, for a moment,
the assets live boxes are fetching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(docs): give the real reason the MCP install URL loses its slash

The layout comment said `/mcp/` answers 404. On the documented deploy,
with QVAC_API_TOKEN set and QVAC_MCP_PUBLIC=1, only the exact `/mcp`
path is public, so `/mcp/` falls behind the token gate and answers 401.
That matters for the reason the slash is stripped: a client handed a 401
reads it as a sign-in prompt, not as a typo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(docs): send Cursor the documented remote config, UTF-8 safe

cursorHref base64-encoded the config with btoa, which throws on any
character outside Latin-1 (the Cursor option then silently did
nothing) and encodes other non-ASCII as Latin-1 rather than UTF-8. It
now encodes the string's UTF-8 bytes; for an ASCII URL the output is
unchanged.

It also sent {"type":"http","url":...}. Cursor's MCP docs show a remote
server as {"url": ...} and document `type` only for stdio servers, and
its install-link docs encode exactly that inner object, so the deeplink
now carries {url} alone. The copyable JSON, VS Code and Claude Code
forms keep `type: "http"`, which those clients document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(docs): re-embed a chunk that overflows the model's context, shorter

The embedder caps each input at 1000 characters on the assumption that
this stays under GTE-large's 512 tokens. It does not for dense text:
1000 CJK characters tokenize to ~1000 tokens, base64-like text to ~700,
and llama.cpp then refuses the whole call ("... exceeds effective context
size (512)") instead of truncating. One such chunk failed its batch of
32, build-mcp-index.ts exited, and no index was published.

The cap stays. When a batch fails with that overflow, that batch alone
falls back to one text at a time, and a text that still overflows is
re-embedded at half its length until it fits, down to a floor of 64
characters, below which the error is rethrown. Each shortened chunk gets
one log line. Batches that fit take exactly the path they took before:
checked with the real model, 96 corpus chunks embed byte-identically
before and after, and a short text embedded during a fallback matches
the same text embedded alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docs): prune nothing when the MCP index rollback build is unknown

When the live manifest named no other build (a re-run after the flip,
or a run after a flip that lost the manifest), prune kept the build
whose index-manifest-<id>.json was uploaded most recently. That is not
the build that was live before. After a rollback, the newest other
build is the bad one that was rolled back from, so a re-run kept it and
deleted the known-good build, the real rollback target.

Nothing records which build was live before this run, so no guess is
safe. Prune now deletes nothing in those two cases and says so in the
log. The next run reads a live manifest that names another build and
prunes as usual, so the cost is a few extra assets until then, which
the step already accepts.

The read step's comment also said previous.json is used by the next
two steps. Only publish reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(docs): name the corpus job as the MCP corpus generator's caller

The generator's header said its first caller was build-mcp-index.ts, via
the index workflow. build-mcp-index.ts never runs it: it only reads
.mcp-build/corpus.json, which the workflow's corpus job generates and
hands to the embed job as an artifact whose digest is checked there.
That split is deliberate, since the corpus job is the trusted side and
the embed job is not, so a reader who believed the old header could move
generation into the embed job or pin the generator's deps somewhere else.

The header now names the corpus job, says build-mcp-index.ts only reads
the file, and notes that both callers install the generator's deps from
scripts/mcp-corpus's lockfile, as that package's description says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The pin was v4, which runs on Node 20; GitHub warns about it on every run of
build-mcp-index.yml, the only workflow that uses the action. v6.1.0 runs on
Node 24 and takes the same inputs (`path`, `key`) this step passes. It needs
runner 2.327.1 or newer, which `ubuntu-latest` has.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
… asterisks (#404)

The emphasis rule in toProse paired a `* ` list bullet with the bold that
followed it, so `* **Persistence**:` became `Persistence**:` in the embedded
text and in the docs search service's snippets. It also removed the asterisks
from `pear-*`, `bare-*` and version wildcards like `1.*`. Emphasized text now
has to start and end on a non-space, as in CommonMark.

Against `published` at d0fe9ae the corpus keeps 211 pages and 4191 chunks.
20 chunks change, and the 10 that showed `word**` after a bullet no longer
do. The same fix is in qvac-docs-search's src/corpus.ts, which mirrors this
function for local builds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lucas-tortora and others added 2 commits October 5, 2026 12:27
* fix(mcp-corpus): strip bold after a list bullet without leaving stray asterisks

The emphasis rule in toProse paired a `* ` list bullet with the bold that
followed it, so `* **Persistence**:` became `Persistence**:` in the embedded
text and in the docs search service's snippets. It also removed the asterisks
from `pear-*`, `bare-*` and version wildcards like `1.*`. Emphasized text now
has to start and end on a non-space, as in CommonMark.

Against `published` at d0fe9ae the corpus keeps 211 pages and 4191 chunks.
20 chunks change, and the 10 that showed `word**` after a bullet no longer
do. The same fix is in qvac-docs-search's src/corpus.ts, which mirrors this
function for local builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: name the app type in the template page titles

"Start from the hello-pear-electron template" never says it is how you start
a desktop app, so the docs search could not connect the two. Asked "How do I
create a new Pear desktop app?" or "Is there a starter project for a mobile
app?", it ranked the release and mobile-deployment pages first, whose titles
do say "desktop" and "mobile app", and missed the templates.

The three titles now name the app type, matching the hub's card labels:

- Start a desktop app from the hello-pear-electron template
- Start a mobile app from the hello-pear-react-native template
- Start a terminal app from the hello-pear-bare template

The sidebar entries in src/lib/p2p-tree.ts change with them. URLs are
unchanged. Link text elsewhere that quotes the old titles still reads
correctly, so it is left alone.

Measured against the MCP index (release 5566647a5b3f) with the 44 chunks of
these pages re-embedded under the new titles: both questions above now rank
the right template page first, and the top result of 88 other questions the
docs answer did not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(docs): collapsible accordions for reference and FAQ content

Puts fumadocs' unused Accordion/Accordions component to work (confirmed
unused anywhere in content/ before this change).

- Register Accordion/Accordions in mdx-components.tsx.
- bare-modules.mdx: the 34-row Node.js -> Bare module table now
  collapses behind one Accordion instead of always rendering in full.
- bare-runtime.mdx / bare-on-native.mdx: "Common questions" sections
  converted to FAQ-style accordions.
- bare-fs.mdx, bare-buffer.mdx, bare-stream.mdx, hypercore.mdx: every
  per-function/per-type reference entry (615 headings total) is now a
  collapsed Accordion item instead of an always-expanded section, on
  pages that were previously thousands of lines of unbroken reference
  text.

  Group headers (##, and ### with sub-headings) are left as real
  markdown headings so the sidebar TOC keeps its section-level
  structure; only true leaf entries (### with no children, #### always)
  convert. Every converted heading keeps its original slug as an
  explicit id/value on the Accordion, so existing deep links (9 real
  cross-page links into hypercore.mdx, plus 150+ in-page
  cross-references inside these files) keep resolving - fumadocs'
  Accordion auto-opens and scrolls to the matching item on a hash
  match. Verified live: /hypercore#corekey opens exactly that item and
  leaves its siblings collapsed.

Verified: an MDX-compile-only check passes on all four large reference
files; check:internal-links passes across all 211 content files;
check:doctypes passes; npm ci + next build both reproduce clean from
scratch.

Split out of #392, which bundled this with the page-feedback/
last-updated work, to isolate a Kinsta preview-deploy failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* revert: drop accordion conversion from reference pages, keep FAQ

Keeps the FAQ-style accordions (bare-runtime.mdx, bare-on-native.mdx —
docType: explanation) and the Accordion/Accordions registration
(mdx-components.tsx, still needed for those), but reverts the five
docType: reference pages back to plain markdown:

- bare-modules.mdx
- bare-fs.mdx
- bare-buffer.mdx
- bare-stream.mdx
- hypercore.mdx

Verified: check:internal-links and check:doctypes both still pass
across all 211 content files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
)

Searches phrased the way people ask, such as "copy a starter project and
change it", ranked the chat tutorial's changelog sections first, because
those use the words "change" and "project" and the template pages used none
of the query's words ("starter", "copy", "change"). The two template intros
now say "copy an official starter project—a boilerplate you clone and
change".

The "Start from a template" section of the P2P getting-started page also
offered only the desktop and terminal templates. It now names mobile apps
and links hello-pear-react-native, matching the template hub.

Measured against the MCP index (release 5566647a5b3f) with the two edited
chunks re-embedded: "copy a starter project and change it", "Is there a
starter project for a mobile app?", "starter project for mobile" and "how do
I start a new pear project" now rank a template page first. Of 90 other
questions the docs answer, only one top result changed (an improvement).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs: document Pear 3.5.0

Covers every item in the 3.5.0 changelog (holepunchto/pear#1226), verified
against a real v3.5.0-rc.2 build rather than read off the source.

CLI reference:
- pear identity, the new command: seed, blind-relay, blind-peer, and
  blind-peer-client each print one of this machine's network public keys.
  The key goes to stdout and the next-step hint to stderr, so
  KEY=$(pear identity seed) captures the key alone. --json is declared on
  the parent, so it goes before the subcommand. blind-peer is the one
  subcommand that is not a pure lookup: it opens a blind peer against the
  platform storage directory to read its key, creating that directory on
  first use.
- pear blind-peer identity is gone, replaced by pear identity
  blind-peer-client. Kept with a Removed badge plus the error the old form
  now produces, and the two-keys table now points at the replacement.
- pear seed's stats table: Verlink replaces Drive Key (--json still carries
  driveKey unchanged), a Blobs Length row joins Drive Length, and both now
  report synced blocks and a percentage. Adds a captured live-view sample.
- pear cores writes its table straight to stdout instead of through
  console.log, which mangled the rules and the tick marks into mojibake on
  Windows. The 3.4.0 table sample was derived from source and flagged as
  needing verification; replaced with a real capture and the caveat dropped.

Also adds a how-to for pear identity. The four keys are distinct and not
interchangeable, and the failure mode for mixing up blind-peer with
blind-peer-client is silent: an untrusted request is stored and served but
never announced, with nothing in the client's own output saying so. The
guide is organised by hand-off rather than by subcommand, since each key
only matters when another machine needs it.

Removes a duplicated copy of the whole "Blind peers and relays" section in
the CLI reference (82 lines, including a second set of pear-blind-peer and
pear-blind-relay anchors).

Refreshes the pear seed sample in the chat tutorial, which was still the
3.3.0 shape, and advances the pear-cli commit pin to 749b37d.

Note: v3.5.0 is not tagged upstream yet (latest is v3.5.0-rc.2), so the
Release Overview date and the upstream-releases-state pin both need a pass
before this merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: verify Pear 3.5.0 against the shipped tag

Re-ran every capture against v3.5.0 (168dd2b) rather than the v3.5.0-rc.2
build the original pass used. The two are identical in code — the only
diff between them is CHANGELOG.md and the version field — so every key,
hint, table, error string and JSON shape in this PR reproduces exactly on
the released tag. The shipped CHANGELOG also matches holepunchto/pear#1226
unchanged.

v3.5.0 is now tagged (2026-09-22), which settles the two items the PR was
holding on:
- the Release Overview date was already the ship date, so it stands.
- upstream-releases-state.json advances to v3.5.0.

Adds the module API changes that arrive with 3.5.0's bundled dependencies.
The reference pages already carry them (#382 regenerated hypercore at
v11.36.1, hyperdht at v6.34.0 and compact-encoding at v3.5.0, which is
exactly the set 3.5.0 bundles), but neither release overview announced
them, so a reader upgrading Pear had no way to find them:
- Hypercore v11.36.0 adds core.setAlwaysLatestBlock(enabled).
- HyperDHT v6.34.0 widens node.stats with punches.tryLater,
  punches.tryLaterRelayedHandshakesAtLeastOnce and a socketPool group.
- Compact-encoding v3.4.0 adds cenc.bitarray.

Advances those three release pins alongside pear's, so the changelog
watcher does not later draft entries for releases this covers. The
remaining module pins stay where #382 left them.

Also records that seed --json has no verlink field — the terminal row is
composed from driveFork, driveLength and driveKey, and a script that wants
it builds it the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: close the gaps #382 left in module pins and release notes

#382 refreshed the reference pages to the versions Pear 3.5.0 bundles but
left upstream-releases-state.json behind, so the changelog watcher would
have re-drafted entries for releases already documented. Advances every
pin to the version its page actually documents:

  bare                     v1.30.3 -> v1.33.3
  autobase                 v7.28.1 -> v7.28.2
  hyperswarm               v4.17.0 -> v4.17.1
  corestore                v7.12.0 -> v7.12.5
  hyperswarm-secret-stream v6.9.1  -> v6.9.2
  protomux                 v3.11.0 -> v3.12.0

A pin is never advanced past what the docs cover: pear-build stays at
v1.3.0 although v1.3.1 exists upstream, because nothing documents v1.3.1
yet and the watcher should still flag it. Same reasoning caps bare at
v1.33.3 rather than the current v1.33.5.

Chasing those pins turned up three surface changes this PR had wrongly
filed as internal. The earlier pass checked for new `####` headings and
found none, but these land as option-table rows and a callout, so they
were missed:

- Corestore v7.12.5: store.get() forwards allowLatestBlock to the
  Hypercore it returns. Absent in 7.12.4, so it is inside Pear's window.
- Protomux v3.12.0: a channel handler that throws after its channel has
  closed now emits `warning` on the stream instead of destroying it. A
  behaviour change for anyone relying on the old teardown.
- Secretstream v6.9.2: adds filterZeroByteMessages, already implied by
  keepAlive and now settable on its own.

Bare v1.32.0's Thread/ThreadOptions change gets an entry too: the runtime
page documents it with Since markers, but no release overview recorded it
and Pear 3.5.0 bundles that version.

Also removes a duplicate `treeCache` row #382 added to CorestoreOptions.
The table carried two rows for the same property with different wording;
kept the pre-existing one, which names the introducing version (7.11.1)
and is not hedged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(bare-refgen): stop TSDoc overlays documenting API the package lacks

The generator swaps in a module's `chore/ts-doc` fork branch .d.ts for its
richer descriptions, guarded by coversBaseline(). That guard only checked
one direction: the fork must declare every published name. It let the fork
declare *more*, so a branch cut before a release that removed API, or
rebased onto unreleased main, put symbols on the page that the installed
package does not have. #382's refresh shipped three such pages:

- bare-type (documented 1.1.1): addTag/checkTag/createTag, removed in
  1.1.1 itself. The fork branch predates the removal.
- bare-assert (1.2.0, current latest): assert.doesNotMatch, assert.ifError
  and assert.match, which no published release implements.
- bare-encoding (1.0.3, current latest): TextEncoderStream,
  TextDecoderStream and their Constructor types, present only on
  unreleased main.

A reader following any of those gets `x is not a function`.

The guard now classifies the fork against the published package:
- `missing`: drops a published name (the old check, unchanged).
- `phantom`: adds a name that is neither a type nor present in the
  package's own .js — the new check.
- `ok`: otherwise.

Checking against the runtime rather than just the .d.ts matters: bare-crypto,
bare-module and bare-bluetooth-apple's forks add typings for API the
published JS already implements (crypto.subtle, loader conditions,
peripheral.central). Those overlays are correct and more complete than the
published .d.ts, and a pure declaration diff would have thrown them away.
Naming a previously inline type (bare-vm's RunOptions) is also allowed.

A phantom entry file now also skips the package's subpath overlays: the
branch is ahead of or diverged from this release, and bare-encoding's
global.d.ts showed why — its type aliases point at the unreleased stream
classes. A merely stale entry (`missing`) leaves subpaths to be judged one
by one, as before.

Measured against all 71 modules: exactly bare-assert, bare-encoding and
bare-type change; the other 68 keep the overlay they had.

Regenerating without the overlay surfaced a second bug: linkType()
collapsed a multi-line type literal by whitespace alone, so a param typed
`{ message?: string <newline> actual?: any }` rendered as
`{ message?: string actual?: any }`. collapseType() now inserts `;`
between newline-separated members.

bare-type regenerates at 1.4.0, the current release, because the generator
always packs latest. The page is built from the published 1.4.0 .d.ts, so
it gains isAsyncGeneratorFunction, the is*Object predicates, type.of and
type.constants, and loses the three tag functions.

Tests: coversBaseline cases for the removed-API, unreleased-class,
runtime-implemented and named-type cases, and collapseType cases. 28/28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: close API gaps found auditing Pear 3.5.0's dependency tree

An adversarial pass over v3.4.0..v3.5.0, checked against source rather than
changelogs, which are empty for most of these repos. Every version below was
bisected against upstream tags.

Release Overview, Pear 3.5.0 entry:
- pear blind-peer identity's removal is now labelled Breaking. It removes
  a command scripts may call; the upstream changelog files it under
  Improvements.
- New Changed item for `pear seed --no-tty`: the `... drive key` line
  becomes `... verlink`, and `... drive length` gains a sync suffix, so
  line-scraping scripts break. --json is unaffected.
- A framing line: these are the versions the CLI bundles. An app pins its
  own copies, so upgrading Pear does not change them in the app.
- Additions the entry had filed as "internal" or missed:
  - Hyperswarm 4.17.1 retries CANNOT_HOLEPUNCH failures through
    relayThrough, which widens when Pear's --relay is used.
  - Hyperschema 1.23.0 packs bool arrays into a bitfield. That is a wire
    format change: specs regenerated across the boundary cannot read each
    other's bool arrays. 1.23.0 also rejects uint1-uint7 arrays at build
    time, and 1.24.0 adds fixed8/16/24.
  - Compact-encoding 3.5.0 adds cenc.fixed8/16/24. The heading already
    named 3.5.0 but listed only 3.4.0's bitarray.
  - Hypercore 11.35.4 lets a session's `group` option assign a group to a
    core that has none. Corestore 7.12.3 is the matching fix.
  - blind-peering: sendNotification() is rate-limited by default (2.8.0).
    In 2.9.0 addAutobase() discovers views itself, so additionalViews is
    ignored, and it accepts an Autobee.
  - blind-peer adds constructor options and raises notificationTimeout to
    30s. The entry states that none of this reaches pear blind-peer or
    pear seed --blind-peer, and that trust handling is unchanged, which
    was verified in the 3.15.0 source.
  - Bare 1.32.0 adds Addon.seal()/Addon.sealed and the callback Thread
    form. It also notes that Thread.create() stays deprecated from 1.31.0.
  - bare-type 1.1.1 removed createTag/addTag/checkTag in a patch release.
- Protomux 3.12.0 is corrected: the `warning` path covers an async
  handler's rejected promise only. A synchronous throw is unaffected.

Reference pages (gaps #382 left):
- hypercore: the `group` constructor/session option was undocumented.
- hyperswarm: relayThrough now documents the function form
  `(force, swarm)`, the error codes that set `force`, and CANNOT_HOLEPUNCH
  from 4.17.1.
- protomux: the same async-rejection correction as the release note.

CLI reference and how-to:
- Next-step hints: pear identity joins the commands that print one.
- pear cores: "other platforms were unaffected" overstated what was
  checked. It now says macOS was already correct.
- The how-to linked "z32" to an explainer that never mentions it. The
  link is dropped and the term inlined.
- pear identity blind-peer: when no blind peer is running, the one it
  opens joins the network and resumes announcing stored cores for the
  command's lifetime. It does not listen for requests.

Tutorial: the seed sample printed `12.4 MB`, but Pear's tiny-byte-size
emits `12.4MB`.

Agent skill: pear-cli's command summary was missing the blind peer and
relay commands (3.4.0) and pear identity (3.5.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
lucas-tortora and others added 2 commits October 5, 2026 12:45
Add .env.production with NEXT_PUBLIC_MCP_URL=https://mcp.pears.com/mcp, the
streamable-HTTP endpoint of the docs MCP server (tetherto/qvac-docs-search).
The "Add to your AI tool" button under the sidebar search renders only when
this is set. The value is public and inlined at build time, so it is committed
rather than kept in the host's build environment.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(docs): add on-device AI how-to with hello-pear-qvac-tui

Recovered from the stale chore/module-updates branch (f16c917), split out
from the unrelated bare-refgen .d.ts pipeline overhaul that was bundled
into that same commit, and rebased onto current published.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(docs): refresh hello-pear-qvac-tui snapshot to 605eb96

Upstream moved 4 commits past the old 8c2e46f pin (#2-#5): CPU ggml
backend registration fix, dep bumps, and update-scheduling/error
handling. Refreshed app.js, bin.mjs, lib/inference.js, package.json,
package-lock.json and workers/qvac.js; kept workers/main.js as the
inlined hello-pear-worker copy per the snapshot's deliberate deviation.
Bumped the pinned SHA in watch-boilerplates.yml and re-checked the
article's #L... code-import ranges, three of which had shifted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(docs): wire hello-pear-qvac-tui into test-examples, fix code-import range

- scripts/test-examples.ts and workers/main.js were missed in the initial
  split of the article commit: the scenario definition backing the
  README's "npm run test:examples -- --filter=hello-pear-qvac-tui" claim
  lived outside the path list used to extract the article from the old
  bundled commit. Added it back.
- Re-synced workers/main.js to the current canonical hello-pear-worker
  copy (examples/getting-started/hello-pear-electron/workers/main.js
  picked up a try/catch around applyUpdate since the old snapshot).
- Fixed the lib/inference.js#L21-L60 code-import range: it never closed
  the class's opening brace within the slice. Starting at the constructor
  line instead balances it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(docs): address adversarial review findings on the QVAC how-to branch

- package.json: drop the bare-refs:poll-tsdoc script — leftover from the
  unrelated bare-refgen overhaul bundled in the old source commit; the
  script it names was correctly excluded, leaving a dead reference.
- examples.yml: restore "pinned Bare runtime" (the diff had flipped this
  to "latest", contradicting the pinned install step 116 lines below) and
  fix the scenario/matrix counts (13 scenarios, 10 run — not 10/9).
- examples.yml: exclude the qvac-tui vendor directory from the push/PR
  path triggers, since its scenario is deliberately not in the matrix —
  every vendor refresh was triggering the full 18-job matrix for nothing.
- test-examples.ts: fix a copy-pasteable local-run command missing a
  second `--` before --filter (silently ran all scenarios instead of
  erroring).
- test-examples.ts: add a typed ci/ciSkipReason field on Scenario plus a
  load-time consistency check, so "deliberately excluded from CI" is a
  machine-checked property of the scenario, not just prose three
  separately-worded comments have to stay in sync on. The three other
  mentions (examples.yml, this README) now point at it instead of
  restating the rationale.
- article MDX: reworded the wire-protocol table's intro so its scope
  (the whole protocol) matches its content, instead of undersellling it
  as covering only the single-question flow the diagram above it shows.
- three files: dropped the hardcoded "eleven"/"ten" vendored-copy counts
  in favor of wording that doesn't need updating on the next addition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(docs): address findings from re-reviewing the previous fix round

The prior fix collapsed 4 restatements of the CI-skip rationale down to
pointers, but overcorrected on two of them: the vendored README's "Why
it isn't wired into CI" section and the workflow header now answered
their own question with nothing but a pointer. Restored a concrete
reason at both sites, matching the balance test-examples.ts's own
top-of-file docstring already struck (a one-line reason plus a pointer
to the canonical source, not one or the other).

Also collapsed the ci?: false / ciSkipReason?: string field pair into a
single ciSkipReason?: string — presence alone means "excluded, and
this is why," so the two fields could never actually desync and the
validation loop policing that was dead weight. Matches the sibling
check-examples.ts's existing skip="<reason>" convention for the same
"deliberately not run, here's why" idea, which the two-field version
was reinventing.

Softened the field's doc comment: it no longer claims exclusion is
"machine-checked" or that unset means "runs in CI" — autobase-multiwriter
and autobee-multiwriter are both absent from examples.yml's matrix with
no ciSkipReason, a pre-existing gap from before this branch that the
new field doesn't cover. Left as a known gap rather than silently
implied away or fixed here (out of scope for this article).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…e/bare/modules/ (#413)

The catalog lived at /bare/reference/modules/bare-modules/, in a one-page
"Modules" sidebar folder separate from the folder holding the per-module
references, so /bare/reference/bare/modules/ itself had no landing page.

- Move content/bare/reference/modules/bare-modules.mdx to
  content/bare/reference/bare/modules/index.mdx.
- bare-tree.ts: drop the standalone "Modules" folder and make the catalog
  the index of the existing Bare > Modules folder.
- Rewrite every link to the old route (/bare/reference/modules/bare-modules,
  including the #nodejs-compatibility anchors) in content/, generated/ and
  the refgen layouts.
- Point refgen (CATALOG_MDX / CATALOG_ROUTE), research-bare-modules.ts and
  the cross-link canonical at the new path.
- redirects.ts: retarget the two legacy /reference/... redirects and add
  /bare/reference/modules/bare-modules/ -> /bare/reference/bare/modules/.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
)

Add the host-loop polling and attaching parts of the C embedding API to the
Embedding section of the Bare runtime reference. They shipped in Bare 1.34.0
(holepunchto/bare#218 and #219) and are not documented anywhere on the site;
the only prose was in the holepunchto/bare README, which is being trimmed to
link here instead.

The text is checked against include/bare.h, src/runtime.c, and the poll and
attach tests at Bare 1.34.1. The version requirement is stated in prose, as on
the embedder context page, because the bare-runtime doc-states axis only tracks
the Bare global in npm/index.d.ts (1.32.0 is its newest) and a <Since> marker
for 1.34.0 would fail check:docs-versions.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* docs: refresh the Bare module reference pins

Regenerates the bare-refs output and moves the module page pins to the releases that Bare 1.34 and the Pear 3.6.0 lockfile pick up.

- bare-module 7.0.3 to 7.2.1. The regenerated page drops the hand-written CommonJS and ECMAScript sections, so they are restored, with the require.resolve() and import.meta.resolve() option docs added in 7.2.0. patch(), evict(), instantiate(), instantiateSync() and the protocol identity change in 7.2.1 are described in the layout describe map, so the next regeneration keeps them. The stale conditions entry is removed from that map: bare-module's loader declares no such option, so check:bare-refs failed on it.
- bare-subprocess 6.2.0, which depends on bare-structured-clone v2 and has no API change
- bare-structured-clone 2.0.1, with a threat model section
- bare-type is left alone: it was already at 1.4.0 from the Pear 3.5.0 docs
- pin bumps for the other Bare modules the regeneration touched (bare-crypto, bare-fs, bare-ipc, bare-sidecar, bare-tls, bare-module-lexer, bare-module-traverse and others)
- bare-make stays at 1.8.0. 2.0.0 is a major release (cmake-toolchains v2, bare-process removed), and the repo's pin policy asks for a versioned page rather than a bumped pin. Only its generated preview is regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* docs: refresh the P2P reference pins

Moves the autobee, hypercore, hyperdrive, hyperswarm, compact-encoding, corestore and protomux page pins to the releases of 2026-09-30.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore(refs): regenerate the P2P reference models and previews

Output of the regenerate-references watcher (generate-refs --all, then gen-curated and refgen/report-all) against current upstream: autobee 2.12.1, compact-encoding 3.5.2, corestore 7.13.0, hypercore 11.37.2, hyperdht 6.34.1, hyperdrive 13.3.4, hyperswarm 4.17.2 and protomux 3.12.1. The live content pages are untouched, and the curated-manifest gate passes.

The JSDoc gap reports now show far lower completeness than the committed ones (for example protomux 94% to 0%). That is what the pipeline reports against the new upstream sources, not a change in the pages.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* docs(bare-subprocess): move the reference to 6.2.1

6.2.1 fixes spawn() when the child fails to launch: the failed subprocess no
longer signals the process group of the parent when it is torn down. There is
no API change, so the page only moves its pin and gains a note. The note joins
the layout's seeAlso so that a regeneration keeps it instead of losing a hand
edit, and the shell and maxBuffer descriptions in the layout now give the
Windows COMSPEC default and the 1 MiB default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active (outdated) deployment
preview — 52500d6a Deployed Oct 6, 2026 by kinsta[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants