Skip to content
Metric VaultHelp Center
Open app

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#

BranchWhat it is
mainProduction. A push here goes live immediately, with no confirmation step
redesignThe 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.

FileOwnerRule
index.htmlbranch redesignIf you are not on redesign, do not edit it. Say so and stop rather than making a "small" change
dashboard.htmlbranch mainIf 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.

FileWhat it reaches
css/tokens.cssEvery color, radius, spacing and shadow token. A tweak for the homepage restyles the whole dashboard
css/app.cssThe dashboard chrome, and the pages that inherit from it
js/mv-modal.jsmvAlert, mvConfirm and mvPrompt on index, pricing, dashboard and admin
chat-widget.jsThe 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#

FileWhyWhat to do
index.htmlOwned by redesignDo not touch it from elsewhere
dashboard.html7.2 MB and CRLF. Two simultaneous editors produce an unusable mergeTake turns. Announce that you are in it
The site footerCopy-pasted into more than twenty pages, not templatedAny footer edit is a twenty-file diff. Do the whole set in one commit or not at all
login.htmlInlined into _worker.jsEditing 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.

bash
git branch --show-current

A 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.

bash
git add dashboard.html js/mv-charts.js

git 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.

bash
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.

DestinationReal sourceSync command
const LOGIN_HTML in _worker.jslogin.htmlnode tests/login-inline-sync.mjs --write
The navbar and footer inside 21 public pagespartials/site-nav.html, partials/site-footer.htmlnode tests/site-chrome-sync.mjs --write
js/mv-i18n-dict.<lang>.js (five)i18n/strings.en.json and i18n/dict.*.jsonnode tests/i18n-generate.mjs --emit
help/docs/kb/**/*.mdnode 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

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.

AreaConvention
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
ExtractionA 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, typeFrom css/tokens.css only. No hardcoded hex
MarkupNo inline style=. No emoji in UI; inline SVG only. No left-accent border stripes
ResultsRenderers 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-current immediately 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 -A is 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?