# wanessalabs-astro - Full Ingestion Context > The Wanessa Labs front door at **[wanessalabs.com](https://wanessalabs.com)**. Static Astro site, no backend, deployed to Cloudflare Pages. --- ## 1. Project Overview & Architecture # wanessalabs-astro The Wanessa Labs front door at **[wanessalabs.com](https://wanessalabs.com)**. Static Astro site, no backend, deployed to Cloudflare Pages. Canonical project docs: `prd.md` (why), `lineage.md` (decisions), `roadmap.md` (what's next). Brand and tokens: `design/brand.md` + `design/tokens.css`. **Read those before any UI work. Never hardcode a colour or a size.** ## Commands | Command | Action | |---|---| | `npm install` | Install dependencies | | `npm run dev` | Dev server on `localhost:4321` | | `npm run build` | Build to `./dist/` | | `npm run preview` | Serve the built output locally | | `npx astro check` | Typecheck (expect 0 errors) | | `npm run timeline` | Regenerate `src/data/projects-timeline.json` from vault lineage files. Manual, never part of the build. See `scripts/README-timeline.md` | | `npm run test:timeline` | Parser + redaction tests for the timeline exporter | ## Deploy Cloudflare Pages project **`wanessalabs-com`**, `pages_build_output_dir = "dist"`. **Pushing `main` already deploys this site.** The project is Git-connected: run `wrangler pages deployment list` and the Production rows carry a commit SHA in the Source column. Confirmed 2026-08-19, when a push built and went live on its own and a manual upload afterwards was redundant. Reach for the manual command below only to ship content that is not committed, or to redeploy without a new commit. A manual upload lands as a Production deployment with an empty Source, which is the tell that it did not come from a build. ```sh npx wrangler pages deploy dist --project-name=wanessalabs-com --branch=main ``` **Wrangler prefers the git branch it detects over the `--branch` flag.** Run the command above from inside this repo on a feature branch and it lands in **Preview** despite `--branch=main`, printing a "Deployment alias URL" line when it does. The operative variable is git *detection*, not which branch you are on. Copy `dist` somewhere outside any git repo and deploy from there, and `--branch=main` is honoured: ```sh cp -r dist "$SCRATCH/deploy" && cd "$SCRATCH/deploy" npx wrangler pages deploy . --project-name=wanessalabs-com --branch=main ``` Verified 2026-07-29: Production `5e3bf4d2` shipped this way while the working tree sat on `feat/13-cards-docs`. This matters because merging to `main` is the owner's call, so a deploy must never require it. Always confirm the environment rather than trusting the success message: ```sh npx wrangler pages deployment list --project-name=wanessalabs-com ``` ## Structure ``` src/ ├── components/ Astro components │ ├── ProjectMindMap.astro /projects map: canvas mount + DOM label layer + accordion │ ├── ProjectGlyph.astro 51 hand-authored 24x24 line marks, keyed by slug │ ├── ProjectMark.astro illustrated artwork on detail pages, glyph fallback │ └── ProjectVisual.astro per-project hero illustrations ├── content/ │ └── projects/ One markdown file per project. Frontmatter drives the catalog. ├── data/ │ ├── projects-timeline.json Committed, generated. Do not hand-edit. │ └── timeline-manual.json Hand-curated history from extracted repos. ├── layouts/BaseLayout.astro head, SEO, OG/Twitter meta, fonts ├── pages/ File-based routes ├── scripts/mindmap3d.ts 3D force sim + three.js scene + label projector └── styles/global.css Design tokens and shared component CSS scripts/ Node, run by hand. Not part of the build. ├── projects-timeline-export.mjs Allowlist -> parse -> redact -> committed JSON ├── timeline-allowlist.json The publish allowlist. Fail-closed. ├── lib/ Parser and redaction rules └── README-timeline.md How to refresh it, and what the rules are ``` Content is markdown in `src/content/`. Adding a project means adding a file; nothing else needs editing. ## The projects map `/projects` renders every project as a 3D force-directed graph. The architecture is deliberate and worth understanding before changing it: - **three.js draws geometry only** (bubbles and 1px edges). No text is ever rendered into the canvas. - **Every label stays a real DOM ``/`