Skip to main content

Writing Plain English

This convention applies to everything we write for each other: DBML notes, code comments, docs, GitHub issues, PR descriptions, review comments, and error messages. The team includes non-native English speakers. Write at roughly B1–B2 level: simple, concrete, direct. A document nobody can parse has zero value, even when it is technically correct.

Audience: every engineer and every AI assistant working in jodapp-api, jodapp-web, and this docs repo.

The rules​

  1. Short sentences. One idea per sentence. More than ~15 words — split it.

  2. Common words. Say "cannot change", not "immutable". Say "copied from", not "snapshotted". Say "does nothing", not "no-op".

  3. Active voice. "The system creates X", not "X is created by the system".

  4. Lead with the point, then expand in bullets (reworded 2026-08-13). Write the idea as the first sentence. Put the parts of that idea in bullets under it. Nest a bullet deeper when it has parts of its own.

    The test is punctuation. If you find yourself listing things inside a sentence using —, ; or ,, those things belong in bullets.

    Then rewrite the lead sentence so it still reads naturally with the list gone. A lead sentence that only makes sense while its list is attached is not finished yet. It should read like an ordinary sentence in prose.

  5. No idioms or metaphors. They do not translate. Write the underlying meaning instead.

  6. Explain, don't compress. Walk through the reasoning in full sentences so the reader learns the why. Plain words, full depth — simplifying the language never means dropping the technical content.

  7. Concrete examples. "When a gig shift completes, we record $5 of the $20 platform fee as revenue" beats any abstract description.

  8. Rules as tables. When stating a set of rules or conditions, use a small decision table instead of prose paragraphs. A table can be scanned in one pass; the same rules buried in a 130-word sentence cannot.

  9. No invented nouns (decided 2026-08-10). Do not turn an action into a noun the reader cannot look up. "The extension", "the sweep", "the correction" name nothing — no table, no column, no enum value. The writer knows what they mean. The reader does not, and has nowhere to check. Write the action as a verb with its actor: "an admin extends expires_at". Or name the record in full: "a Billing::CreditAction of type expiry_extension". Repeat the model name every time if you have to.

  10. Name the model, never a short form (decided 2026-08-12). Short forms like "the lot" or "the entry" look harmless. On a page about EntitlementLotAllocation, "lot" can mean two different tables, and "the entry" names nothing on the page. Write the model name every time the short form could be misread. Repeating a name is cheaper than one wrong guess by the reader.

    Drop the domain prefix when the model belongs to the page's own domain. Write EntitlementLot, not Billing::EntitlementLot. Write entitlement_lots, not billing_entitlement_lots. Keep the prefix on models from another domain. Gig::Shift and Identities::Admin tell the reader that the model lives somewhere else.

    Two places keep the full name even inside their own domain:

    • diagram node labels, because a reader looks at the picture without reading the sentence above it
    • values inside a worked example, because a table cell is read on its own
  11. Write field values the way code writes them (decided 2026-08-13). Use entry_type: :grant, not "entry_type grant" and not "the entry type is grant". Code style is exact, and the reader can search for it.

    • Use the full column name. Never a friendlier short label. Write deferred_revenue_delta_cents, not "deferred revenue".
    • Write an enum value as a Ruby symbol: :trial_grant, not trial_grant.
    • Write one field per line. Inside a table cell, separate the lines with <br/>.
    • A step list may describe what a write sets with an equals sign, when the right side is plain words: `available_delta` = the granted units, positive (decided 2026-08-16). When the right side is a literal value — an enum value, a number, or NULL — use the column: value form instead: units_allocated: 0, never `units_allocated` = 0.
  12. A link says what it points at (decided 2026-08-13). Write [Billing D10 — A credit action needs one admin, not two], not [D10]. The reader learns the rule without opening the page.

    • A link to a decision always carries three things: the domain, the number, and a short sentence.
    • Keep it to one short sentence. When the decision's own title is long, or lists things, write a shorter sentence that states its point.
    • A link to anything else carries a title only when its own name does not already say what it is. [The deferred revenue on a lot] needs nothing added.
  13. A heading resolves its own words (decided 2026-08-15). Readers arrive from the table of contents, not from the paragraph above. A heading and its first sentence must define or link every term they use, in place. A heading that leans on a word defined in an earlier section fails the reader who lands on it directly.

    When a review flags one heading, sweep the whole file. The flagged heading is never the whole problem.

  14. Do not borrow a technical word for something else (decided 2026-08-16). Some words carry one precise meaning in SQL, Rails, or accounting. Use such a word only when you mean exactly that. When you mean something else, use a plain word. Write "combination", not "tuple" — in SQL, a tuple is a row.

    This rule protects the expert reader, the way the idiom ban protects the non-native reader. The reader who knows the word loads its precise meaning without stopping. Nothing warns them that the writer meant something else. An unknown word costs a lookup. A borrowed word costs a wrong belief.

    Cases this rule has caught:

    • "tuple" for a product + legal entity + account combination. In SQL, a tuple is a row.
    • "WYSIWYG" as the name of a pricing rule. WYSIWYG names editors that show the final output while you type. The rule is now called "the one-active-price rule".
    • "immutable" for a business policy. In Ruby, immutable is a guarantee the language enforces. "Cannot change once ledger history exists" states the real rule, and who enforces it.

    The finance terms-of-art exception, stated under "Words to avoid", stays. It allows a real accounting term used with its own meaning, defined in plain words on first use. This rule bans the opposite case. No definition can rescue a word used against its own meaning.

