Satellite orbit visualization: a Vue 3 + Vite + CesiumJS + Nuxt UI single-page app
in src/, with a Cloudflare Worker backend in worker/ (the satvis-worker
workspace package) and a native iOS app in ios/.
CONTEXT.md— the domain glossary. Use its names in code and discussion, and sharpen an entry when a term drifts.README.md— before setting up, read its "Setup"; before running the app against a local worker, "Full-stack dev"; before deploying, "Deploy"; before changing how the data arrives, "Satellite data".docs/adr/— decisions and the alternatives they beat, one file per topic. Read the one on an area before changing how it works.e2e/— Playwright specs against the running app:journeys/walks the main flows through the menus,regressions/pins down one past bug each, for what needs layout, a GPU or frames. Reade2e/support/before writing one: the fixtures, the app and menu helpers, and why they count clock ticks (app.ts).worker/src/gp/types.ts— the group, preset and satellite-table config schema, field by field. Read it before editing the core config,worker/src/config/satvis.core.yaml, or a plugin'sdata/custom/*/satvis.yaml.src/modules/benchmark/README.md— read it before measuring what a frame costs: the benchmark framework, how the frame cost scales, and its traps.ios/AGENTS.md— read it before changing, building or testing anything underios/.
- Per-satellite metadata is attached to records at refresh time. There is no
metadata endpoint and no rule matching in the browser: a record either carries
the bag or the frontend applies its defaults (
src/config/satelliteMetadata.ts). - A satellite's 3D model is the
modelFileits model manifest gives it (data/models/models.yaml,data/custom/*/models.yaml), by NORAD id or by GCAT bus, never its name. Records carry it asmodel, with its lower-detail copies, and asmodelFilefor apps released beforemodel.
package.json holds the scripts. What it does not say:
pnpm lintcovers both packages, butpnpm testcovers only the frontend — the worker suite ispnpm --filter satvis-worker test, which also startswrangler devto check the asset routing (worker/scripts/check-routes.mjs).pnpm test:buildchecksdist/sw.jsafter a build.pnpm update-parity-fixturesreruns the web code the native app is held to (scripts/parity/). Rerun it after any change undersrc/modules/,src/config/orsrc/stores/, which is all it reads; CI fails while the committed output is stale.- The full e2e suite takes 10+ minutes even on the GPU, so run it only when
asked. Otherwise run the specs a change touches:
pnpm test:e2e:gpu <file>, about four times faster on a Mac thanpnpm test:e2e's SwiftShader. - Playwright reuses any server already on port 5199, including one another
worktree's run left behind, and then tests that checkout's code. Check with
lsof -iTCP:5199 -sTCP:LISTEN, and setE2E_PORTto a free port if it is taken. - A fresh
git worktreehas no submodules. Rungit submodule update --init, ordata/modelsstays empty andgenerate-groupswarns that no satellite gets a public model; restart a dev server started before the init, which keeps answering.glbrequests withindex.html.
- Comments say only what the code cannot: a constraint, a library quirk, a unit, a
measured number, a rejected alternative. Judge density by
src/as it stood before 2025 (commitf0005ea5: about 6% comment lines), not by today's files, which run about 15%. - A doc on a declaration is
/** */, exported or private: module-level declarations, class and interface members (#privateones too), entries of module-level objects, every function, and what a store or composable returns. Everything inside a function body is//, as are notes on a group of declarations and file headers (the split of Google's TypeScript guide: readers of the code versus its internals). Prose only, no JSDoc tags. - Keep the private plugins in
data/custom/out of commits, their satellites, names and models included:.gitignorekeeps their files out, not what is copied from them into tracked ones.
- Probes read the answer, not the status.
pnpm devanswers a missing file withindex.htmland a success status, ranged or not, soresponse.okalone passes exactly where a probe should fail. Use adefinewhere the answer is known at build time (__IMAGERY_MAX_LEVEL__invite.config.ts); otherwise copy the probe insrc/config/starMaps.ts. - The committed Cesium ion token works only on satvis.space
(
src/config/ion.ts). Terrain and the surface models on localhost need an unrestrictedVITE_CESIUM_ION_TOKENin the gitignored.env.development, which a freshgit worktreelacks: copy the main checkout's. - Dev and preview are cross-origin isolated; production is not (COOP/COEP in
vite.config.ts). Symptom to recognise: a black globe with the satellites still drawing in a visible tab means the isolated page refused a worker script the service worker cached without a COEP header — clear that origin's service worker. A hidden tab, such as an agent's browser pane, gets no frames, so what it shows proves nothing. More insrc/modules/benchmark/README.md, "Cross-origin isolation" and "Things that will bite you". - Everything under
data/ships, exceptdata/custom/(only its synceddist/) and the models repo (onlydata/models/public/). That is why the generators live underscripts/.