Skip to content

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 in packages/docs/public/_redirects.
  • Link untranslated locale navigation to canonical English pages rather than adding placeholders.
  • packages/docs/development/roadmap.md tracks product direction, Figma compatibility gaps, raw metadata coverage, and the code map. packages/docs/development/testing.md is the canonical testing architecture. Explanations that would bloat an AGENTS.md belong under packages/docs/development/.
  • Component API data loaders share one vue-component-meta checker through sharedChecker() 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/vue primitives, derive API tables from source and JSDoc with vue-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 from appSourceConfig() in @open-pencil/vite-config (vite/) and its design tokens from src/theme/tokens.css. CanvasKit and core's bundled fonts are Vite asset imports handed to core in landing/stage/engine-assets.ts; do not copy them into public/. Server-rendered sections must not import from landing/stage/; they reach it through the client-only ui/StageFrame.vue, whose imports are guarded by import.meta.env.SSR so the server build never compiles the editor.
  • The landing (.vitepress/theme/landing/**) is part of the root tsconfig.json, bun run lint, and bun 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.json overrides).
  • 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-canvas EditorCanvas there or mount and unmount stages on scroll (landing/stage/StageCanvas.vue).
  • The collaboration stage (landing/stage/collab/) joins two stages to one room through createLocalRoom(), an in-page CollabRoomTransport, and the app's own openRoomSession(); 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 of development/roadmap.md and changes with it.
  • The download page (download.md in each locale, landing/download/) shares the landing's look and catalog. landing/download/releases.data.ts reads the latest stable release and any newer beta from GitHub when the site builds, with GITHUB_TOKEN or GH_TOKEN when 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() in src/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 against LandingMessages, with en.ts as the source; change every catalog together. Each locale's index.md imports LandingPage and 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 through landing/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 by landingPosterFingerprint() in each landing page's posters frontmatter, and tools/generate/landing-posters captures them from the built site afterwards. Stills are named by landing/posters.ts; a stage reports ready and marks its canvas and panel with data-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.ts preload 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 in tools/checks/docs/tests/download.test.ts, and stills in tools/generate/landing-posters/tests. Keep such code free of vitepress imports so it loads under bun test (landing/content/messages/fill.ts).

Released under the MIT License.