Repository structure
Every top-level file and directory in the production repository, what it is for, whether it ships, and which ones must never be edited directly.
Last updated 2026-08-06
Summary#
The production repository is flat by design. The worker, the customer app, the marketing pages and the admin console all sit at the root; supporting assets live in a small number of directories. Nothing is generated at build time except the staged dist/ copy, so what you see in the tree is very close to what is served.
This page enumerates the whole tree. Use it to answer three questions: what is this file for, does it ship, and is it safe to edit directly.
Overview#
Four rules explain most of the layout.
Root means "shipped as a page or a top-level asset". The deploy workflow stages every top-level *.html plus a named list of root files. If a file is at the root and is not in that list and is not HTML, it does not ship.
Directories ship whole or not at all. The workflow copies icons free-tools reports legal js css images recursively. Any other directory (partials/, i18n/, tests/, docs/, cron-worker/, chrome-extension/) is a source or tooling directory and never reaches production.
Three files are generated or mirrored and must not be hand-edited in their destination. LOGIN_HTML inside _worker.js mirrors login.html. The navbar and footer inside 21 public pages mirror partials/. js/mv-i18n-dict.<lang>.js is emitted by the translation generator. Editing the destination is silently overwritten by the next sync.
dist/ is a lie locally. It is git-ignored, rebuilt by CI on every deploy, and your local copy is whatever you last put there. It currently holds files CI would never ship.
Root files#
| File | Size | Ships? | What it is |
|---|---|---|---|
_worker.js | 1,710,142 B / 28,268 lines | Yes | The entire backend. Router, every /api handler, the inlined login page, the schema creators, the cron jobs. See The worker (_worker.js) |
_headers | small | Yes | Cache-Control overrides. no-cache, no-store, must-revalidate for /login.html, /login, /dashboard.html, /dashboard, /sw.js, /index.html, /. The first four also get Pragma: no-cache and Expires: 0 |
_routes.json | small | Yes | Which paths invoke the worker. include: ["/*"] with 15 excluded paths that are served as pure static assets |
.assetsignore | 196 B | No | Root copy is inert for the CI deploy: the workflow writes an empty dist/.assetsignore instead, and wrangler reads the one in the output directory |
wrangler.toml | small | No (config) | Pages-format config: name, pages_build_output_dir = "dist", compatibility_date, and the D1/KV/R2/AI bindings. Never port this file between repositories |
dashboard.html | 7,223,279 B / 64,435 lines | Yes | The customer application. CRLF line endings. Owned by branch main |
admin.html | 302,928 B | Yes | The operator console. English by design, excluded from the i18n pipeline |
login.html | 75,840 B | Yes, but not served | The sign-in page. _worker.js intercepts /login and /login.html and returns the inlined LOGIN_HTML copy instead |
index.html | 242,812 B | Yes | Marketing homepage. Owned by branch redesign |
pricing.html | 210,307 B | Yes | Pricing page and the plan feature lists |
blog.html, blog-post.html | ~40 KB each | Yes | Public blog shell and post template |
legal.html | 54,548 B | Yes | Legal hub, CRLF line endings |
404.html | 5,961 B | Yes | Deliberately chrome-less. Served by the worker with a real 404 status |
ext-preview.html | 41,842 B | Yes | Internal extension preview page |
manifest.json | 644 B | Yes | PWA manifest. Excluded from the worker by _routes.json |
sw.js | 3,155 B | Yes | Service worker. CACHE_NAME is bumped to evict stale client caches after a deploy |
robots.txt, sitemap.xml | small | Yes | Also emitted inline by the worker at /robots.txt and /sitemap.xml |
chat-widget.js | 79,671 B | Yes | The Metric Vault Assistant. One engine, three surfaces. Loaded by index, pricing and dashboard. Shared file, in the design-lint targets |
pwa-install.js | 13,826 B | Yes | Install banner |
blog-embed.js | 14,835 B | Yes | Third-party embed script for customer blogs |
chatbot.js | 584 B | Yes | A deliberate no-op stub. Pages auto-injects a <script src="/chatbot.js"> tag; shipping a valid empty file stops that resolving to an HTML 404 body |
admin-supabase-setup.sql, admin-supabase-full-setup.sql | - | No | Manual Supabase schema, run by hand in the SQL editor. Nothing applies them automatically |
CLAUDE.md | - | No | Project instructions loaded by AI sessions |
README.md | - | No | Repository readme |
metricvault-extension*.zip | 14-45 KB each | No | Chrome Web Store build archives, produced by the zip command in chrome-extension/README.md. -v2.1.1 matches the current manifest.json version and is the artifact that was uploaded; the unversioned copy is byte-identical to it. Not staged |
Icons and images at the root that do ship: favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png, icon-192.png, icon-192.svg, icon-512.png, icon-512.svg, logo-dark.png, logo-light.png, og-image.png.
Directories#
| Directory | Ships? | Contents |
|---|---|---|
css/ | Yes | 24 stylesheets plus a README. tokens.css is the only place tokens may be defined; legacy-mvx.css (1.05 MB, 12,046 lines) is frozen and may only shrink |
js/ | Yes | 24 classic scripts plus vendor/supabase-2.38.0.umd.js. No modules, no bundling, no defer by convention |
free-tools/ | Yes | index.html plus 11 public tool pages |
legal/ | Yes | privacy.html, terms.html, gdpr.html, security.html |
reports/ | Yes | 9 static sample reports used by marketing |
icons/, images/ | Yes | Sidebar icons and platform imagery |
partials/ | No | site-nav.html, site-footer.html and extras/. Source for the centralized chrome, stamped into pages by tests/site-chrome-sync.mjs |
i18n/ | No | strings.en.json and dict.{es,fr,pt,de,ru}.json. Pipeline inputs; the shipped dictionaries are js/mv-i18n-dict.<lang>.js, one per language |
tests/ | No | 19 Node verification scripts plus design-lint-baseline.json and a README. See Verification scripts |
docs/ | No | DESIGN-CONSTITUTION.md, handoffs, guides, and docs/kb/ (this knowledge base) |
cron-worker/ | No | A separate Cloudflare Worker with its own wrangler.toml and its own deploy |
chrome-extension/ | No | Extension source |
dist/ | git-ignored | The staged copy CI assembles. Locally it is whatever you last copied there |
.wrangler/ | git-ignored | Local wrangler state: state/v3/{cache,d1,kv,r2,workflows} |
.github/ | No | workflows/deploy.yml and workflows/cron.yml. Never port deploy.yml between repositories |
css/ in full#
| File | Bytes | Scope |
|---|---|---|
tokens.css | 4,051 | The single source of truth for color, spacing, radius, shadow and type tokens. Light :root, dark under [data-theme="dark"] |
app.css | 105,939 | Dashboard chrome: the 3-column shell, rail, sidebar, topbar, cards. Re-declares :root with the dark palette and puts light behind [data-theme="light"] |
legacy-mvx.css | 1,050,377 | The frozen .mvx stylesheet. Holds the 11 forked token definitions the ratchet tracks |
mvr.css | 44,883 | The .mvr-* result-panel component system |
mvr-premium.css | 9,192 | Additive premium layer, loaded last in <head> |
site-chrome.css | 24,088 | Public navbar and footer, namespaced .mv-* |
chapter-demos.css | 20,723 | Homepage mini-dashboards, .cd-*. Index only |
blog.css | 72,633 | Blog Studio, .mvblog-* |
tool-explainer.css | 23,767 | In-app explainers, .mvte-* |
rec-panel.css | 16,832 | Recommendations panel, .mvrec-* |
psi.css | 23,194 | PageSpeed report, .psi-*. Outside the design-lint targets so it can keep Google's exact palette |
library.css | 13,231 | Library timeline, .mvlib-* |
library-saved.css | 18,023 | Saved-results prompt, .mvsv-* |
responsive-analyzer.css | 24,068 | .ra-* |
image-tool.css | 27,069 | .it-* |
content-checks.css | 14,379 | .mvcc-* |
content-decay.css | 3,049 | .cd-* scoped under #view-contentdecay. Shares a prefix with chapter-demos.css but the two are never on the same page |
advtech.css | 8,730 | .mvat-* |
social.css | 12,214 | .mv-soc-*. Brand colors live here deliberately, outside the ratchet |
notifhub.css | 3,252 | .nh-* |
merge-tabs.css | 1,050 | .mvmg-* |
tech-guide.css | 5,129 | .mvtg-* |
gbp.css | 2,889 | .gbp-* |
dev-placeholder.css | 1,809 | .mvdev-*, for announced-but-unbuilt tools |
js/ in full#
| File | Bytes | Purpose |
|---|---|---|
mv-i18n-dict.<lang>.js | es 2,949,060 … ru 3,990,234 (one downloaded per visitor) | Generated. window.MV_I18N keyed by English source string. Loaded lazily on first non-English language |
tool-samples.js | 467,002 | window.MV_TOOL_SAMPLES, captured real results for the "See example" preview. Data, not UI; excluded from the i18n scanner |
mv-blog.js | 239,357 | Blog Studio, a multi-site CMS inside the app |
mv-library.js | 62,383 | Library and Saved Work |
mv-guide-rules.js | 39,313 | The "Get recommendations" rule table |
mv-export.js | 31,071 | Shared PDF/Excel/CSV/JSON export engine |
mv-psi.js | 31,081 | PageSpeed report renderer, shared with the free tool page |
rec-panel.js | 22,868 | Recommendations panel controller |
mv-charts.js | 20,909 | Shared chart wrappers over Chart.js |
mv-content-checks.js | 19,941 | Plagiarism and fact checking |
mv-modal.js | 16,499 | mvAlert, mvConfirm, mvPrompt. Shared file |
mv-advtech.js | 14,864 | Advanced Technical Checks |
mv-polyfills.js | 13,470 | Globals the premium renderers reference by bare name. Do not reorder |
mv-i18n-runtime.js | 11,742 | The shared translation runtime for every page except dashboard.html |
tool-explainer.js | 9,861 | Motion for .mvte explainers |
mv-combined.js | 7,954 | Merged tools: run a primary view and its siblings together |
mv-particles.js | 7,087 | Vanilla particles background |
chapter-demos.js | 6,720 | Homepage chapter animations |
mv-merge-tabs.js | 5,640 | Tab strip for merged tools |
mv-site-chrome.js | 5,478 | Mobile hamburger for the shared navbar |
mv-helpers.js | 5,348 | Extracted utilities such as mvRelativeTime and mvDownloadCsv |
mv-reveal.js | 4,632 | Scroll-reveal, disabled under reduced motion |
export-gate.js | 2,107 | Disables export controls until a tool has a result |
kgap-vs.js | 2,075 | Gap Finder dual-field sync |
vendor/supabase-2.38.0.umd.js | 98,746 | Self-hosted Supabase SDK |
Files that must not be edited in place#
| Destination | Real source | Sync command |
|---|---|---|
const LOGIN_HTML in _worker.js | login.html | node tests/login-inline-sync.mjs --write |
| Navbar and footer inside 21 public pages | partials/site-nav.html, partials/site-footer.html | node tests/site-chrome-sync.mjs --write |
js/mv-i18n-dict.<lang>.js (five) | i18n/strings.en.json and i18n/dict.*.json | node tests/i18n-generate.mjs --emit |
help/ | docs/kb/**/*.md | node tests/kb-build.mjs --write |
dist/ | the repository root | the deploy workflow, or a manual copy for local dev |
Each of the first three has a no-argument drift check that exits 1 when the two sides disagree. Run them before committing.
File-ownership rules#
Two developers share this repository, and the split is by file rather than only by branch, because git merges cleanly when two branches touch different files.
| File | Owner | Rule |
|---|---|---|
index.html | branch redesign | Do not edit from any other branch |
dashboard.html | branch main | Do not edit from redesign. 7.2 MB and CRLF; two simultaneous editors produce an unusable merge |
css/tokens.css | shared | A change for one page restyles every page. Say so explicitly before editing |
css/app.css, js/mv-modal.js, chat-widget.js | shared | Loaded by both the marketing site and the app |
| The site footer | copy-pasted into 20+ pages | Any edit is a 20-file diff. Do the whole set in one commit or not at all |
Full detail, including the pre-commit checklist, is in Code conventions and file ownership.
Line endings#
dashboard.html and legal.html use CRLF. A search-and-replace written for \n matches nothing in them; use \r?\n. login.html is also CRLF, but ECMAScript normalises CRLF to LF inside a template literal, so the inlined copy is LF and login-inline-sync.mjs compares line-ending agnostically. Several public pages have mixed endings, so site-chrome-sync.mjs preserves untouched regions byte for byte and inserts using each file's dominant EOL.
See also
Was this article helpful?
Thanks — feedback noted for the docs team.