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

26 KiB

Docs, comments and naming across the four apps

Read: three docs/conventions.md, four README.md, every docs/*.md, both CLAUDE.md, shared/src/fonts.ts, shared/src/icons.ts, all four src-tauri/Cargo.toml, all four package.json and tauri.conf.json, scripts/docs-check.mjs, and the CI workflows.

1. The three conventions.md files

Only three exist: margin-caledar/docs/conventions.md (89 lines), margin-editor/docs/conventions.md (103), margin-mail/docs/conventions.md (139). margin, the app all three defer to, has no conventions.md at all. That is the first hole: the house style is written down only in the repos that copied it, never in the repo that set it.

What all three share, near verbatim

Each opens by disclaiming originality. Calendar line 3: "This project is a sibling to ../margin and follows its conventions deliberately rather than inventing new ones." Editor line 3 and mail line 3 are the same sentence with the sibling list extended.

Five rules are word-for-word identical in all three:

  • Result<T, String> everywhere. No anyhow.
  • "DTOs crossing the IPC boundary live in src-tauri/src/dto.rs and are marked #[serde(rename_all = "camelCase")]. That file is the contract and is frozen: implementation modules add bodies, not fields. Its mirror is src/ipc.ts." (calendar:12, editor:13, mail:14)
  • "One zustand store per domain in src/store/. No middleware. One selector call per field (useThing((s) => s.field), never a destructured object), actions as inline arrow properties, and set((s) => ...) returning {} to no-op." (calendar:26, editor:26, mail:35)
  • "Flat kebab-case class names, not BEM. State is a data-* attribute, never an is- class."
  • "Transitions name explicit properties and use var(--ease). Never transition: all."

And each closes with a ## Never section of the same shape. Calendar:88 and mail:138: "No CSS framework, no component library, no router, no zustand middleware, no directory trees in any document, and no em dashes anywhere including code comments."

Where they contradict each other

Em dash versus en dash. Editor:103 is the only one that bans both: "no em dashes or en dashes anywhere, including code comments." Calendar:89 and mail:139 ban only em dashes. The user's own rule bans both. Editor is right and the other two are stale.

Container queries. Calendar:67 permits exactly one, on the event block, and says "Nothing else may reach for a container query without the same kind of reason." Mail:105 hardens it to "There is no container query in this repository" while explicitly crediting the calendar's exception. Editor does not mention container queries. Not a real contradiction, but three different postures.

Where a token lives. Calendar:46 and editor:81: "Every colour, radius and size goes through a token in src/styles/tokens.css." Mail:55 redefines tokens.css as a seam, not a list: it imports margin-shared's set and then src/styles/mail.css, and mail:86 says add to mail.css instead. Mail is the only one with the three-layer tokens/primitives/screens rule (mail:51-66) and the only one with a Kit page at #/kit.

Icons. All three keep "Inline Feather-style 24x24 stroke d strings passed to <Icon d={...} />. There is no icon set and no registry, and there will not be one." Calendar:79 and mail:120 add "An icon-only button always carries a title with its shortcut written in real glyphs"; editor drops that line. Mail:116 is the only one that names a file (src/ui/icons.ts) and the only one that acknowledges margin-shared/icons, which editor and calendar do not mention at all even though margin-shared is a real dependency of editor.

Comment density. Calendar:22 and mail:31: "Comments are rare and explain why, never what. Match the density in lib.rs." Editor:22 keeps the first sentence and drops the pointer. See section 5: the density claim is false in all three.

Errors. Calendar allows one error enum (google::api::ApiError), mail allows one (provider::ProviderError), editor:11 allows none. Each names its own reason. This is the right pattern: an app-specific carve-out written next to the shared rule.

What is genuinely app-specific and should stay that way

Editor's ## Markdown (38-59) and ## Tests (61-75) sections, mail's ## Places and stages (68-80) and ## Work packages (129-134), calendar's data-phone versus data-touch argument (57-70, copied verbatim into mail:94-103). Storage key prefixes differ by design: margincal-, margindocs-, marginmail-.

Broken cross-references

Calendar:3 says ../margin, and from python/margin-caledar that resolves. Editor:3 says ../margin and ../margin-calendar, and neither exists: rust/margin and rust/margin-calendar are not on disk. Calendar:17 cites margin/src-tauri/src/gdrive.rs:286 and calendar:20 cites pdf.rs:90. Both files exist; neither line carries a comment (section 5).

2. READMEs

repo lines verdict
margin 15 compliant. Description, install, one docs link, licence.
margin-calendar 15 compliant, but the last seven lines are one dense paragraph of six links.
margin-docs 14 compliant and the cleanest of the four.
margin-mail 20 over. Lines 3 to 10 are an eight-line pitch that belongs in docs/design.md.

Mail is the offender. Its opening paragraph restates the product ("New senders wait at the door until you let them in; people, newsletters and receipts live in three separate boxes") which is already docs/design.md:31-73. Cutting it to two sentences puts it at 13 lines.

Four other READMEs exist inside margin and are not project descriptions: margin/website/README.md (48 lines, 7 em dashes), margin/appstore/screenshots/README.md (51), margin/simplify/README.md (26), margin/simplify/guidelines/README.md (31). margin/shared/README.md is 15 lines and compliant. The website one is the worst artefact in the suite: bold-marked feature bullets, em dashes throughout, a "Built with" line. It reads like a different author.

margin/shared/README.md:3 and shared/package.json:7 both say the package is for "Margin and Margin Docs". Margin Mail has depended on it since package.json:24. The description is stale.

3. The docs set

  • margin: docs/publishing.md only (229 lines). No architecture, no design, no conventions, no setup, no release. The originating app is the least documented.
  • margin-calendar: architecture.md (145), conventions.md (89), design.md (152), mobile.md (304), release.md (105), setup.md (55). This is the set that got copied.
  • margin-docs: architecture.md (545), conventions.md (103), design.md (131), release.md (117), setup.md (29). Calendar's set minus mobile.md, macOS only.
  • margin-mail: architecture.md (352), conventions.md (139), design.md (153), release.md (119), plus features.md (475), ui.md (339), settings.md (201), plan.md (193), keyboard.md (129), help.md (74), mockups/, research/. No setup.md, which is the one file a new machine needs, and mail is the app that requires a Google OAuth client.

Three shapes are stable across the set. architecture.md always opens with the same stack sentence: "Tauri 2, React 19, Vite, TypeScript and zustand on the front, Rust behind" (calendar:3, editor:3, mail:3), then "The split is strict" and what each side owns. design.md is always product decisions argued as prose under "why" headings, and always ends with ## Visual language. release.md always starts ## Installing locally then ## Cutting a release and ends ## Updates.

Proposed canonical set

Five files, every app, same names, same opening move:

architecture.md, conventions.md, design.md, setup.md, release.md.

Then only what the product actually has: features.md, ui.md, keyboard.md, settings.md, help.md, mobile.md, publishing.md, plan.md, research/, mockups/. Never a numeric prefix.

Concretely: margin needs all five written and should keep publishing.md; mail needs setup.md; mail's plan.md is a milestone tracker and will go stale, so it belongs in the issue tracker or under research/.

4. The comment voice

What a comment is for here

A comment records a decision and the alternative that was rejected, so that a competent person does not undo it by accident. It never says what the line below does.

The shape is consistent enough to be a template. A file-head block states what the file is in one line, then a blank comment line, then one paragraph per decision. Each paragraph names the thing chosen, then the thing not chosen, then the concrete failure the wrong choice produces. Sentences are long, declarative, no hedging, no first person, no "note that". Length is one to seven lines per paragraph; a file head runs 3 to 17 lines. Doc comments on exported items are one sentence and often end on the consumer ("which is what a Typst preamble names a face by").

The tell is that almost every comment contains a causal clause: "because", "so that", "which is why", "rather than", "or a ... would".

Four exemplars, in full

margin/shared/src/fonts.ts:1-7:

// The faces both apps offer, and the two slots they set them into.
//
// This lives in one place because the two apps have to agree about it. A face named here is a
// `@font-face` in css/fonts.css, a file in fonts/, and a family a Typst preamble names on the way
// to a PDF, and those four lists going out of step with each other is a document that renders in
// one app and falls back to Georgia in the other. There is no way to keep four lists in two repos
// honest by hand, so there is one list.

margin/shared/src/icons.ts:3-6:

// Here because the two apps kept drifting. Each had its own idea of what a search or a moon looked
// like, they were adjusted independently, and the result was two products from the same hand that
// did not look related. A path is a design decision, not a detail, and the fix for two copies of a
// decision is one copy.

margin-mail/src-tauri/Cargo.toml:61-63:

# Refresh tokens, the backup key and every uploaded journal segment are sealed with
# XChaCha20-Poly1305. There is no `keyring` here on purpose: it has no Android backend at all, and
# on macOS it ties the item to the code signature, so every rebuild re-prompts.

margin-editor/src-tauri/Cargo.toml:54-56:

# Pinned exactly: the [patch] stubs at the foot of this file are tied to this version's burn/cubecl
# graph. A minor bump could silently invalidate a patch ("unused"), and the whole CUDA and LLVM
# subtree those stubs remove would come back. Bump deliberately and re-audit the stubs.

margin/shared/src/icons.ts:29-37 is the purest case, because it justifies an SVG path: two letterforms rather than one on a rule, because one letter over a full-width line is the underline button in every editor, and because the neighbouring SPELLING glyph is also built on a capital A, so the distinguishing feature has to be the bowl versus the tick, which survives at 16px.

Violations, in both directions

Undocumented decisions. The clearest cases are the exact decisions two sibling repos cite by file and line:

  • margin/src-tauri/src/gdrive.rs is 953 lines with zero comments. Calendar's conventions.md:17 points at gdrive.rs:286 as the origin of read_json and explains why the body goes to a String first. The reason is written in the calendar's docs and in mail's docs and never in the file.
  • margin/src-tauri/src/pdf.rs is 129 lines with zero comments. Calendar:20 and mail:23 both cite #[tauri::command(async)] on a synchronous fn at pdf.rs:90 as the trick for getting off the main thread. compile_pdf at line 90 carries no comment saying so.
  • Other zero-comment files over 200 lines in margin: src/components/EditorView.tsx (560), src/import/epub.ts (534), src/export/typst.ts (421), src/components/Sidebar.tsx (310), src-tauri/src/proofing.rs (275), src/model/book.ts (260), src-tauri/src/lib.rs (256), src/editor/search.ts (233), src/components/Dock.tsx (223).

Narration that should go.

  • margin/src/components/ExportPreview.tsx:320: // render cancelled or page failed; keep the previous canvas. Lowercase, no full stop, and the second clause narrates the line below.
  • margin/src-tauri/Cargo.toml:9: # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html, and lines 12-14, the _lib suffix paragraph. Both are cargo new boilerplate. The three sibling Cargo.toml files deleted them; margin did not.
  • // Prevents additional console window on Windows in release, DO NOT REMOVE!! is 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 configured, in package.json or on disk. Dead directives.

Beyond these, a sweep for narration patterns across all four repos found almost nothing. Every hit on "comment starts with a verb" or "comment starts lowercase" turned out to be a wrapped continuation line of a real reason. The voice is being held.

5. The CLAUDE.md tension, resolved

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." margin-docs and margin-mail have no CLAUDE.md.

The code says otherwise. Comment lines as a fraction of source in 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,973 10,634 24.2%
margin-mail 70,737 9,921 14.0%

margin obeys the rule as written. The three younger apps ignore it by a factor of ten to twenty, and they are the disciplined ones. The rule was true of a repo that had no siblings; it stopped being true the moment a decision had to survive being copied into another repo.

The operating rule, read off the code: a comment never says what, and always says why. The test already exists in simplify/guidelines/code-style.md:30: "delete the comment and ask whether a competent person would make the same mistake twice. If yes, keep it. If it just narrates the line below, cut it." Under that test margin is not compliant by being sparse; it is under-commented, and gdrive.rs is the proof.

The two CLAUDE.md files should be corrected or deleted. As written they are a live instruction to strip the best thing in this codebase.

6. Typographic compliance

Across *.md, *.ts, *.tsx, *.rs, *.css, *.toml, *.html, *.js, *.mjs, excluding node_modules, dist, target, .git, target-mas, .playwright-mcp and gen:

repo em dashes en dashes
margin 37 5
margin-calendar 0 0
margin-docs 7 0
margin-mail 3 2

margin-calendar is perfectly clean. margin holds 42 of the 54 in the suite.

Worst files:

  • margin/simplify/.research/memories-raw.md, 19. A dump of old memory files, so arguably input rather than committed prose, but it is in the repo.
  • margin/website/README.md, 7, all in body copy (lines 3, 21, 22, 23, 25, 37, 41).
  • margin-docs/src/markdown/corpus/real/margin-website-readme.md, 7. A copy of the file above, kept as a serializer test fixture. Fixing the website README without fixing the fixture will not help, and fixing the fixture may break a round-trip test.
  • margin/src/export/run.ts:8 and :36, margin/src/components/Library.tsx:88, margin/src/components/ExportPreview.tsx:185. These four are user-visible app copy, which is the worst place for it: the string at run.ts:8 puts one between "desktop app only" and "open the window from".
  • margin/src-tauri/stubs/burn-cuda/src/lib.rs:1 and stubs/cubecl-cpu/src/lib.rs:1, one each.
  • margin/simplify/guidelines/prose-and-docs.md:8 is the rule itself quoting both characters. Fine.

margin-mail's three are all legitimate: scripts/docs-check.mjs:46-47 is the regex that enforces the rule, and src/screens/guide/guide.test.ts:104 asserts the guide text is free of them.

The gate already exists, in one repo

margin-mail/scripts/docs-check.mjs is a 72-line node script wired to just docs (margin-mail/justfile:33-34). It walks every markdown file and reports em dashes, en dashes, directory trees and dead relative links. Its own header, lines 2-11, is a model comment. No other repo has it, and margin, which has 42 offences, is the repo that most needs it. Copying that one file into the other three, and extending it past *.md to source files so app copy is covered, is the single highest-value action in this report.

7. Directory trees

None. A search for box-drawing runs and for the ASCII form across markdown, TypeScript, Rust, JSON and text in all four repos returned nothing. The rule is being kept without a gate in three of the four repos, which is worth noting: the risk is a future file, not an existing one.

8. Naming

margin calendar docs 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 id 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
html title margin Margin Calendar Margin Docs Margin Mail
README h1 margin Margin Calendar Margin Docs Margin Mail
storage prefix (none stated) margincal- margindocs- marginmail-
licence FSL-1.1-MIT MIT MIT FSL-1.1-MIT

Disagreements, worst first:

  1. python/margin-caledar is a typo. Confirmed. Everything inside it says margin-calendar, including the remote and the Nix flake output (ci.yml:81, nix build .#margin-calendar).
  2. rust/margin-editor versus margin-docs. Confirmed. The directory is the only place the word "editor" appears as a name; package, crate, bundle id, product name and remote all say docs.
  3. python/ and rust/ parents are wrong for all four. All four are Tauri apps 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:24 both carry "margin-shared": "file:../../python/margin/shared", and margin-mail/.github/workflows/ci.yml checks the two repos out into rust/margin-mail and python/margin (lines 25 and 30) precisely to reproduce that path. The word "python" is baked into a GitHub runner's filesystem layout.
  4. margin-docs CI is failing on exactly this. The latest run's failure is ENOENT: no such file or directory, scandir '/Users/runner/work/python/margin/shared'. Editor's ci.yml runs pnpm install --frozen-lockfile after a single checkout, so the relative path has nothing to resolve to. Mail solved it with a second checkout; docs never did. Five of the last six runs failed.
  5. margin-mail has no git remote. One local commit, 088ec9c. Everything else in the suite is on GitHub. Whatever name the repo gets is still an open choice, which makes this the cheapest moment to fix the pattern.
  6. margin-app versus Margin. margin is the only app whose package and crate name is not its product name lowercased and hyphenated. margin-app also breaks the bundle id pattern: studio.margin.app reads as a namespace with a placeholder in it.
  7. margin/index.html title is lowercase margin while productName is Margin. The README h1 is lowercase too. The other three are consistent title case.
  8. Licences split two and two, and neither README in the MIT pair says so. margin and mail are FSL, calendar and docs are MIT, and nothing explains the split.
  9. Editor's conventions.md:3 points at ../margin and ../margin-calendar, neither of which exists relative to rust/margin-editor. The paths only make sense if all four sit as siblings, which is what the rename below produces.
  10. margin-docs/src/markdown/corpus/real/ holds copies of the calendar's and the editor's own conventions.md as test fixtures. Two of the three canonical convention documents exist twice in the suite and will drift.

9. Rename proposal, with cost

Target layout: one parent, ~/Workspace/projects/margin/, holding margin, margin-calendar, margin-docs, margin-mail as siblings.

A. margin-caledar to margin-calendar. Cheapest and unambiguous. Cost: mv the directory. No package.json anywhere references it. Nothing on GitHub changes; the remote is already correct. Only local shell history and any editor workspace file break.

B. margin-editor to margin-docs. Also cheap. Cost: mv the directory. The file:../../python/margin/shared path is unaffected because the depth does not change. docs/conventions.md:3 should be corrected in the same edit. No remote change: the remote is already margin-docs.

C. Collapse python/ and rust/ into one margin/ parent. The expensive one, and the one that pays. Cost, exhaustively:

  • margin-docs/package.json:33 and margin-mail/package.json:24: file:../../python/margin/shared becomes file:../margin/shared. Both lockfiles need regenerating.
  • margin-mail/.github/workflows/ci.yml:21,25,29,30,57,61: the checkout paths rust/margin-mail and python/margin become margin-mail and margin, and the working-directory lines follow.
  • margin-docs/.github/workflows/ci.yml: needs the second checkout added, which it is currently missing. This fixes the failing build rather than costing anything.
  • Local shell history, editor workspaces, and any absolute path in a memory file.
  • Nothing on GitHub changes. No remote, no clone URL, no release artefact, no bundle id.

D. margin-app to margin, and studio.margin.app to studio.margin.writer or similar. Recommend doing the crate and package rename and not the bundle id. Cost of the package and crate rename: package.json:2, src-tauri/Cargo.toml:2, Cargo.lock, the lib name margin_app_lib and every use margin_app_lib:: in src-tauri/src/main.rs, plus any workflow that names the binary. Cost of the bundle id rename: do not. Changing identifier on a shipped macOS app orphans the application support directory, breaks the Homebrew cask, breaks the updater's signature check, and makes the Mac App Store record a different product. The inconsistency is not worth that. Write one line in docs/conventions.md saying studio.margin.app is frozen and why.

E. Give margin-mail a remote. priyanshujain/margin-mail, matching the other three. Free now, and the naming stays consistent by default.

Order: B, A, E, C, D. B and A are free. C is best done immediately after, while mail has no CI history to invalidate.

10. The proposed house style

Docs

A README is the project description. Under 15 lines: one paragraph on what it is, one line on how to install, one paragraph of links into docs/, one line on the licence. margin-docs' README is the model. Mail's is eight lines over and those eight lines are already in docs/design.md.

docs/ holds architecture.md, conventions.md, design.md, setup.md, release.md in every app, plus whatever the product genuinely has. No numeric prefixes. No file that is a status report.

architecture.md opens with the stack sentence and "The split is strict", then what each side owns, then one section per hard part, then ## Order of work. design.md argues product decisions in prose under "why" headings and ends with ## Visual language. release.md runs ## Installing locally, ## Cutting a release, ## What the build needs, ## Updates.

conventions.md exists in every app including margin, and is split: the shared rules (the five identical ones, verbatim) and the app's own. When an app carves out an exception, the carve-out names the reason in the same sentence, the way mail:9 names provider::ProviderError and the four cases it has to branch on. Both em dashes and en dashes are banned, taking editor's wording over the other two.

Prose a colleague would write. No em dash, no en dash, no directory tree, ever, and the rule holds inside code fences and app copy as well as in body text.

Comments

A comment records a decision and the alternative that was rejected. It never says what the line below does; if you want to, rename something instead.

Shape: a one-line statement of what the file is, a blank comment line, then one paragraph per decision. Each paragraph names what was chosen, what was not, and the concrete failure the other choice produces. One to seven lines per paragraph. Declarative, third person, no hedging. Doc comments on exported items are one sentence.

The test, already written at simplify/guidelines/code-style.md:30: delete it and ask whether a competent person would make the same mistake twice.

Two consequences worth stating plainly, because the current text says the opposite. The instruction in margin/CLAUDE.md:9 and margin-caledar/CLAUDE.md:9 is wrong and should be replaced with the rule above. And "comments are rare" in all three conventions.md files is false and should be cut: the suite averages one comment line in seven, and the three densest repos are the three best ones.

Enforcement

Copy margin-mail/scripts/docs-check.mjs into the other three repos, wire it to just docs and to CI, and extend it beyond *.md to *.ts, *.tsx, *.rs and *.css so app copy is covered. That one file catches every offence in section 6, plus the dead links, plus any future tree. Fix margin's 42 dashes first, starting with the four in user-visible strings.