Files
margin/simplify/naming.md
T
2026-10-03 22:48:49 +05:30

27 KiB

Names and documentation conventions

Three of the four apps disagree with themselves about what they are called, and the rules they all follow are written down three times with three sets of edits. This is the plan for settling both. The evidence is in .research/docs-conventions.md and .research/repo-facts.md; the rules themselves are in guidelines/prose-and-docs.md and guidelines/code-style.md and are not restated here.

The names as they stand

Bold marks a cell that disagrees with the others or with itself, and paths are relative to /Users/pj/Workspace/projects.

Margin Margin Calendar Margin Docs Margin Mail
directory python/margin python/margin-caledar rust/margin-editor rust/margin-mail
package.json name margin-app margin-calendar margin-docs margin-mail
Cargo package margin-app margin-calendar margin-docs margin-mail
Cargo lib margin_app_lib margin_calendar_lib margin_docs_lib margin_mail_lib
bundle identifier studio.margin.app studio.margin.calendar studio.margin.docs studio.margin.mail
git remote priyanshujain/margin priyanshujain/margin-calendar priyanshujain/margin-docs none
productName Margin Margin Calendar Margin Docs Margin Mail
window title margin (index.html:27) Margin Calendar (:41) Margin Docs (:55) Margin Mail (:38)
README h1 # margin # Margin Calendar # Margin Docs # Margin Mail
name in docs prose margin, lowercase Margin Calendar Margin Docs Margin Mail
storage prefix margin- margincal- margindocs- marginmail-
licence FSL-1.1-MIT MIT MIT FSL-1.1-MIT

