8.7 KiB
Project Architecture
Overview
Aholo Viewer is split into two main products:
@manycore/aholo-viewer: the TypeScript renderer package.@manycore/aholo-viewer-website: the Astro documentation and playground website.
The root package coordinates build, check, API generation, and packaging through pnpm workspace scripts.
Workspace Layout
./
package.json
pnpm-workspace.yaml
AGENTS.md
website/
package.json
astro.config.mjs
.generated/
api/
src/
components/
config/
content/
i18n/
layouts/
pages/
playground/
styles/
utils/
packages/
renderer/
package.json
tsconfig.json
src/
dist/
scripts/
build-package.mjs
check-content.mjs
clean-package.mjs
ensure-submodules.mjs
package-utils.mjs
prepare-egs-types.mjs
generate-api-docs.mjs
docs/
architecture.md
ai/
vibe-coding-guide.md
external/
egs-core/
splat-transform/
Structural Roles
- Root package: workspace entry point for dependency preparation, renderer build, API generation, website checks, and release packaging.
packages/renderer/: public renderer package source.src/index.tsis the public API barrel; package-local files hold math, events, animation, loader, and utility namespaces.website/: Astro application for home, manual, API reference, examples, and Playground. Route modules live underpages/; shared UI lives underlayouts/andcomponents/; route-independent data helpers live underutils/.website/src/playground/: route-local Playground runtime, Monaco integration, renderer adapter, camera control, and example-facing types.website/src/content/: manual pages, manual assets, and paired example metadata/source files.scripts/: shared Node automation for submodule readiness, EGS declarations, renderer packaging, API HTML generation, content validation, and clean tasks.external/egs-core: upstream submodule consumed by workspace packages and renderer packaging scripts.external/splat-transform: required workspace package used by the manual and examples workflow.
Dependency Direction
external/egs-core workspace packages
-> scripts/prepare-egs-types.mjs
-> renderer type checks and declaration bundling
packages/renderer/src/index.ts
-> scripts/build-package.mjs
-> packages/renderer/dist
-> website Playground runtime, examples, and type hints
packages/renderer/src/index.ts
-> scripts/generate-api-docs.mjs
-> website/.generated/api/
-> website pages
website/ may depend on @manycore/aholo-viewer through the workspace package. The renderer package should not depend on the website. Website dev, build, and check first run the renderer build, then regenerate API HTML and manifest data.
external/egs-core is an upstream dependency submodule. external/splat-transform is a required workspace package. Scripts may read external sources and generate dependency outputs needed for local builds, but repository changes should not hand-edit upstream code unless a task explicitly targets that package.
Root Command Graph
Root scripts keep the workspace build order explicit:
pnpm dev
-> .egs:types
-> .renderer:build
-> .docs:api
-> check:content
-> .site:dev
pnpm check
-> .egs:types
-> .renderer:check
-> .renderer:build
-> .docs:api
-> check:content
-> .site:check
pnpm build
-> build:website
-> .egs:types
-> .renderer:build
-> .docs:api
-> .site:build
Targeted commands preserve the same boundaries: check:renderer prepares EGS types and runs the renderer type check, check:website prepares renderer/API outputs before Astro checks, and docs:api prepares EGS types before generating API HTML and manifest data.
API Documentation Flow
- Export public API from
packages/renderer/src/index.ts. - Add concise JSDoc comments to public classes, functions, interfaces, and types.
- Run
pnpm docs:api. - The generated HTML fragments are written to the ignored local directory
website/.generated/api/{locale}/. - A generated manifest beside those fragments drives API navigation, metadata, and table-of-contents data.
- Astro API routes inline the TypeDoc HTML through the same DocsLayout flow used by the Manual.
Do not edit generated API HTML or manifest data by hand. pnpm dev, pnpm build, and pnpm check regenerate it before the website starts.
Playground Flow
Examples live in the Astro content tree as paired metadata and source files:
website/src/content/examples/
basic-scene.json
basic-scene.ts
The JSON file holds title, description, tags, accent, and order. The same-named TypeScript file is imported with ?raw, passed to Monaco, and rendered in the Playground preview. Playground URLs support:
example: selected preset slug.code:lz-stringcompressed editor source.
Resetting or switching presets clears custom code and returns to a clean example URL.
Playground type hints use packages/renderer/dist/index.d.ts through the workspace package. Do not add a second handwritten renderer declaration file under website/src/playground/.
Content Validation
pnpm check:content scans manual pages, localized slugs, heading-depth parity, example source pairs, manual image references, orphan manual images, and internal-only documentation links. pnpm check and pnpm check:website run it before Astro checks.
Website Style Layers
Website styles are split by responsibility so global changes stay small and feature surfaces remain easy to reason about:
website/src/styles/
theme.css Design tokens: color, type, radii, shadows, layout widths
global.css Reset, base document styles, buttons, code, simple primitives
site.css Header, language/theme controls, listing-page shell
home.css Immersive home page and home-only interaction states
examples.css Examples list and example detail viewer chrome
docs.css Manual/API documentation layout and prose
playground.css Playground workspace, editor, preview, inspector chrome
BaseLayout.astro imports theme.css, global.css, and site.css. Feature pages import their own feature stylesheet only when needed. Avoid moving feature-specific selectors back into global.css.
The home page intentionally keeps a darker immersive style around the 3D canvas. Docs, API, examples, and Playground should stay lighter, more restrained, and tool-like.
Build Outputs
- Website output:
website/dist/ - Astro cache:
website/.astro/ - Renderer output:
packages/renderer/dist/index.js: bundled public runtime.index.d.ts: bundled public declarations, including upstream EGS types used by exported symbols.splat-worker.js: bundled worker referenced by the runtime.
Root-level dist/ and .astro/ are stale and should not exist after the workspace split.
Ignored generated folders can be removed when a clean workspace is needed, but they should be produced by commands rather than hand-edited.
Architecture Improvement Opportunities
-
Keep API navigation metadata generated from the same source as API HTML.
scripts/generate-api-docs.mjsderives API namespaces, categories, entries, and TOC data frompackages/renderer/src/index.ts. Keep any future API navigation changes in that generated manifest flow to avoid drift when public namespaces change. -
Keep Playground integration behind the website adapter.
website/src/playground/renderer-adapter.tsis the boundary between Monaco-transpiled examples, preview lifecycle, camera control, runtime stats, and@manycore/aholo-viewer. New preview behavior should stay in the website adapter unless it is a reusable renderer capability that belongs inpackages/renderer/. -
Make external EGS consumption more declarative.
prepare-egs-types.mjsdiscovers and prepares EGS declarations, whilebuild-package.mjsdiscovers runtime packages for esbuild aliases. A small allow-list or manifest for consumed EGS packages would make renderer packaging easier to audit and less dependent on directory traversal. -
Treat generated output ownership as an architectural contract.
packages/renderer/dist/,website/.generated/api/, and temporary API cache folders are command-owned. Any new generated surface should also have an owning script, stale-file cleanup, and a validation path before it is referenced by website routes or package exports. -
Keep validation mapped to changed surfaces.
Current checks are well split between renderer, website, content, and package build flows. As the site grows, add new validation to the narrowest existing command first, then wire it into
checkonly when it protects a cross-area contract.