Repository navigation
Release published docs to main - #306
Open
lucas-tortora wants to merge 86 commits into
Open
lucas-tortora wants to merge 86 commits into
lucas-tortora wants to merge 86 commits into
Conversation
- 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>
Preview deployments for pear-docs preview ⚡️
Commit: Deployment ID: Static site name: |
* 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>
* 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>
…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>
* 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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
publishedbranch tomain, replacing the legacy docs site with the current published Pear docs platform.mainintopublishedwith all conflicts resolved in favor ofpublished.mainwith 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.mdguide/making-a-pear-desktop-app.mdguide/starting-a-pear-desktop-project.mdWhat's included
@tetherto/docs-seo-*end-to-end (metadata, sitemap, robots, JSON-LD, OG)Test plan
mainMade with Cursor