Docs site
Published VitePress site. bun run docs:dev, bun run docs:build for fast checks, and bun run docs:build:production for deployment output. bun run check:docs runs the integrity checks in tools/checks/docs/ plus check:authoring-reference.
- Keep routes under
/getting-started,/overview/**,/user-guide/**,/programmable/**,/reference/**, and/development/**, plus the top-level/download; do not recreate/guide/**. Preserve moves inpackages/docs/public/_redirects. - Link untranslated locale navigation to canonical English pages rather than adding placeholders.
packages/docs/development/roadmap.mdtracks product direction, Figma compatibility gaps, raw metadata coverage, and the code map.packages/docs/development/testing.mdis the canonical testing architecture. Explanations that would bloat anAGENTS.mdbelong underpackages/docs/development/.- Component API data loaders share one
vue-component-metachecker throughsharedChecker()in.vitepress/sdk/component-meta.ts; VitePress evaluates each loader separately, so a checker per module meant one TypeScript program per page and about 7 GB of heap. - SDK pages embed the demos colocated with
packages/vueprimitives, derive API tables from source and JSDoc withvue-component-meta, and process examples with Twoslash so imports and types stay aligned with the public package API. The docs Tailwind entry scans those demos. - Generated authoring-reference copies under this site are checked, not edited; run
bun run generate:authoring-reference(packages/core/AGENTS.md, Tools). - CI runs documentation integrity and the docs build for docs-only changes, not engine, browser, Storybook, or native suites (
tools/AGENTS.md).
Landing
- The landing page (
.vitepress/theme/landing/) mounts the app's own components, so the site takes the app's aliases, icon plugins, and build constants fromappSourceConfig()in@open-pencil/vite-config(vite/) and its design tokens fromsrc/theme/tokens.css. CanvasKit and core's bundled fonts are Vite asset imports handed to core inlanding/stage/engine-assets.ts; do not copy them intopublic/. Server-rendered sections must not import fromlanding/stage/; they reach it through the client-onlyui/StageFrame.vue, whose imports are guarded byimport.meta.env.SSRso the server build never compiles the editor. - The landing (
.vitepress/theme/landing/**) is part of the roottsconfig.json,bun run lint, andbun run format, so it is checked with the app; its#docs/*imports resolve there to.vitepress/*, and it alone may import the app through@/(oxlint.jsonoverrides). - Each landing stage is one document pinned with
provideTabEditorStore()and one WebGL canvas that mounts once and is never unmounted. Browsers cap WebGL contexts per page, so do not use the app's two-canvasEditorCanvasthere or mount and unmount stages on scroll (landing/stage/StageCanvas.vue). - The collaboration stage (
landing/stage/collab/) joins two stages to one room throughcreateLocalRoom(), an in-pageCollabRoomTransport, and the app's ownopenRoomSession(); it never reaches the network or IndexedDB. It is the only stage with two canvases, so the page holds one more WebGL context than it has stages. - Counts shown on the landing page come from source at build time through
landing/content/stats.data.ts; do not hardcode them. Its roadmap is the short view ofdevelopment/roadmap.mdand changes with it. - The download page (
download.mdin each locale,landing/download/) shares the landing's look and catalog.landing/download/releases.data.tsreads the latest stable release and any newer beta from GitHub when the site builds, withGITHUB_TOKENorGH_TOKENwhen set, so the page stays static; a build that cannot reach GitHub links to the releases there instead. - The 4B Pen Cloud waitlist forms (Cloud section and roadmap entry) sign up through the app's
useWaitlistSignup()insrc/app/cloud/waitlist/signup.ts, the flow Settings uses: the typed feedback client, Turnstile, and the same checks as the service. Turnstile runs only on the sites the feedback service lists, so dev servers and previews show the form's unavailable state. - Landing copy lives in
landing/content/messages/, one catalog per docs locale typed againstLandingMessages, withen.tsas the source; change every catalog together. Each locale'sindex.mdimportsLandingPageand only its own catalog, so the theme never registers the landing and other docs pages download none of it. The embedded app follows the docs appearance switch and locale throughlanding/stage/useDocsAppearance.ts. - Production builds (
build:production,OPENPENCIL_LANDING_POSTERS=1) show WebP stills of each stage until it is ready: the config names them bylandingPosterFingerprint()in each landing page'spostersfrontmatter, andtools/generate/landing-posterscaptures them from the built site afterwards. Stills are named bylanding/posters.ts; a stage reportsreadyand marks its canvas and panel withdata-poster-part, and capture mode (?poster-capture) must start stages at once and keep them still (no autoplay). Development builds have no stills. - Landing pages set
landing: true, which makes.vitepress/landing-head.tspreload CanvasKit and the scenes' fonts for fine pointers; on touch screens a stage starts only when tapped. Stage panels, the toolbar, and the preview controls are async components (landing/stage/definitions.ts), so a stage downloads only what it shows; keep new stage-specific code behind the same kind of import. - Landing logic is tested where its counterpart lives: the download page against the release catalog in
tools/release/release-packages/tests/native/download-page.test.ts, its pure helpers intools/checks/docs/tests/download.test.ts, and stills intools/generate/landing-posters/tests. Keep such code free ofvitepressimports so it loads underbun test(landing/content/messages/fill.ts).