Code conventions and file ownership
The two-developer file split, the branch strategy, the shared files that change two pages at once, the CRLF traps, and the checklist to run before every commit.
Last updated 2026-08-06
Summary#
Two people work on this repository, often with AI agents running in parallel sessions against one working tree. Merge conflicts are avoided by splitting files, not just branches, because git merges cleanly when two branches touch different files and painfully when they touch the same one.
That single decision explains most of the rules on this page: who owns index.html, who owns dashboard.html, why a change to css/tokens.css needs a conversation first, and why git add -A is banned. The rest covers the code conventions the repository actually enforces, and three traps that silently discard your work.
Purpose#
There is no build step, no CI test job and no approval gate. A push to main is live on metricvaultai.com within about a minute. Convention is therefore not a style preference here; it is the only control between an edit and a customer.
The conventions are also shaped by two very large files. dashboard.html is 7,223,279 bytes and _worker.js is 1,710,142 bytes. At that size a conflicted merge is not resolvable by reading it, so the rules aim to make conflicts impossible rather than survivable.
Overview#
Branches#
| Branch | What it is |
|---|---|
main | Production. A push here goes live immediately, with no confirmation step |
redesign | The homepage redesign. Deploys to a preview at redesign.metricvaultai.pages.dev and never touches the live site |
The deploy workflow triggers on a push to either branch, and can also be started by hand. What makes one production and the other a preview is a single flag on the deploy command that passes the branch name through to Cloudflare. Any other branch added to that trigger list gets its own preview at <branch>.metricvaultai.pages.dev, with the same bindings, which means a preview branch reads and writes the production database. Treat previews as a way to look at a change, not as a safe place to mutate data. The genuinely isolated environment is the staging repository, described in Production and staging repos.
Who owns what#
The split is by page, and the two largest files never overlap, so git merges them without conflict.
| File | Owner | Rule |
|---|---|---|
index.html | branch redesign | If you are not on redesign, do not edit it. Say so and stop rather than making a "small" change |
dashboard.html | branch main | If you are on redesign, do not edit it |
That is the whole ownership model, and it holds only if both halves are respected. A one-line "harmless" fix to the other page is exactly what produces an unresolvable merge in a 7 MB file three days later.
Shared files, the real conflict risk#
These are loaded by both the marketing site and the app, so a change made for one silently changes the other. Neither developer sees it until something looks wrong on a page they were not working on.
| File | What it reaches |
|---|---|
css/tokens.css | Every color, radius, spacing and shadow token. A tweak for the homepage restyles the whole dashboard |
css/app.css | The dashboard chrome, and the pages that inherit from it |
js/mv-modal.js | mvAlert, mvConfirm and mvPrompt on index, pricing, dashboard and admin |
chat-widget.js | The assistant, one engine on three surfaces |
Before editing any of them, say so explicitly and check whether the change is wanted on both pages. If it is only wanted on one, scope it to that page's own stylesheet instead of the shared token. The design system explains why the token file is the one place a definition may live, and how the ratchet enforces it.
Files that merge badly#
| File | Why | What to do |
|---|---|---|
index.html | Owned by redesign | Do not touch it from elsewhere |
dashboard.html | 7.2 MB and CRLF. Two simultaneous editors produce an unusable merge | Take turns. Announce that you are in it |
| The site footer | Copy-pasted into more than twenty pages, not templated | Any footer edit is a twenty-file diff. Do the whole set in one commit or not at all |
login.html | Inlined into _worker.js | Editing one without re-syncing the other leaves production serving a stale copy |
How it works#
Before every commit#
Three steps, in this order, every time.
1. Confirm where you are.
git branch --show-currentA parallel session can move HEAD in a shared working tree. Checking costs a second; discovering it after a push to main does not.
2. Stage explicit paths. Never git add -A.
git add dashboard.html js/mv-charts.jsgit add -A sweeps up the other developer's work in progress, and in this repository it also sweeps up whatever a concurrent AI session has left in the tree. Name the files you changed.
3. Run the gates that apply.
node tests/design-lint.mjs # any UI change
node tests/chkblk.mjs dashboard.html # any inline-script edit
node tests/login-inline-sync.mjs # only if login.html changed
node tests/site-chrome-sync.mjs # any public page change
node tests/i18n-verify.mjs # any new user-visible string
node tests/kb-build.mjs # any change under docs/kb/None of these run in CI. The deploy workflow has four steps and not one of them is a test, so skipping a gate does not stop anything from shipping. Each script is documented in Verification scripts.
Files that must not be edited in place#
Four destinations in this repository are generated from a source elsewhere. Editing the destination does nothing, because the next sync overwrites it. Editing the source without running the sync does nothing either, because the destination is what ships.
| Destination | Real source | Sync command |
|---|---|---|
const LOGIN_HTML in _worker.js | login.html | node tests/login-inline-sync.mjs --write |
| The 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 |
The contract is always the same: edit the source, run the tool, commit both sides. The first three have a no-argument drift check that exits 1 when the two halves disagree.
Warning: login.html is the one that bites hardest. _worker.js intercepts GET /login and /login.html and serves an inlined template literal instead of the static asset, added to dodge a Pages caching bug. Nothing regenerates that copy automatically. Drift there once silently swallowed a full set of authentication fixes.
Line endings#
dashboard.html and legal.html use CRLF. A search-and-replace written for \n matches nothing in them. Write \r?\n.
login.html is also CRLF, but ECMAScript normalises CRLF to LF inside a template literal, so the inlined copy in _worker.js is LF. login-inline-sync.mjs compares the two line-ending agnostically for exactly that reason. Several public pages have mixed endings within one file, which is why site-chrome-sync.mjs preserves untouched regions byte for byte and inserts using each file's dominant style.
The failure mode is silent: your edit reports success, the file is unchanged, and you spend the next twenty minutes debugging code that was never modified.
Code conventions#
There is no build step. No package.json, no npm install, nothing is compiled or bundled. The files you edit are the files that ship. Every rule below follows from that.
| Area | Convention |
|---|---|
js/ | Plain classic scripts. No ES modules, no bundling, no defer on extracted files. Only order-independent, window-assigning leaf code. No top-level const or let, which are not window-visible. No side-effect IIFEs |
| Extraction | A file lifted out of an inline <script> is re-linked at the exact position the block occupied, so parse and execution order survive. The diff must show the source region is otherwise byte-identical |
css/ | Plain <link rel="stylesheet">. No preprocessor. Linked at the original position of the <style> block it replaces, because the cascade here is source order |
| Color, spacing, radius, shadow, type | From css/tokens.css only. No hardcoded hex |
| Markup | No inline style=. No emoji in UI; inline SVG only. No left-accent border stripes |
| Results | Renderers return data; the shared .mvr-* Result Shell renders layout |
Tests in tests/ | Node standard library only, because there are no dependencies to install. Run with Node 24 |
Localization is not optional. Every string a customer can see must go through the translation system when you write it, not in a later audit pass. The app ships in six languages. If you add a page, add it to HTML_FILES in tests/i18n-extract.mjs; if you add a component script that builds UI, add it to JS_FILES, or none of its strings ever enter the dictionary. See Localization system.
Concurrent sessions#
More than one AI session may be working in this tree at once. Two habits follow:
- Re-check
git branch --show-currentimmediately before staging, not only at the start of your work. - Assume any file you did not touch may have changed under you. That is the second reason
git add -Ais banned: it commits someone else's half-finished edit under your message.
If you find uncommitted changes you did not make, leave them alone and commit only your own paths.
Commit hygiene#
Commit the source and its generated copy together. Commit a footer change to all of its pages together. Keep a port from the staging repository in its own commit, separate from new work, so a revert is surgical.
Rollback in this repository is git revert plus a push, and the granularity of your commits is the granularity of your rollback. There is no rollback script. See Deployment.
See also
Was this article helpful?
Thanks — feedback noted for the docs team.