All notable changes to @pagenary/publisher are documented here.
Format is based on Keep a Changelog; versioning
is CalVer (YYYY.M.PATCH, no leading zeros — see .claude/rules/versioning.md).
- Added the brochureware/marketing-site planning track for UX-owned React tenants, including ADR-017 for the content/UX boundary, the #140 spike findings, and the #139 construction breakdown for magly.net tenant-zero work.
- Retargeted the Fortémi integration plan to
@fortemi/core@2026.7.8, distinguishing the DB-free record tier from the later PGlite projection tier and updating the dependency-posture and supply-chain guidance accordingly.
- Re-cut the 2026.7.22 release: its tag failed the publish version guard
because
apps/embed's internal@pagenary/blog-clientdependency pin was not bumped alongside the package versions. 2026.7.22 was never published.
- Added a React/SPA publishing assessment that plans optional React-backed app routes while preserving Pagenary's static docs runtime and generated SEO/data artifacts.
- Landed the hybrid React runtime: tenant
runtimeschema and validation, the optional@pagenary/reactVite adapter (workspace package, not yet published to the registry), authored-fallback React routes, and thehybrid-reactexample tenant with unit and browser smoke coverage. - Adopted
@fortemi/react@2026.7.4: the docs map's React control now renders through upstreamGraphViewfrom the PGlite-free/graphsubpath — the@fortemi/corestub alias and hand-rolled SVG renderer are retired, and the adapter externalizes@electric-sql/pgliteso no WASM bytes ship (33 MB → 440 KB react assets on the hybrid example). - Baked docs-map render snapshots: builds emit a deterministic
docs-map/render-graph.json(sorted nodes/links with baked positions) that warm-start-capable renderer tiers load;docsMap.snapshot: falsesuppresses. - Interactive docs-map tiers:
docsMap.renderergainsfortemi-react-2d(Sigma live explorer) andfortemi-react-3d(Three.js force graph) with per-tier optional peer preflight and actionable errors; newdocsMap.palette(community/greyscale/custom) anddocsMap.draggable(pin-drag re-layout) options. Unselected tiers are externalized so static tenants ship zero Sigma/Three bytes.
- Inline markdown images, links, and tooltip spans whose attributes contain entities (apostrophes in alt text, querystring ampersands in URLs) rendered as escaped literal text instead of real tags (#138, seen live on docs.aiwg.io). The escape/restore pass now matches attribute values to the real quote delimiter and un-doubles the second escape pass.
- Registered the supply-chain documentation pages (dependency posture, supply
chain, vendoring) in the dogfooded docs tenant so
strictLinksno longer fails the docsite deploy. - The React adapter no longer mis-injects a code-split chunk named
indexas the tenant entry script — entry detection is root-level only.
- Re-cut the release with the signed annotated tag preserved on the GitHub mirror while retaining the idempotent mirror-tag release workflow.
- Made the release workflow's GitHub mirror tag push idempotent when the same signed tag object already exists on the mirror, comparing canonical Gitea and GitHub tag object IDs directly.
- Enabled the built-in Docs Map in Pagenary's own dogfooded docs tenant so the public documentation exercises the relationship-map feature by default.
- Generated crawler/static fallback pages for dynamic Docs Map sections so
static navigation, sitemap URLs, and no-JS readers do not land on a missing
/pages/docs-map.htmlsnapshot.
- Documented the monthly-updates docbase pattern for bespoke SPAs that consume
Pagenary collection manifests, RSS feeds,
@pagenary/blog-client, or the<pagenary-blog>embed. - Added an Agentic SEO and AI discoverability docs page that ties GEO/AEO,
llms.txt, static snapshots, corpus artifacts, and PR-gated maintenance workflows to shipped Pagenary features.
- Scaled the built-in docs-map SVG layout for dense documentation graphs so large corpora, including the AIWG docs map, no longer collapse into heavy border clamping.
- Backfilled and refined Pagenary update posts for the public release announcement and monthly project history.
- Updated the Pagenary docsite to use the same SVG favicon as the main
pagenary.comsite. - Removed Pocket from the built-in sharing catalog, tenant schema, docs, Pagenary docsite share menu, tests, and bundled share icon assets because the service has shut down.
- Updated the GitHub npm publish workflow for npm trusted publishing: Node 24, npm 11.16, OIDC-based publishes without a long-lived npm token, automatic provenance from npm trusted publishers, and runtime GitHub repository metadata rewrites for provenance matching.
- Documented the npm trusted-publisher setup for all public Pagenary packages
and the required GitHub
workflowscope for mirror pushes that update.github/workflows/*. - Expanded the new
@pagenary/blog-clientand@pagenary/embednpm READMEs to match the main Pagenary package format and added npm search keywords for both packages.
- Added the public
@pagenary/blog-clientpackage for fetching, normalizing, source-labeling, and newest-first aggregation of one or more Pagenary blog indexes. - Added the public
@pagenary/embedpackage with the framework-agnostic<pagenary-blog>custom element for runtime blog/update embeds. - Documented cross-site blog consumption with install examples, source identity, SSR/build-time aggregation, embed styling hooks, CORS headers, Cloudflare/CDN allowances, CSP setup, and troubleshooting.
- Versioned the generated blog index contract with
schemaVersion, top-level and post-levelsource/docbaseidentity, and explicit posturlfields. - Updated the release workflows to publish
@pagenary/blog-client,@pagenary/embed, and@pagenary/publishertogether with aligned CalVer versions and tarball allowlist checks. - Added CORS/cache headers for generated blog
index.jsonandfeed.xmlartifacts in the bundled Caddyfile.
- Added local full-color and monochrome share icon assets for every built-in share target, and rendered those icons in the desktop share menu without loading any third-party icon CDN or script.
- Enabled the optional Share control on the Pagenary docs tenant, with native mobile sharing and a docs-friendly desktop service set across all content pages.
- Documented the built-in share target list, local icon assets, custom service icons, and trademark/source notes for tenant configuration.
- Added authored section heading pages: manifest groups can now publish their
own navigable page when they declare
filealongside childsections. - Added visible child-page link lists to authored Markdown and HTML section heading pages.
- Included module-bearing group pages in recursive prev/next navigation and export/search documentation.
- Documented section heading page authoring and SEO guardrails across the tenant, SEO, API, and developer docs.
- Let the root no-JS HTML fallback scroll as a normal document by removing app-shell fixed-height and hidden-overflow behavior only when JavaScript is unavailable.
- Allowed generated
/pages/*.htmlstatic snapshots to scroll normally by overriding the app-shell fixed-height and hidden-overflow styles in the standalone HTML view.
- Removed the no-JS meta refresh from generated
/pages/*.htmlsnapshots so root fallback navigation stays on the selected static page instead of snapping back to the SPA root/default page.
- Removed the no-JS fallback-only nav class when the JavaScript runtime mounts the interactive navigation, so browser accessibility smoke sees the hydrated primary nav.
- Added an all-open static sidebar navigation to the root no-JS HTML fallback so
HTML-only readers can navigate generated
/pages/snapshots without running the JavaScript app.
- Added
seo.rootHtmlFallbackso tenants can explicitly enable or disable the rootindex.htmlno-JS fallback. It defaults to enabled.
- Embedded the default page HTML into the root SPA shell so published docs show useful content without JavaScript instead of an empty header/footer chrome.
- Repaired the mobile portrait showcase and page-effects layout so full-bleed heroes, CTAs, on-this-page controls, and bottom navigation stack in readable flow instead of overlapping the first screen.
- Materialized configured collection posts for tenants that use an explicit
manifest.json, so curated docs navigation can coexist with a mixed docs + blog collection without hand-listing every post. - Made generated collection
index.jsonpost paths hash-routed (/#posts/<slug>) so custom blog indexes can use the emittedpathwithout navigating to missing route pages.
- Clarified mixed docs + blog behavior for manifested tenants and custom blog index links.
- Added launch-time base path overrides for tenant builds:
pagenary build <tenant> --base <path|auto>andPAGENARY_BASE=<path|auto>. - Added
pagenary serve --mount <path>/--base <path>so local previews can serve a built tenant at the same subpath used in deployment.
- Base-less tenant builds now resolve runtime assets from the served
index.htmldirectory, making one build portable across/,/docs/,/<tenant-id>/, and reverse-proxy subpath mounts. ExplicitbasePathvalues still hard-pin the generated base.
- Published the mixed docs + blog materialization fix so npm-pinned consumers
receive navigable collection posts: manifestless flat
content/tenants now rendercontent/posts/*.mdas hash-routed sections/pages in addition to collection metadata. - Regenerated and guarded packaged site CSS so released artifacts preserve the rail on-this-page TOC viewport cap, static header/controls, and internal list scrolling rules.
- Clarified the supported mixed docs + blog configuration, hash-router linking
rule (
entry.idfor#...links), and thepageToc.placement: "rail"CSS contract for stylesheet customizations.
- Repaired the page-effects figure image reference in the published docs so the current examples render with the expected visual asset.
- Capped the wide rail on-this-page TOC to the viewport and moved scrolling into the heading list, keeping the title and prev/next controls visible while long outlines scroll internally.
- Declared the repository remote topology in AIWG project config: Gitea
originremains the primary, issue-tracker, and CI remote, while GitHub is now recorded as a secondarypublishmirror for release workflows.
- Trimmed the published package to the public publisher surface. Internal
commercial tooling that is not part of the open publisher has been moved to a
separate private repository, and the related tenant-config fields were removed
from the public schema. The supported CLI surface is
build,serve,tenants,check,new/init,doctor,lint,clean,sync, andcaddy(runnpx pagenary --help).
- On-this-page TOC placements.
pageToc.placementselects where and how the outline renders:right— a persistent nav-style list in the right gutter, collapsible to a chevron handle for full-width reading;left— the same nav-style list mirrored into the sidebar under the site navigation (content runs full-width);top— a bordered block above the content;rail— a pinnable panel that collapses to a prev/next bar revealed on hover/tap. The heading list scrolls on its own with the title and prev/next pinned above it. - Quick-copy control on code blocks (#89). Opt-in via
codeCopy: true— a Copy button on each<pre>copies the exact source via the Clipboard API with transient "Copied" feedback; a pure enhancement (code stays selectable with JavaScript off). - The Pagenary docs enable reading-progress and code-copy as live examples.
- Docs content is centered by default (
.doc-content), so wide and landscape viewports get balanced margins instead of crammed-left layouts.
- The on-this-page rail no longer inflates a long page to ~300,000px (a stray grid-row span over the row gap).
- Scroll-spy now activates the final heading at the bottom of the page — the last entry was unreachable, so Next greyed one entry early.
- The right-placement list is bounded to the viewport: it scrolls independently with the title and prev/next pinned above, and never runs past the fold or under the footer.
- Prev/next article navigation is a generic docs feature, on by default. The
bottom prev/next pager (titled links to the adjacent article in nav order) now
shows on all screen sizes for docs layouts —
bottomNavdefaults to"always"and is read fromconfig.jsonas well as the manifest. Readers move between pages without opening the menu; scope it down with"mobile"or"never".
- Deploy: Cloudflare edge-cache purge now uses the
CF_ZONE_ID/CF_CACHE_PURGEsecrets, so a docs deploy purges thepagenary.comedge cache and edits to existing pages/assets go live immediately instead of waiting out the 4-hour cache TTL.
- Page-effects primitives. Disclosure accordions (
.pe-accordion, native<details>, optional single-open), staggered reveal (data-reveal-stagger), generalized parallax on any element (data-pe-parallax), accessible figure zoom (.pe-figure[data-pe-zoom]→ focus-trapped<dialog>), scroll-snap sections (.pe-snap), and scrollytelling (.pe-scrolly, sticky stage + step observers). Living scroll now applies to any layout, not just blog. - On-this-page TOC. Opt-in heading nav generated from each page's
h2/h3with scroll-spy, prev/next section controls, a landscape rail that uses the gutter, and a portrait collapsible menu. Configurable viapageToc(rail/top/off,minHeadings). - Collapsible navigation. The sidebar nav and the on-this-page TOC both
collapse. New
navCollapseconfig selects the drawer behavior:overlay(default — a mobile-style drawer over full-width content),push, orinstant. - Showcase. A live, in-docbase demo section (one engine, many styles): a landing gallery (hero, staggered reveal grid, accordion, scroll-snap), a theming-palettes demo, a figure-zoom demo, and a scrollytelling story.
- Documentation restructured for publishing.
apps/publisher/docsis now the user-facing docbase; planning, spikes, and in-dev specs moved to the.aiwgSDLC workspace. Getting Started and Publishing are now published; Accessible Authoring promoted; nav reorganized (Welcome · Getting Started · Guides · Showcase · Reference).
- TOC rail no longer overlaps content or stretches the first heading's row; the Fortémi info icon stays clear of the rail; the overlay nav drawer is opaque.
- Print/export TOC no longer splits across a page break; scrollytelling no longer leaks raw HTML; the accordion open/close animates smoothly.
- Reliable smooth scrolling for TOC navigation (rAF-based, replacing the
silently-dropped native smooth
scrollToon the nested scroller); prev/next step deterministically via a navigation cursor.
- Blog layout family (
layout: "blog"). A second layout alongside docs: a chronological index of post cards and reading-first post pages with hero, byline, and tags, driven by the existing collections engine.blog.sidebarchooses a single centered column (hidden) or a posts rail (rail). - Blog post navigation. Every post ends with a persistent, accessible
control — previous post · back to index · next post — scoped to the collection
and visible on all screen sizes. Configurable via
postNav. - Living scroll for blog posts (
blog.livingScroll). Post content reveals as it enters the viewport, with a reading-progress bar. Opt-in, reduced-motion and JS-off safe (the full article is in the prerendered snapshot). - Page-effects toolkit. A runtime lifecycle that attaches/tears down effects
per render (gated on
prefers-reduced-motionandhtml.has-js); reveal-on-scroll and a reading-progress bar; and rich hero / banner primitives — full-bleed, overlay scrim, parallax, sticky, and a CTA band — authored by class/data-*, fenced HTML, or a declarative frontmatterhero/bannerblock. New Page Effects guide and apage-effectsrecipe. - Themed blog example gallery.
blog-dark,blog-editorial,blog-rail,blog-vivid, andblog-matrix— one set of posts, many looks. - Accessibility tooling. Narration artifacts, report artifacts, a content strict mode, a contrast/focus gate, browser smoke scans, and an authoring guide + planning baseline.
- Media accessibility metadata and configurable media renderers.
- Reading metadata model — realistic reading-length estimation from content.
- Configurable export (
export.enabled,export.scopes) so publishers can disable export or restrict it to single-page exports. - A
navAlignsidebar list alignment option.
- Export renders to the browser print / Save-as-PDF dialog from an off-screen frame — no pop-up window to allow or dismiss, and nothing left open afterward.
- Content-addressed runtime URLs are now the default. Tenant builds emit
deterministic hashed copies for browser-loaded runtime assets and rendered
section modules, rewrite the shell/manifests to those URLs, and keep stable
compatibility files on disk. Use
cacheStrategy: "stable"only for legacy hosts that require unversioned filenames. - Docs Map: force-directed layout + renderer controls and smoke coverage. The SVG-backed renderer gains search/focus, zoom/fit, drag-pan, neighbor highlighting, and node-detail popups, covered by the browser smoke script while preserving the no-React static-site runtime.
- The sidebar list is top-aligned by default.
- Export print table of contents no longer splits across pages — the front-matter (header + TOC) takes its own page and the TOC stays whole, while long content sections break naturally.
- The site title in the header is a home link (pointer cursor, routes to the default section) instead of inert text.
- Blog index cards behave as clickable tiles — the whole card is one link, no text caret over the body.
- The Quick Find palette stays above page-effects heroes.
- Theme-token config alignment; docsite registration for the page-effects spike page; Fortemi deep-link format/hook-count docs.
- Re-vendored
@fortemi/core2026.6.7 → 2026.6.8 and updated the exact devDependency/lockfile pin. The newaiwg-indexcontract adds relationship labels/confidence/privacy/metadata plus optional SKOS concepts/relations and PROV-style provenance events. Pagenary now emits those rich fields in generated Fortemi records, preserves them insearch-index/metadata.json, and surfaces them through the quiet per-page metadata panel. - Docs Map now consumes the Fortemi graph artifact directly. Build-time docs-map output includes the Fortemi weighted community graph plus compact node/relationship metadata, and the SVG renderer uses that data for edge weight, relationship confidence, shared-concept context, zoom/pan controls, neighbor highlighting, and node-inspection popups before navigation.
- Updated dev tooling to latest gate-eligible versions:
jest29.7 → 30.4.2,terser5.44 → 5.48.0. Both cleared the.npmrcmin-release-age=7gate (jest 30.4.2 published 40 days ago, terser 5.48.0 27 days ago — neither needed an override) and resolve fromregistry.npmjs.orgwith integrity. The jest 30 major reduced the dev-tree audit surface from 22 advisories (incl. 2 high) to 17 (all moderate, all still dev-only — no production/runtime exposure). The production build (terser-minified) and SEO smoke checks pass unchanged. - Fixed the search-index determinism test for jest 30's ESM loader. jest 30's
--experimental-vm-moduleschanged dynamic-import()behavior: the first import of a temp ESM section module is misclassified ("Unexpected export statement in CJS module") and degrades to title/summary, while a second import of the same path succeeds and extracts the body — a sandbox-only cold/warm asymmetry. The "byte-identical across runs" test now warms the module registry before its two measured runs, so it asserts the generator's determinism contract rather than jest's import cache. No product code changed; real-Node determinism is independently covered bybuild-tenants.test.js's spawned build. Full suite (303 tests) green. - Tracked
@fortemi/core2026.6.6 → 2026.6.7 (devDependency pin + lockfile). The 6.7 release'saiwg-indexdist is byte-for-byte identical to 6.6 — both the.jsand.d.ts— verified by SHA-256 (70cb729f…, unchanged) against the upstream CDN; the 6.7 release changed other parts of the package, not the surface Pagenary vendors. Sosrc/vendor/fortemi-aiwg-index.jsneeded only a bannerSourcebump (no body change), and the.d.tsis unchanged. Adopting the fresh (<7-day) version used the documented first-party gate exception in.npmrc(@fortemi/coreis our own Gitea-hosted lib); the lockfile resolves it fromregistry.npmjs.orgwith an integrity hash, andnpm auditshows no new advisories (the 22 pre-existing are all in the jest dev-tree). Provenance re-verified,lint:contentclean, full suite (303 tests) green.VENDORING.mdmanifest updated to2026.6.7.
- Traceable
@fortemi/coredeclaration. The vendored search engine is now declared indevDependencies, pinned to the exact vendored version (@fortemi/core2026.6.6), so the dependency is visible and auditable inpackage.json+ the lockfile (integrity hashes,npm audit signatures) and tracked bynpm outdated/ dependabot — not just recorded in a code comment. It stays out ofdependencies(the build never imports it; the vendored copy is self-contained). Adding it fresh required a documented first-party exception to themin-release-age=7gate, valid because@fortemi/coreis our own Gitea-hosted lib (Fortemi/fortemi-react); the policy is recorded in.npmrc. The addition introduces no new audit findings (the 22 pre-existing advisories are all in the jest dev-tree), andnpm ciinstalls the locked version without re-tripping the gate. docs/VENDORING.md— documents how third-party runtime code is vendored (the@fortemi/coreprocess: pull upstream dist → commit under a provenance banner withSource/SHA-256→ verify the hash against the upstream dist → run the suite), why (no-bundler, relative-path module loading), the first-party release-age-gate exception, and the remaining undeclared runtime CDN loads (PrismJS pinned, Mermaid floating on@10viaesm.sh). Links the canonical definition of vendoring.
- Re-vendored
@fortemi/core2026.6.3 → 2026.6.6 (src/vendor/fortemi-aiwg-index.js.d.ts). The only code change landed in 6.5 (6.4 and 6.6 ship identicalaiwg-indexdist — doc-only); it is purely additive: two new exports (encodeAiwgDetailId,aiwgDetailHrefForIdforbase64urldetail-id encoding) and a transparent LRU match-cache insidequeryAiwgFortemiIndexthat speeds repeated command-palette searches at no API cost. Every existing export is signature- and behavior-stable, so no Pagenary code changed; the match-cache benefit is gained automatically. Banner SHA-256 refreshed to the upstream 6.6 dist; all 303 tests pass against the re-vendored engine.
- Theming recipe gallery (#31). A copy-paste gallery of common
customization patterns — colors (custom palette,
dark,matrix), basic style changes (fonts + brand), and every navigation position — plus a fully-custom showcase tenant. All recipes share one small docs set inapps/publisher/examples/content-base/and differ only byconfig.json; the showcase ("Interocitor Labs") lives inapps/publisher/examples/interocitor/. Build them with the newnpm run build:examples(registry:examples/recipes.tenants.json). Documented indocs/THEMING-RECIPES.mdwith a Playwright screenshot per recipe — and is itself a page on the docs site (Guides → Theming Recipes). - Live recipe demos on the docs site (#31). The docsite build now also
builds every example recipe into
dist/pagenary/<id>/, so each is deployed and clickable athttps://docs.pagenary.com/<id>/(e.g./nav-top/,/interocitor/). The gallery links each live demo and lists real-world Pagenary sites (docs.aiwg.io, docs.fortemi.io, docs.roko.network). Wired into bothdocsite-build.yml(validation) anddocsite-deploy.yml. navPosition: top | bottom | hybrid(#31). Navigation can now render as a horizontal bar above (top) or below (bottom) the content, or as ahybridlayout — a horizontal primary strip (generated from the tenant's top-level sections) plus the full left rail. Layout rules are scoped to adata-nav-positionattribute set at build time;left(default) and the existingrightare unchanged. Now documented indocs/TENANT-CONFIG.md, which also gains the previously-undocumentedtheme/inkColor/mutedColor/gridLineColor/fontBody/fontMonokeys.- Docs Map relationship view (#33). Opt-in
docsMap.enabledadds a standalone, framework-free SVG view of how a tenant's pages relate. The build runs each page's full body through a concept-extraction procedure (the same Fortemi model that powers search): pages cluster into communities by nav group and pages that share salient concepts are linked withrelatededges, then the graph is embedded asdocs-map-data.jsand rendered client-side (sparse tenants fall back to a manifest-derived graph). The newexamples/docs-map-corpus/recipe is a 14-page interconnected "Lumen API" sample that produces 5 clusters and dozens of concept edges. - Runtime theme/color picker (#35). Opt-in
themePickeremits a per-theme stylesheet for each selectable theme and injects a<select>control that swaps the active stylesheet, persists the choice tolocalStorage, and honorsprefers-color-schemeon first load — no rebuild required. - GFM autolinks (#34). The markdown parser now linkifies angle-bracket
autolinks (
<https://…>,<mailto:…>) and bare URLs in prose, without double-wrapping existing[label](url)links or touching fenced code. - Bespoke Interocitor showcase (#36). The Interocitor Labs example now
demonstrates a fully-custom look that shares Pagenary's controls/data but
bears no resemblance to the defaults — built with a
.public/CSS overlay and post-buildoverrides/, the supported deep-customization path.
- Cloudflare edge-cache purge on deploy (#37). The docsite deploy now purges
the Cloudflare edge cache after publishing (guarded on
CLOUDFLARE_ZONE_ID+CLOUDFLARE_API_TOKEN), so edits to stable filenames are visible immediately instead of after the 4-hour TTL. Cache guidance indocs/DEPLOYMENT.mdcorrected.
- App-shell clipping on orientation change & short viewports. The mobile
layout used a static
calc(100vh - 120px)that overflowed the real header+footer height (chopping content and the footer when rotating between portrait and landscape). Replaced with the grid app-shell's natural1frsizing plus dynamic-viewport (dvh) units, and gave the scroll canvasalign-content: startso tall sections are no longer clipped to the container height. - Awkward line spacing from soft-wrapped markdown. Source lines within a
paragraph were each emitted as a separate
<p>, so the inter-paragraph grid gap appeared mid-sentence. Soft-wrapped lines are now merged into one paragraph; blank lines still separate paragraphs.
- Runtime page
<title>uses the tenant title, not the generic brand (#29). The legacy manifest build path (the one Pagenary uses) droppedsiteTitlefromSITE_CONFIG, sosrc/seo.jsrendered the runtime<title>as " · Documentation" even though the static shell title (#28) was correct.processTenantManifestLegacynow receivesconfigand setssiteTitle/siteUrl/ogImage(parity with the nested path). Verified in a real browser: "Welcome · Pagenary Docs". Caught only by a live check — static build tests passed throughout. - Tighter sidebar spacing (#30). Reduced the default nav gap (
0.75rem→0.15rem), item padding, and title↔summary gap so the left navigation reads denser and cleaner.
- Allowlist
examples/in the npm tarball audit so the package publishes. v2026.6.6 addedexamples/to the publishedfilesbut not toci/expected-tarball-top-level.txt, so the supply-chain top-level audit failed and the npm publish aborted (the docs site and Gitea release still shipped). v2026.6.7 is the first npm build to include the publishing examples.
- Publishing on-ramp for any docs repo. New
docs/PUBLISHING.mdplus copy-pasteexamples/(GitHub Pages + Gitea Pages workflows and a minimaltenants.json) make it easy to turn any folder of Markdown into a deployed Pagenary site — navigation and titles are auto-discovered, so no manifest is required even when the layout isn't tidy. Theexamples/directory now ships in the npm package, and both READMEs link the guide.
- Repositioned the docs and the pagenary.com landing around low-cost,
self-hosted documentation for individual developers and app makers: turn a git
repo of Markdown into a fast, searchable, SEO-ready docs site you host yourself
for next to nothing, with the features you'd expect from a paid docs platform.
Multi-tenant is demoted from the lead to a "scales to many sites when you need
it" note. Refreshed value props and use cases across both READMEs,
SEO-STRATEGY.md,QUICKSTART.md, and thepagenarytenant landing (config.jsonwelcome block +content/welcome.md).
- Shell
<title>now derives from the default page's metadata title (#28). The crawler-visible root<title>was the generic brand; it now uses the default section's metadata title as"<page title> · <brand>"(mirroring the runtime), falling back to the generic brand only when no default title exists — a stronger on-page SEO signal for the most-linked URL.
- Update the vendored
@fortemi/corestatic-index engine to 2026.6.3 (additive: addsbuildAiwgChunkedIndex/createAiwgFetchDetailLoader; the API Pagenary uses is unchanged).
- Shell asset URLs resolve correctly under both domain-root and subpath
serving. The shell used root-absolute
/styles.css//app.js//sections/(needed for production domain-root deploys with SPA fallback) which 404'd under a subpath mount such as the dev preview server's/<tenant>/. The shell now uses./-relative URLs plus a build-injected<base href>bootstrap that resolves to the tenant root —/<tenant>/under a subpath,/on a domain root (where./styles.cssequals the previous/styles.css, so production behaviour is unchanged). Module paths emit./sections/so dynamicimport()resolves againstapp.js's base-resolved URL. Also satisfies the SEO smoke stylesheet check.
- Publish the Fortemi search feature (v2026.6.1 publish failed). v2026.6.1's
npm publish aborted on a failing build test: an unreleased, in-progress
shell/module URL rework (tenant-relative
styles.css/./sections/) had leaked intobuild-tenants.jsand its build test while the matchingindex.htmlchange was excluded, conflicting with the shipped #22 root-based URLs. Revertedbuild-tenants.jsand the build test to the #22 root-based behavior so the release carries only the Fortemi search feature. v2026.6.2 is the first published build of that feature (see the 2026.6.1 notes below).
- Search now runs on the real
@fortemi/corestatic-index engine (ADR-015): the publisher's bespoke search reimplementation is replaced by the vendored@fortemi/coreaiwg-indexengine. Builds emit a deterministic chunked index per tenant underdist/<tenant>/search-index/; the command palette loads it via an index controller with lazy chunk fetch (precache), ranked results with snippets, and offset paging for infinite scroll. A legacy in-browser index remains as a transparent fallback — bundles stay statically hostable (no server, no WASM, no React).
- Build-time
scripts/lib/search-index-generator.jsand a shared, deterministic corpus buildersrc/lib/fortemi-corpus.js(records sorted/deduped by id, contentbuild_hash), both validated against the vendored@fortemi/corecontract. searchContentPage()paged search andbuildCommunityGraph()(Fortemi community graph) insrc/lib/search.js; command-palette infinite scroll inapp.js.@fortemi/coreandaiwgnpm badges in the root and publisher READMEs.- AIWG CalVer release process:
.aiwg/release.config(flow-release gates) andRELEASING.mdrunbook.
- Deep-linked nested routes hydrate correctly (#22): shell asset URLs and
generated section module imports now resolve from the site root, and nested
section IDs emit deterministic encoded module filenames. Cold loads such as
/blog/<slug>now request/styles.css,/app.js, and the matching section module instead of resolving relative to the nested route. - Collection post metadata is rendered and ordered (#23): auto-discovered
collection posts now use frontmatter title, summary, date, and reading time,
respect collection
sortBy/order, and honorshowDate,showSummary, andshowReadingTimeon post pages.
- Build pipeline documentation refreshed (#20/#21): API, architecture, and developer docs now cover the build-time
scripts/lib/*surface: SEO artifact generation, collection manifests/feeds, and frontmatter parsing. The architecture guide also shows the actual tenant build order through SEO and collections, and the developer guide now explains collection post authoring and SEO extension points.
- Page renderer leaked YAML frontmatter as visible text (#19): the
markdown render path (
markdownToHtmlinscripts/build-tenants.js) did not strip a leading----fenced frontmatter block, so collection posts — where #18 made frontmatter mandatory — rendered the YAML keys as<p>paragraphs above the first heading. Wired the existingparseFrontmatter()helper — already used byscripts/lib/collections-generator.js— into the renderer so every caller benefits. Added regression tests for the strip, the no-op-without-frontmatter case, and preservation of mid-document---horizontal rules in the body. - Nav disclosure arrow misaligned on Firefox: the left-nav group arrow
(
.nav-parent/.nav-parent-with-content) relied on CSS Grid auto-placement mixed with a column-spanning summary, which Firefox resolved differently from Chromium — bumping the arrow onto its own centered row instead of beside the group title. Pinned the title, arrow/toggle, and summary to explicit grid rows/columns so placement is deterministic cross-browser.
- Collection support (#18): mark a content folder as a collection via
config.collections(e.g. a blog) and the build emits a machine-readableindex.json—{ title, route, count, generated, posts[] }derived from each post's YAML front matter (title,date,summary,hero,tags,reading_time, absolutecanonical,path), sorted bysortBy/order. Optional RSSfeed.xml(feed: true). Lets downstream sites consume posts without scraping rendered HTML. Adds a minimal, zero-dependency front-matter parser (scripts/lib/frontmatter.js).
- Minification for installed consumers (#14):
terseris now anoptionalDependencyinstead of a clone-onlydevDependency. Consumers building via thepagenarybin previously gotTerser not installed; skipping minificationand shipped unminified section JS unless they addedterserthemselves; it now installs and minifies by default.
og:image/twitter:imagesupport (#16): newseo.ogImageconfig field (absolute or site-relative) plus per-sectionogImageoverride. When set, static snapshots and the runtime SPA emitog:image+twitter:imageand upgradetwitter:cardtosummary_large_image. Absent → unchanged.
- Absolute SEO URLs by default (#15): the SEO generator now resolves its
base URL as
seo.siteUrl→ tenantdomain(https-prefixed) → relative. Tenants that declare onlydomainnow emit valid absolute sitemap<loc>, canonical,og:url, and robotsSitemap:URLs — previously these fell back to relative/(invalid per the sitemap spec) unlessseo.siteUrlwas set explicitly. Builds now warn when neitherdomainnorseo.siteUrlis configured. - Self-canonical static snapshots (#17): static pages and the runtime SPA
now canonicalize to the crawlable static URL (
/pages/<id>.html) instead of the SPA#hashroute. Search engines ignore URL fragments, so the previous/#idcanonicals collapsed every page onto the homepage. The#hashroute is still used for the human-facing JS redirect and "interactive version" link. - Default site-title fallback changed from
Docs ToolkittoDocumentation.
- CLI from a consumer repo (#11):
pagenary build:tenantsresolvedbuild.jsand packaged assets against the caller's CWD, sonpx @pagenary/publisher build:tenantsfailed withMODULE_NOT_FOUNDwhen run from a consumer project. The bin now resolves bundled scripts/assets from the package directory while keeping tenantsource/target/registryrelative to the caller. strictLinksgate (#12):strictLinks: truelogged broken-link errors but the build still succeeded and exited 0, making the gate a no-op. Broken internal links now fail the tenant and exit non-zero so CI can gate on them.strictLinks: falseremains warn-and-continue.- External-tenant SEO (#13): building an external
type: localtenant surfacedsectionEntry is not definedand skipped SEO artifacts. Fixed via thestrictLinkspropagation above; clean and warn-mode builds now generatesitemap.xml,robots.txt, andllms.txtcorrectly.
- Initial public release of
@pagenary/publisher— a multi-tenant static documentation publisher (hash-based routing, per-tenant bundles, zero runtime dependencies). - CI-portable
pagenaryown-docs tenant — the dogfooded reference site. - Compiled site shipped inside the npm package under
site/viaprepack. pagenarybin CLI:build,build:tenants [id],tenants:list,serve.- Compiled site bundle attached to Gitea + GitHub releases (with checksums).
- Supply-chain split: internal Gitea registry (Gitea CI) and public npmjs.org with provenance (GitHub Actions).
- Repaired the search/export test suites (17 failures) — a
flattenManifestregression that dropped module-less sections, plus a stale export-branding test.