Words to avoid​

AvoidUse instead
immutablecannot change
snapshottedcopied from
no-opdoes nothing / has no effect
denormalisedcopied into this table
idempotentsafe to run twice
load-bearingimportant — removing it breaks X
red herringlooks like the cause, but is not
leaks into / cascadesspreads to
move the needle / north star / dial inmake a real difference / main goal / adjust
the sweep / sweptname the job and its effect: "the nightly cleanup job closes the lot"
re-arm / fire againsends again / runs again

Exception — finance terms-of-art (decided 2026-08-04). In finance and billing docs, an industry term may be used when it is the exact term engineers will meet in accounting tools and their docs (for example "snapshot at issuance"). Two conditions: define it in plain words on first use ("snapshot = copied at that moment and never changed after"), and do not use the term outside the finance docs.

GitHub issues​

  1. Open with a short vocabulary list defining each table, column, and concept the issue uses ("an Org::Membership connects one Identities::User to one Org::Company").
  2. Include a worked example — a numbered walk-through tracing one real person or value through the code.
  3. Do not rely on initiative names ("employer sync Phase A 2.2") as context the reader must already know — state the rule itself. Assume the reader has zero context from prior conversations.

The check before committing prose​

  1. Read it aloud. Split any sentence over ~15 words.
  2. Highlight every word that would not appear in basic technical English. Replace it, or define it on first use.
  3. A comment block over ~6 lines — would bullets be clearer?
  4. Find every "the <noun>" your text leans on. If the noun is not a table name, a column name, an enum value, a model class, or a heading in the docs, you made it up. Replace it with the verb and its actor, or with the record's full name.
  5. Search your text for —, ; and ,. Each one may be a list hiding inside a sentence. Move it to bullets, then read the sentence again on its own.
  6. Find every link whose text is only an identifier, like [D10] or [UC-5]. Add a short sentence saying what it points at.
  7. Find every field value written as prose, like "the entry type is grant". Rewrite it as entry_type: :grant, with the full column name.
  8. Read every heading alone, as if you arrived from the table of contents. Every word in it must be defined in the heading's own section, or linked.
  9. Find every word with a precise SQL, Rails, or accounting meaning. Check that your sentence uses that exact meaning. If it does not, replace the word with a plain one.