Skip to content
C.W.K.
Lesson 06 of 06 · published

Encapsulation: Amounts Stay Closed Until Opened

~13 min · encapsulation, disclosure, swiftui, react

Level 0Mise en Place
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The principle leads

Invariant 7 has two clauses: the principle leads, and parameters stay private. Why a dish works comes first on every page, and amounts, times and temperatures open on demand. It is the app's thesis written as a UI rule. A recipe app leads with grams because grams are what it hands out. Sizzle hands out classes, so the grams are the private detail of one realization of a class.

One closed box

Parameters are a single free-text column on the class row, parameters. There is no schema for units and no structure inside. The placeholder Dad sees asks for grams, times and temperatures and calls them private detail. Whatever he writes there is his note for one dish or one version.

Only Dad writes it. No reading touches the column: a newer reading may refresh a proposed dish's description and bindings, but the fields it refreshes never include parameters. None of the actions I can propose sets it either. The amounts reach the record through one door, a PATCH from the class page, so they survive every re-read, even on a class that is still a proposal.

On the web the box is the Parameters component in ClassPage.tsx. Its open state starts as false, so a class page loads with one line, Exact amounts, and a chevron. An empty box says so on that same line, (none yet), so Dad can tell empty from closed without opening it. Opening it shows a text area and a Save button that stays disabled until the draft differs from what is stored.

A Sizzle dish page for Pulpo a la brasa con cachelos: Proposed and Eaten badges, an Adopt into my tree button, an Inherits line naming two principles, HOW IT BINDS THE ROLES with five roles, a closed Exact amounts (none yet) line, two plate thumbnails and a Make my version button.
A dish's class page: the principles it inherits, how it binds their roles, the Eaten badge its plates earned, and Exact amounts closed on one line that already says the box is empty.

On the phone the same box is a SwiftUI DisclosureGroup labelled Parameters, collapsed until tapped, with the same placeholder and a Save parameters button guarded the same way. The phone also makes sure a background refresh never wipes a draft being typed. It replaces the field only while the draft still matches the stored value.

Where the box appears

The two clients disagree about which classes have a box. The web shows Exact amounts on dishes and variants and never on a principle, which matches the doc: a principle is an abstract class, and an abstract class has no amounts of its own. On a dish the line sits under the role bindings, on a variant under the overrides and the verdict, and in both cases above the list of versions. The phone shows Parameters, with the verdict box beside it, on every class, principles included. The phone also has no Make my version form. Neither difference breaks the invariant, since the box starts closed on both. Still, when the architecture doc says the phone covers the web's surfaces, read that as mostly.

Closed is not secret

Encapsulation here is a presentation rule. The engine returns parameters in every class payload, and both clients hold the value before Dad opens anything. Nothing in the API withholds it. For a record that Dad alone reads, that is the right scope, and it is worth saying plainly so nobody mistakes the disclosure for access control.

What I am given follows the same idea at the prompt level. The compact tree handed to every reading and to the kitchen conversation carries ids, names, glosses, cuisines, summaries and statuses, and no parameters. A plate's conversation gets the plate's reading and the classes it is linked to, in that same compact form. Only a conversation about one class receives that class's whole row, amounts included. The box opens for me when the conversation is about that box.

Hiding a different thing

OO Quest's cooking track has an encapsulation section of its own, and it hides something else. Track 4 keeps the chemistry, the Maillard reaction and its friends, behind public methods such as browning the meat for more flavor. Sizzle puts the why in front, the physics and chemistry in plain words, and closes the box on the numbers. Both are encapsulation. They differ in what counts as the implementation: for Track 4 it is the science, and for Sizzle it is the measurements of one realization.

Principle: Decide what your reader should carry away from a page, put that first, and close the rest by default. Then decide separately who may read the closed part, because hiding it from the eye and withholding it from a caller are two different promises.

Code

cwkSizzle, frontend/src/pages/ClassPage.tsx: the web's Exact amounts box, closed on load (verbatim, lines 231-252)·typescript
function Parameters({ value, onSave }: { value: string; onSave: (value: string) => void }) {
  const [open, setOpen] = useState(false);
  const [draft, setDraft] = useState(value);
  useEffect(() => setDraft(value), [value]);
  return (
    <section className="parameters">
      <button className="disclosure" onClick={() => setOpen(!open)}>
        {open ? <ChevronDown size={15} /> : <ChevronRight size={15} />} Exact amounts {value ? "" : "(none yet)"}
      </button>
      {open && (
        <>
          <textarea value={draft} onChange={(e) => setDraft(e.target.value)} rows={4} placeholder="Grams, times, temperatures — private detail." />
          <div className="row-end">
            <button className="primary" disabled={draft === value} onClick={() => onSave(draft)}>
              Save
            </button>
          </div>
        </>
      )}
    </section>
  );
}
cwkSizzle, clients/apple/SizzleMobile/Sources/Views/ClassView.swift: the phone's Parameters disclosure (verbatim, lines 168-173)·swift
            DisclosureGroup("Parameters") {
                TextField("Grams, times, temperatures — private detail.", text: $parameters, axis: .vertical)
                    .lineLimit(3...10).textFieldStyle(.roundedBorder)
                Button("Save parameters") { run { try await model.client.patchClass(id, fields: ["parameters": parameters]) } }
                    .disabled(busy || parameters == (klass.parameters ?? ""))
            }
cwkSizzle, backend/store.py: the compact tree in my prompts, with no amounts in it (verbatim, lines 497-503)·python
    def catalog(self) -> dict[str, list[dict[str, Any]]]:
        """Dad's tree, compact, for Pippa's prompts (data, never instructions)."""
        principles = self._all("SELECT id, name, summary, status FROM classes WHERE kind = 'principle' ORDER BY name")
        dishes = self._all(
            "SELECT id, kind, name, gloss, cuisine, status FROM classes WHERE kind IN ('dish', 'variant') ORDER BY name"
        )
        return {"principles": principles, "dishes": dishes}

External links

Exercise

Open a detail page in software you build and sort everything on it into two piles: what a reader should take away, and the specifics of one case. Put the first pile at the top and move the second behind a disclosure that starts closed and says when it is empty. Then list every caller that receives the page's data, including any model prompt or export, and decide for each whether the closed pile belongs in what it receives.
Hint
Keep the closed state honest: an empty marker on the collapsed line, and a save control that stays disabled until something changed. For callers, compare the payload you send with the fields that caller actually uses, and cut the rest at the source.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.