The disagreements, worst first:

  1. python/margin-caledar is a typo. Everything inside says margin-calendar: the package, the crate, the remote, and the Nix flake output that CI builds (flake.nix:14,18,19, .github/workflows/ci.yml:81 runs nix build .#margin-calendar).
  2. rust/margin-editor is the only place the word "editor" survives as a name. Package, crate, bundle id, productName and remote all say docs.
  3. python/ and rust/ are wrong for all four. Every one is a Tauri 2 app with a React front end and a Rust backend; none is a Python project. This is not cosmetic: margin-docs/package.json:33 and margin-mail/package.json:27 both carry "margin-shared": "file:../../python/margin/shared", and margin-mail's CI checks two repos out into rust/margin-mail and python/margin (ci.yml:25,30) purely to reproduce that path. The word "python" is baked into a GitHub runner's filesystem layout.
  4. Margin Mail has no remote. One commit, 088ec9c, with 123 uncommitted files on top. The name is still an open choice, which makes this the cheapest moment to fix the pattern.
  5. margin-app is the only package and crate name that is not the product name lowercased and hyphenated, and it is why the bundle id reads studio.margin.app, a namespace with a placeholder in it.
  6. Margin calls itself margin in lowercase in its README h1, its window title (index.html:27) and in every sibling's prose (margin-calendar/docs/conventions.md:3, margin-docs/docs/conventions.md:3, margin-mail/README.md:8), while productName is Margin.
  7. Licences split two and two and neither MIT README says so. repo-layout.md depends on this: the shared repo has to be MIT so all four can consume it.
  8. Margin Docs' docs/conventions.md:3 points at ../margin and ../margin-calendar. Neither exists relative to rust/margin-editor. The paths only make sense once the collapse has run.

Target: one parent, ~/Workspace/projects/margin/, holding margin, margin-calendar, margin-docs, margin-mail as siblings, with margin-shared joining them later.

The rename plan

Before anything moves

Margin Docs has 123 uncommitted files and Margin Mail has 123 on top of a single scaffold commit. Moving a directory does not disturb git, which stores paths relative to the repository root, so that work survives a mv intact; commit or stash first anyway, so a mistake is one git checkout away. What does not survive is everything keyed on the absolute path:

  • node_modules. pnpm's store directory name encodes the specifier (node_modules/.pnpm/margin-shared@file+..+..+python+margin+shared/), so both Docs and Mail need node_modules removed and pnpm install rerun after the move, not just a lockfile edit.
  • The assistant's per-project memory and session history, under ~/.claude/projects/-Users-pj-Workspace-projects-<slug>/. Five such directories exist, one per app plus margin-website. Rename each to the new slug or the app's learnt facts are orphaned.
  • Shell history, editor workspaces, and any absolute path written into a doc.

Steps 1 and 2: rename the two misnamed directories

rust/margin-editor to margin-docs, then python/margin-caledar to margin-calendar. Both are one mv and free. Nothing references either by name: file:../../python/margin/shared is unaffected because the depth does not change, both remotes are already correct, and the flake output never mentioned the misspelling. Fix Docs' docs/conventions.md:3 in the same sitting, which is disagreement 8.

Step 3: give Margin Mail a remote

priyanshujain/margin-mail, matching the other three, created before the collapse so the CI paths below are written once. Free now, expensive to change after the first release, because the repo name is in the release URL the Homebrew cask and the updater fetch from.

Step 4: collapse python/ and rust/ into one margin/ parent

The expensive step and the one that pays, and best done immediately after step 3, while Mail has no CI history to invalidate. Exhaustively, what changes:

  • margin-docs/package.json:33 and margin-mail/package.json:27: file:../../python/margin/shared becomes file:../margin/shared, an interim value. repo-layout.md replaces it with a published @margin/* dependency, so if the shared repo lands first, skip this edit entirely.
  • Both lockfiles record the specifier in three places each: margin-docs/pnpm-lock.yaml:54,55,1449, 1450,3207 and margin-mail/pnpm-lock.yaml:36,37,918,919,1957. Regenerate with pnpm install, never hand-edit.
  • margin-mail/.github/workflows/ci.yml: the checkout paths at lines 25 and 30 and the working-directory lines at 21, 57 and 91 lose their rust/ and python/ prefixes.
  • margin-docs/.github/workflows/ci.yml: gains the second checkout it never had, which is the fix rather than the cost. See below.
  • The local paths listed under "before anything moves".

What must not change: any bundle identifier, any git remote, any release tag, the Homebrew tap or its cask. Nothing on GitHub is affected by a local move.

The failing build. Margin Docs' ci.yml:19 does one checkout and ci.yml:29 runs pnpm install --frozen-lockfile, so file:../../python/margin/shared has nothing to resolve to. Run 33308997470 (2026-08-30) failed with ENOENT: no such file or directory, scandir '/Users/runner/work/python/margin/shared', and five of the last six runs failed. Margin Mail solved this by checking priyanshujain/margin out a second time; Margin Docs never did. The fix is Mail's two-checkout block with the shorter paths, and since priyanshujain/margin is public the second checkout needs no token.

Step 5: margin-app to margin, package and crate only

Do the package and crate rename. Do not touch the bundle identifier. The cost: package.json:2, src-tauri/Cargo.toml:2 and the lib name at :15, Cargo.lock, src-tauri/src/main.rs:5 (margin_app_lib::run()), .github/workflows/release.yml:47 and :50 (an awk that bumps the version by matching /^name = "margin-app"$/, which silently stops matching rather than failing), the artefact name at appstore.yml:128, and the Xcode project under src-tauri/gen/apple/, which holds margin-app.xcodeproj and a margin-app_iOS directory named at project.yml:1,27,34,40,57 and is best regenerated with tauri ios init rather than renamed by hand.

The bundle identifier is frozen

studio.margin.app stays, along with the other three, and one line in Margin's docs/conventions.md should say so and why: it is the one inconsistency in the table deliberately left standing.

Changing an identifier on a shipped app orphans the application support directory Tauri derives from it. Every app reads its library through app_data_dir (Margin Mail's src-tauri/src/library.rs:8-9 is the shared shape), so a new identifier makes an existing install look like a fresh one: accounts, sealed refresh tokens, the log and the database all move out from under the app. Five other things are keyed on the same string:

  • The sealed secret service name. margin-calendar/src-tauri/src/google/secrets.rs:41 and margin-mail/src-tauri/src/google/secrets.rs:42 use the identifier as SERVICE, and the reference is composed from it (secrets.rs:307 asserts studio.margin.calendar/1234).
  • The OAuth redirect registered with Google. google/auth.rs:112 in Calendar and :136 in Mail declare studio.margin.<app>:/oauth2redirect, matched by the deep link schemes in tauri.conf.json (Calendar :35,41, Mail :34,38). Changing it means re-registering the mobile clients in the Google console.
  • The macOS notification settings deep link, margin-mail/src-tauri/src/notify/macos.rs:259.
  • The App Store record. Ten scripts under margin/scripts/ default BUNDLE_ID to studio.margin.app (appstore-listing.rb:24, apple-provision.rb:40, apple-secrets.sh:9, mas-upload-local.sh:16, the three testflight-*.rb at :19 and :21, and three more), and src-tauri/gen/apple/project.yml:3,14 carries it into the Xcode build. A bundle id is the App Store's primary key: a new one is a new app, with no reviews, testers or purchase history.
  • The Homebrew cask. release.yml:231-258 pushes a version and sha into priyanshujain/homebrew-margin, Casks/margin.rb, whose uninstall and zap stanzas name the installed bundle.

A note on the updater, which is easy to get wrong by reading the committed config alone. The plugins block in Margin's and Margin Docs' tauri.conf.json is empty and Calendar's and Mail's hold only deep-link, but that is the whole point of the overlay described in guidelines/distribution.md: all four apps carry a src-tauri/tauri.release.conf.json and all four of those configure the updater, because the key's mere presence in the committed file would make a local tauri build demand a signing key. So the updater is real, and a bundle id change does break the update path for every existing install, which would no longer recognise the new bundle as itself. It is not a signature problem, it is an identity one. The five reasons above stand regardless.

The conventions files

Only three exist: margin-calendar/docs/conventions.md (89 lines), margin-docs/docs/conventions.md (103) and margin-mail/docs/conventions.md (139). Margin, the app all three defer to, has none: the house style is written down only in the repos that copied it. Five rules are identical in all three, word for word:

rule calendar docs mail
Result<T, String> everywhere, no anyhow 9 11 9
dto.rs is the frozen IPC contract, mirrored by src/ipc.ts 12 13 14
one zustand store per domain, no middleware, one selector per field 26 26 35
flat kebab-case class names, state as data-*, never is- 44 79 84
transitions name explicit properties and use var(--ease) 52 88 92

Each file also opens by disclaiming originality in near-identical words, and each closes with a ## Never section of the same shape (calendar:86, docs:100, mail:136).

Where they genuinely contradict each other:

  • Dashes. Docs:103 bans em dashes and en dashes. Calendar:89 and mail:139 ban only em dashes. The house rule bans both, so Docs is right and the other two are stale.
  • Where a token lives. Calendar:46 and docs:81 say every colour, radius and size goes through a token in src/styles/tokens.css. Mail:55 redefines that file as a seam rather than a list, and mail:86 says add to src/styles/mail.css instead. Mail is also the only one with the three-layer tokens, primitives, screens rule (mail:51-66).
  • Container queries. Calendar:67-70 permits exactly one, on the event block, with a reason. Mail:105 hardens that to "there is no container query in this repository" while crediting the calendar's exception. Docs is silent. Three postures, one subject.
  • Icon buttons. Calendar:79 and mail:120 both require an icon-only button to carry a title with its shortcut in real glyphs; docs drops the line. Mail:116 is the only file that names the icon module (src/ui/icons.ts) and the only one that acknowledges margin-shared/icons, a real dependency of Margin Docs.
  • Comment density. Calendar:22 and mail:31 both say "Comments are rare and explain why, never what. Match the density in lib.rs." Docs:22 keeps the sentence and drops the pointer. The claim is false in all three by a factor of ten to twenty.

The error carve-outs are not a contradiction and are the pattern to keep: calendar:9 allows google::api::ApiError because the sync engine has to tell a 410 from a 412, mail:9 allows provider::ProviderError, docs:11 allows none. Each names its own reason in the same sentence.

The one dead cross-reference is margin-docs/docs/conventions.md:3, which says the project is a sibling to ../margin and ../margin-calendar. From rust/margin-editor neither path exists. The collapse makes both resolve, and the rename should correct the sentence anyway. The docs checker does not catch it, because it is inline code rather than a markdown link.

The plan is one shared document, in the shared repository. guidelines/ moves out of margin/simplify/ into margin-shared/guidelines/ when that repo exists (repo-layout.md), MIT licensed so all four apps can copy from it whatever their own licence says, and it gains guidelines/conventions.md holding the five identical rules verbatim plus the shared CSS and store rules, so there is exactly one copy of each.

Four separate repositories mean a relative link between them cannot resolve and a URL is not read by anyone working offline. Use the mechanism that already exists for fonts: margin-shared/bin/ ships sync-fonts.mjs with a --check mode, wired as fonts:sync and fonts:check in margin/package.json:12-13 and margin-docs/package.json:15-16. Add sync-guidelines.mjs on the same shape, copying guidelines/*.md into each app's docs/guidelines/ with --check failing CI on drift. The cost is four copies of nine files, acceptable only because the check makes drift loud.

Each app then keeps a docs/conventions.md holding only its own rules: Docs' ## Markdown (38-59) and ## Tests (61-75), Mail's ## Places and stages (68-80) and ## Work packages (129-134), the three-layer token rule, the storage prefix, and each error carve-out with its reason. Calendar's data-phone versus data-touch argument (57-70) is copied verbatim into mail:94-103, so it belongs in the shared file, once.

Until the shared repo exists, fix the three files in place: take Docs' dash wording into the other two, cut "Comments are rare" from all three, and correct Docs' line 3.

One thing not to fix. margin-docs/src/markdown/corpus/real/ holds 16 markdown fixtures, among them calendar-conventions.md, editor-conventions.md, margin-readme.md, margin-claude.md and margin-website-readme.md. They are snapshots for the serializer round-trip tests and they will drift from the originals, which is correct: a fixture that tracks a moving file is not a fixture. Say so in Docs' ## Tests section and exclude the directory from the checker.

The canonical docs set

Five files, same names in every app: architecture.md, conventions.md, design.md, setup.md, release.md. Then only what the product genuinely has. Never a numeric prefix.

file Margin Calendar Docs Mail
architecture.md missing 145 545 352
conventions.md missing 89 103 139
design.md missing 152 131 153
setup.md missing 55 29 missing
release.md missing 105 117 119
product files publishing.md (229) mobile.md (304) none features.md (475), ui.md (339), settings.md (201), plan.md (193), keyboard.md (129), help.md (74), mockups/, research/

Margin, the originating app, has one document. Margin Mail has ten and is missing the one a new machine needs, and it is the app that requires a Google OAuth client to run at all. What each of the five holds, read off the three sets that exist: architecture.md opens with the same stack sentence in all three ("Tauri 2, React 19, Vite, TypeScript and zustand on the front, Rust behind", calendar:3, docs:3, mail:3), then "The split is strict" and what each side owns, then one section per hard part, then ## Order of work. design.md argues product decisions as prose under "why" headings and ends with ## Visual language. release.md runs ## Installing locally, ## Cutting a release, ## What the build needs, ## Updates. setup.md is what a fresh machine does: toolchain versions, credentials, the first run.

Margin needs all five written and keeps publishing.md. Mail needs setup.md, covering the Google OAuth client, google-credentials.json and the fixture harness. Mail's plan.md is a milestone tracker that will go stale, so it moves under docs/research/ or into the issue tracker. Mail's README is 20 lines whose lines 3 to 10 are a pitch already written at docs/design.md:31-73; cutting it to two sentences puts it at 13. Margin's README h1 and index.html:27 become Margin.

The comment style

The rule the code actually follows, as against the rule two repos have written down: a comment never says what, and always says why. Measured over src/, src-tauri/src/ and shared/src/:

repo source lines comment lines share
Margin 10,377 125 1.2%
Margin Calendar 20,211 2,208 10.9%
Margin Docs 43,985 10,634 24.2%
Margin Mail 70,737 9,921 14.0%

The three densest repos are the three best ones. Margin is not compliant by being sparse, it is under-commented, and the proof is that the two files sibling repos cite by name as the source of a decision carry no comment at all:

  • margin/src-tauri/src/gdrive.rs, 953 lines, zero comment lines. margin-calendar/docs/conventions.md:17 points at gdrive.rs:286 as the origin of read_json and explains why the body goes to a String first, so the error payload survives into the message. That reason is written in two other repositories' docs and nowhere in the file.
  • margin/src-tauri/src/pdf.rs, 129 lines, zero comment lines. Calendar:20 and mail:23 both cite #[tauri::command(async)] on a synchronous fn as margin's trick for getting off the main thread without hand-writing spawn_blocking. compile_pdf is at pdf.rs:90 and says nothing.

Both get a comment naming the decision and the failure the other choice produces. The narration to delete, specifically:

  • margin/src/components/ExportPreview.tsx:320: // render cancelled or page failed; keep the previous canvas. The second clause narrates the line below.
  • margin/src-tauri/Cargo.toml:9, the See more keys and their definitions at ... line, and lines 12 to 14, the paragraph explaining the _lib suffix. Both are cargo new boilerplate that the three sibling manifests deleted.
  • // Prevents additional console window on Windows in release, DO NOT REMOVE!! at src-tauri/src/main.rs:1 in all four repos. Tauri scaffold text, shouting, and none of these apps ships on Windows.
  • Four // eslint-disable-next-line react-hooks/exhaustive-deps in Margin (src/components/FindBar.tsx:44, src/editor/FloatingToolbar.tsx:69, src/editor/Editor.tsx:113 and :122). No repo has eslint, in package.json or on disk. Dead directives.

A sweep for narration patterns across all four repos found almost nothing else: the voice is being held, and it is the written rule that is wrong.

The CLAUDE.md problem

margin/CLAUDE.md:9 and margin-caledar/CLAUDE.md:9 are byte-identical:

- Avoid excessive comments. Only comment when absolutely necessary. Code should be readable and
  not require comments to understand it.

Read literally, that instructs anyone picking up the work to strip the best thing in the codebase: the dependency blocks in Mail's and Docs' Cargo.toml that say why a version is pinned exactly and why keyring is not used, and the file heads in shared/src/fonts.ts and shared/src/icons.ts that say why one list exists instead of four. It was true of a repo with no siblings and stopped being true the moment a decision had to survive being copied into another repo. Margin Docs and Margin Mail have no CLAUDE.md at all, so the two most disciplined repos run on rules nobody wrote down.

The replacement, given that guidelines/ now exists and is the real answer: delete the coding and prose bullets from both files and give all four repos the same short CLAUDE.md, about ten lines, naming the app and pointing at docs/guidelines/ (synced by sync-guidelines.mjs --check, above) and at the app's own docs/conventions.md, with nothing else in it. The git rules in Margin's and Calendar's files are correct and already restated in guidelines/git.md, so they move rather than being lost. margin-docs/src/markdown/corpus/real/margin-claude.md is a snapshot of Margin's current file used as a test fixture, and it stays as it is.

The docs checker

margin-mail/scripts/docs-check.mjs, 72 lines, wired to just docs (margin-mail/justfile:33-34) and to CI (margin-mail/.github/workflows/ci.yml:51). It walks every markdown file in the repo, skipping node_modules, dist, target, .git, gen and .playwright-mcp, and reports three things: em and en dashes anywhere including inside code fences, directory trees (a run of box-drawing glyphs, or three or more consecutive lines of the ASCII form), and relative links whose target does not exist. It exists in one repository, and Margin, which has the most offences, is the one that most needs it. Run unchanged against each repo today (2026-09-06):

repo em en trees dead links total outside verbatim material
Margin 27 0 0 42 69 7
Margin Calendar 0 0 0 0 0 0
Margin Docs 7 0 0 46 53 1
Margin Mail 0 0 0 0 0 0

The last column is the number that matters. Margin's 69 are almost all inside this plan directory: 46 are in simplify/.research/memories-raw.md, a verbatim dump of old memory files that exists so a claim can be checked and must not be edited, and most of the remaining dead links point at plan documents not written yet. Margin's only real offences are the seven em dashes in website/README.md (lines 3, 21, 22, 23, 25, 37, 41). Margin Docs' 53 are all in src/markdown/corpus/ except one false positive at docs/architecture.md:151, where the prose discusses markdown link syntax and the checker's link regex fires inside a code span, reading a bracketed target as a path.

Three changes before it can be shared:

  1. The link check must ignore fenced blocks and inline code spans. The dash check must not: catching a dash inside a fence is deliberate.
  2. A skip list for verbatim material, as a .docsignore or an exported constant: src/markdown/corpus in Margin Docs, simplify/.research in Margin. Without it Margin Docs can never go green, because fixing a fixture breaks the round-trip test it exists for.
  3. Extend it past *.md to *.ts, *.tsx, *.rs, *.css, *.toml and *.html so app copy is covered. That adds six hits in Margin and none anywhere else: user-visible strings at src/export/run.ts:8 and :36, src/components/Library.tsx:88 and src/components/ExportPreview.tsx:185, which are the worst place for a dash and the first to fix, plus one line each in src-tauri/stubs/burn-cuda/src/lib.rs and stubs/cubecl-cpu/src/lib.rs. It must exempt the checker's own regex (docs-check.mjs:46-47) and the assertion that the guide text is free of them (margin-mail/src/screens/guide/guide.test.ts:104), which necessarily contain the characters.

Where it lives and how it is wired. It moves into the shared repo as a bin, the way margin-shared-fonts already is (margin-mail/package.json:15 calls the bin, while margin/package.json:12 and margin-docs/package.json:15 still call the script by path and should converge on the bin form). Each app then gets a docs recipe in its justfile and one line in CI: Calendar and Docs need a - run: added to their existing ci.yml, and Mail already has both. Margin has neither a justfile nor a ci.yml (its workflows are appstore.yml and release.yml only), so it needs a docs script in package.json and a small CI workflow, worth having regardless because Margin has no typecheck or test gate at all today.

Order of work

  1. Rename rust/margin-editor to margin-docs, fix its docs/conventions.md:3, and rename python/margin-caledar to margin-calendar.
  2. Create priyanshujain/margin-mail and push Margin Mail's work.
  3. Collapse both parents into ~/Workspace/projects/margin/: the two package.json shared paths, both lockfiles regenerated, Mail's CI paths rewritten, the second checkout added to Docs' CI, and the five ~/.claude/projects/ directories renamed.
  4. Fix the three conventions.md files in place: Docs' dash wording into the other two, cut "Comments are rare" from all three.
  5. Fix the seven em dashes in website/README.md and the four in Margin's user-visible strings.
  6. Patch docs-check.mjs (code spans, skip list, source files) and copy it into the other three repos with the wiring above, including a first CI workflow for Margin.
  7. Replace the two CLAUDE.md files and add one to Margin Docs and Margin Mail.
  8. Write Margin's five missing docs and Margin Mail's setup.md, trim Mail's README, move Mail's plan.md under docs/research/.
  9. When the shared repo exists, move guidelines/ into it, add guidelines/conventions.md and sync-guidelines.mjs, and cut each app's docs/conventions.md down to its own rules.
  10. Rename margin-app to margin, package and crate only. The bundle identifier never changes.