Oro Computer's static website uses DOMStack to build marketing pages, learning chapters, and documentation for Runtime, Silk, Virtnosis, Sage, and slg.
Use Node 24. Builds, ingestion tools, audits, and tests run in TypeScript on Node; Python is not required. No sibling checkout is needed to build the site.
npm ci
npm startnpm start builds, serves, and watches for changes. npm run build creates
public/ from scratch. Generated HTML, search indexes, raw Markdown, and LLM packs
are output files; do not commit public/.
src/**/page.html: authored page fragments, with metadata inpage.vars.ts.src/**/page.md: public documentation, including YAML frontmatter.src/layouts/: shared page chrome, navigation, and progressive browser clients.registry.tsregisters actual layout exports with DOMStack's type-only registry. HTMLpage.vars.tscompanions useCheckedPageVarsfrom#lib/page-vars.tsto validate supplied metadata against their layout chain and global vars duringnpm run typecheck; this adds no runtime registry or subscriptions.src/globals/global.css: shared Oro styles, based ondocs/branding/. Homepage styles live insrc/style.css; learn styles insrc/layouts/learn.layout.css.src/lib/: rendering, URL, collection, and navigation helpers.src/globals/global.vars.ts: shared site configuration.src/globals/global.data.ts: a watch-session index keyed by DOMStacksourceId, caching validated document metadata, Markdown, and rendered search text. Navigation, search, and LLM-export views are derived from those cached entries.src/layouts/docs.layout.ts: the shared documentation renderer, consuming one lightweight navigation key for all collections.src/{product}/llms.txt.template.ts: product LLM packs; Silk includes its wiki.src/{product}/docs/search.json.template.ts: collection search indexes; the wiki usessrc/silk/wiki/.src/lib/docs-page-outputs.ts: sharedpageOutputshook exported by thedocsandspeclayouts. Each page owns its raw Markdown export at its existing collectionsource/URL, using itssourcePath.src/llms.txt.template.ts,sitemap.xml.template.ts,CNAME.template.ts, andnojekyll.template.ts: site-wide artifacts.
Templates live beside their output locations. Single-file templates return strings
and use their filenames without .template.ts as output names. The root
nojekyll.template.ts explicitly names its hidden output .nojekyll.
Raw Markdown uses DOMStack page outputs instead of collection templates: body
edits update only that page's raw copy, and watch rebuilds retain unchanged copies
without rewriting them. Deleted pages and changed raw paths clean up owned files.
Navigation is rendered into HTML from global data; the legacy navigation
index.json endpoints are no longer generated.
Consult docs/branding/ before changing visual design or copy tone. The approved
logo originals remain in docs/branding/assets/; their checked mirrors in src/docs/branding/assets/ retain
the same public URLs. Update both when replacing an asset. Handlebars is disabled globally so literal {{ ... }} code
examples remain intact. The Markdown parser deliberately keeps the existing
plugin policy: tables, strikethrough, HTML, linkification, GitHub alerts, syntax
highlighting, and legacy heading IDs. DOMStack's other default extensions are
not implicitly enabled; review fragment and rendered-output compatibility before
adding plugins.
Each documentation page declares description, docsCollection, section,
order, sourcePath, githubRepo, and githubRef. Its first Markdown H1 is the
default title; do not repeat it in frontmatter. Preserve
sourcePath: it defines the stable raw Markdown endpoint and import identity.
The start document lives at its collection root; the specification lives at
src/silk/spec/2026/page.md. Order is explicit and does not depend on filenames.
New imported pages are appended; review their section and order after syncing.
DOMStack infers the first H1 as inline Markdown. src/lib/titles.ts converts that
value to plain text for document/social metadata, navigation, search titles, and
LLM-pack headings, while the article keeps its formatted heading and anchor.
An optional frontmatter title overrides the inferred title; use it only for an
intentional difference (inline Markdown is reduced to plain text there too).
An H1 edit changes navigation metadata, while a body edit below it does not.
Imports infer titles from H1s, retain explicit overrides, and use a filename
fallback only for new documents without an H1. Raw Markdown remains unchanged.
Use layout: "docs" for documentation pages in every collection (spec for the
specification). All docs share one lightweight navigation dependency: navigation
changes rebuild all docs, while body-only edits remain isolated to the changed
article, its raw output, and its collection's search and LLM exports. Search and
LLM artifacts retain separate, per-collection dependencies; raw outputs need none.
Use canonical directory links such as /runtime/docs/guides/hello-world/.
Collection roots redirect legacy ?p= links while preserving fragments. Static
redirect pages retain Silk's old logger-guide and specification aliases. The
articles, sidebar, previous/next navigation, and ToC work without JavaScript;
search, tabs, copy controls, and Ask AI progressively enhance them.
DOMStack supplies private previousState, reset/delta changes, and
setState() to the data callback. Initial builds and resets process all documents;
watch deltas replace only changes.upserted entries and delete changes.removed
IDs. Pages leaving a docs collection retain only their route/redirect metadata.
The cache contains plain data, never page instances or renderers, and DOMStack
commits its snapshot only after a successful build. It does not persist across
watch sessions or change the clean-build/ingestion workflow.
Collection views are still rebuilt from cached document references, and existing
DOMStack fingerprints plus dataDeps remain the only downstream invalidation
system. There are no extra application hashes or changed-key declarations.
DOMStack also caches Markdown source preparation between watch builds, avoiding
repeated reads and H1 parsing for unchanged documents. Projection, state cloning,
and fingerprinting still have collection-wide costs. The standalone watcher test
measures the data callback's document reads/renders directly.
Dependency tracking has known limitations for package import aliases and static
re-exports (see DOMStack #328). Restart the watcher after changing shared helpers
reached through #lib/* or re-exports, or inputs read outside tracked imports.
Editing global.data.ts itself resets the index; ordinary page edits are tracked.
Follow the DOMStack redirect-pages recipe: keep old paths on the destination page, rather than in a separate route map. For Markdown, add frontmatter:
redirectFrom:
- /old-guide/
- /old-guide.htmlHTML pages can declare the same array in their page.vars.ts object. Global data
validates and collects these aliases; src/redirects.pages.ts subscribes only to
redirects and renders each old URL through the existing redirect layout. The
target comes from the destination page's actual URL, so retained aliases follow
it when it moves. Removing an alias or destination removes its generated redirect
in watch mode. Body edits do not change the redirect collection.
Use same-origin paths beginning with /, with trailing / for directory URLs.
Queries, fragments, unsafe paths, duplicate aliases, and aliases colliding with
source pages are rejected. Choose paths that do not overlap assets or other
generated outputs. Redirect pages have a canonical target and a no-JavaScript
meta-refresh/fallback link; with JavaScript they preserve the incoming query and
fragment. These are static client-side redirects, not HTTP 301 responses.
Legacy ?p= document IDs still use the separate client resolver; do not put them
in redirectFrom. Manual content imports preserve existing page metadata,
including aliases. The logger guide and specification now declare their old
paths this way without changing their Markdown bodies or raw exports.
The blog at /blog/ lists posts newest first. Start and publish posts with:
npm run import-author -- bcomnes
npm run import-author -- jwerle
npm run new-blogpost -- --author bcomnes --author jwerle "Post title"
npm start -- --drafts
npm run publish-draft -- post-title
# For a draft from an earlier year:
npm run publish-draft -- 2025/post-titleThe create command scaffolds src/blog/<current-year>/<slug>/page.draft.md and
an img/ directory. Replace the draft's summary and write the article before
publishing. The publish command sets publishDate to now and installs page.md
without overwriting an existing post, then removes the draft. It preserves the
article body and metadata values, but normalizes YAML formatting/comments.
Blog posts require a title in frontmatter. The blog layout renders the H1,
followed by the publication date and authors with linked avatars. Do not repeat the title as a Markdown
H1; start body sections at H2. This differs from documentation pages, which keep
their title in Markdown.
You can also author Markdown posts and local images directly under
src/blog/<year>/<slug>/, using page.md for a published post:
---
layout: blog
title: "Your post title"
description: "A short summary for the index and feeds."
publishDate: "2026-09-18T12:00:00Z"
authors:
- bcomnes
- jwerle
---
Write the post here. Link local images with `./image.png`.Posts require a nonempty authors array of registered lowercase GitHub usernames.
The local registry in src/authors/ includes bcomnes, jwerle, and oro-computer.
Repeat --author username to create a multi-author draft; only the create command
defaults to ['oro-computer'] when no authors are specified. Missing or empty
frontmatter arrays, duplicate usernames, unknown usernames, and legacy aliases
are rejected. Author order is preserved in bylines and both feeds. Display names,
profile links, and local avatars come from the registry; JSON Feed includes
absolute avatar URLs and Atom includes each author's name and profile URI.
Use npm run import-author -- username to import or refresh a profile, then review
and commit its author-meta.json and avatar under src/authors/<username>/.
Importing contacts GitHub and its avatar service and is subject to GitHub rate
limits; an optional GITHUB_TOKEN may be supplied through the environment.
Builds and post creation read only the local registry and never fetch author data.
Restart npm start after importing authors or editing registry metadata: DOMStack
tracks static imports, not the registry's filesystem reads.
See tools/authors/README.md for importer details.
updatedDate is optional and must not precede publishDate. Quote date values
and include a timezone. Publication dates control ordering, not scheduling:
future-dated published files are still public.
Use page.draft.md while writing and preview with npm start -- --drafts.
Normal builds omit draft pages. Drafts appear in the preview index but never in
feeds; files and images in a draft directory are not private, so do not commit
sensitive material. Rename to page.md to publish without resetting its date, or
use the publish command to set it to now. The initial published post is
src/blog/2026/hello-world/page.md.
/feed.json (JSON Feed 1.1) and /feed.xml (Atom) contain the latest 20 published
posts with full HTML, stable URL-based IDs, authors, and update dates. Feed links
and image sources are absolute. Every HTML page advertises both feeds. Empty
feeds are valid and deterministic; Atom uses the Unix epoch until a post exists.
Both formats share one feed-data builder. Atom is generated by jsonfeed-to-atom;
a small compatibility adapter preserves JSON Feed 1.1 author arrays, the existing
feed ID, deterministic dates, and exact HTML content with its 1.0-only API.
Blog data shares the incremental global-data index. Body edits refresh the post
and feeds; the index subscribes only to summaries, and docs navigation is
independent. Posts use layout: blog; the listing uses blog-index. Both inherit
root, keeping site chrome shared. src/blog/archives.pages.ts generates each
/blog/<year>/ archive from the posts in that year directory, with no hand-authored
index needed. Archives link from the main index and post breadcrumbs and appear
in the sitemap. Publishing an older-year draft keeps it in its original year
archive even when its publication date is newer. Removing the last post from a
year removes that archive during watch builds.
Every page has an Edit this page footer link to its source in GitHub, matching the repository browser/edit workflow. Generated redirect pages link to the source page that declares their alias, rather than a nonexistent output file.
Ingestion is separate from building and CI. These commands read explicit upstream checkouts, preserve the established curated/website-owned content, and write committed DOMStack Markdown pages. Review and commit their changes before deploying.
node silk/tools/sync-from-silk-docs.ts --silk-repo /path/to/silk
node runtime/tools/generate-js-api-reference.ts --runtime-repo /path/to/runtime
npm run build
npm run audit
npm run audit:contentThe defaults use adjacent silk and runtime directories, independent of the
website checkout's name. Silk's legacy --repo-root workspace option is retained.
The tools stage flat Markdown temporarily, apply the existing ownership and
pruning rules, then import it through tools/import-public.ts. Shared normalization
and reference-linking helpers live in tools/ingestion/. Public-copy normalization
runs during ingestion, never during rendering; fenced examples
are preserved. Unchanged staged pages keep their exact Markdown and metadata;
linked API headings become plain-text titles when a page changes. The reference
catalog includes new pages in the same import batch. Runtime's generated
reference markers retain surrounding prose.
The generators call the TypeScript importer directly; they never run during a
DOMStack build. Search indexes and LLM exports are DOMStack templates, not ingestion
outputs. Content audits derive document IDs and expected raw filenames from committed
Markdown frontmatter, then inspect the generated files in public/ by default.
They do not depend on public navigation JSON. Set ORO_SITE_OUTPUT to inspect
another output directory. Set ORO_RUNTIME_REPO explicitly to additionally audit
against a particular upstream Runtime checkout; ordinary checks are independent
of whatever happens to be checked out next door.
Install the browser once, then run the same complete gate used by both CI and Pages deployment:
npx playwright install chromium
npm run checkIndividual checks are also available:
npm run typecheck
npm test
npm run build
npm run audit
npm run audit:content
npm run test:site
npx playwright install chromium
npm run test:browser
npm run test:tooling
npm run test:reproducibilityThe crawler checks every generated internal link and fragment. Site tests cover
all original 27 HTML routes and 585 documentation routes, raw-source parity,
search text, and LLM packs. Browser tests cover desktop/mobile layouts,
no-JavaScript rendering, compatibility redirects, search, tabs, copy controls,
Ask AI, specification heading search, and fragments inside tabs. Tooling tests
verify all current documents survive unchanged imports and that a renamed standalone
checkout rebuilds articles and exports during development. To use an existing Chromium install,
set ORO_BROWSER_EXECUTABLE to its executable path.
tests/fixtures/legacy-routes.json records the original HTML and raw-document
endpoints as a fixed compatibility baseline. Keep it independent of the current
source inventory so deleting a page cannot silently erase its compatibility check.
When intentionally retiring routes, update the relevant assertions and redirects
together. The one-time converter and migration report remain available in Git history.
Pull requests run the Docs Audit workflow. Production-branch pushes run the
Pages workflow, which validates once, uploads that checked public/ artifact,
and deploys with the Pages environment. Both workflows use Node only and retain
browser failure artifacts.
The repository's Settings → Pages → Source must be GitHub Actions. Merging
these changes does not itself change that repository setting. The output contains
CNAME, .nojekyll, branding assets, raw Markdown, and llms.txt packs.
DOMStack is pinned to an exact beta version in package.json and the committed
lockfile; CI uses npm ci for reproducible installs. To refresh to the current
beta, run npm install --save-dev --save-exact @domstack/static@beta, run the
complete validation sequence, and compare representative desktop and mobile
screenshots. Commit both package.json and package-lock.json.