Nylonite

169 The design record Append only

The arguments, kept the way the stock is kept.

Every decision about how Nylonite looks and behaves is written down when it is made and never edited afterwards. When one turns out to be wrong it is not deleted or quietly reworded: a later decision replaces it, and the old one stays where it was with a line saying what replaced it. That is the same rule the stock record runs on, and it is on this page for the same reason — you can only trust a figure if you can see what it used to be.

These 37 cover type, colour, light and layout. The other 132 are the data model, the integrations and the commercial reasoning, and they are not published. 3 of the 37 argue their case from an internal document, so the decision is here and the reasoning is not. Nothing on this page has been reworded to fit it: a line is published as it was written or it is not published.

Published
37
Decisions
169
Later revised
2
  1. D109

    Four surfaces, one design system, two densities

    Bench, Floor, Desk and Document are separate surfaces with separate shells and separate ergonomic rules, served by one token set and one component library at two densities.

    Why The differences are postural, not dimensional: gloved and walking versus seated and comparing. A responsive layout expresses width and cannot express that. But the differences do not reach the components — a panel is a panel in both — so two design systems would be two things to keep in agreement, which is the failure mode this repository has documented a dozen times.

  2. D110

    Navigation is by work, not by entity

    The primary rail groups screens by job — Inbound, Outbound, Integrity, Reference, You — not by table.

    Why The entity menu is a rendering of the schema and requires the operator to know which noun holds the thing they want. It is a substantial part of why the recorded process takes a dozen screens. Grouping by work also gives `Integrity` a name, and that group is where this system's premise lives.

  3. D111

    The scan bar is the primary locator, and a failed resolution is a record

    One always-present input resolves any scannable identifier to its subject and navigates. A scan resolving to nothing writes a resolution-failure record and tells the operator it has.

    Why The fastest path to a record is the barcode on the thing in your hand. And D28 already requires that resolution failures are recorded; the locator is where they arise, so it is where they get written.

  4. D112

    A badge counts work waiting for you, at this site, now

    Every count in the chrome is scoped to the caller's site and to work that is actionable. Zero hides. Totals never appear.

    Why Already the rule behind the weigh badge — *"a badge showing nothing to do is a badge people stop reading"* — generalised rather than invented. A badge reading 1,247 is a decoration; a badge reading 4 is an instruction.

  5. D113

    React for interactive surfaces, server-rendered for documents

    Every interactive surface is React over the existing JSON API. maud narrows to what is genuinely a document: the A4 packing list, paperwork, labels.

    Why Argued from an internal document, so it is not published.

  6. D114

    The client formats values; the server phrases judgements

    Grouping, units, decimals and dates are formatted client-side. Anything reading as a judgement — "due tomorrow", "overdue", "three weeks ago" — is phrased by the server and sent as text.

    Why Formatting is presentation. Phrasing a promise against today is a business rule with tests behind it already, and a second implementation in TypeScript is a second thing that can disagree.

  7. D115 amended by D118 and D126

    Amber is exclusive; green is confirmation, not status

    Amber marks findings and nothing else. Green marks an action that just completed and never a steady state.

    Why One colour, one meaning, which erodes the moment amber is convenient for something else. The green half pre-empts the same argument: a status dot on every healthy row trains people to ignore the colour channel the finding rule depends on.

  8. D116 superseded by D118 and D120

    Two rule weights, no radii

    Kept for the record. What it decided is now decided by D118 and D120.

  9. D117

    Scan focus is claimed by a screen, never stolen by the chrome

    A screen declares its scan target. The locator receives scans only when nothing has claimed them. Scan input sits behind one abstraction from the first commit.

    Why Argued from an internal document, so it is not published.

  10. D118

    The chassis is metal and the data is paper

    The enclosure is a material with physics — blasted hardcoat anodise, one lamp, chamfers, occlusion. Every number and identifier sits on a bright matte instrument face seated into it. The face is themed day and night; the chassis is not.

    Why A dark, textured, richly lit interface and a screen read at arm's length by a gloved operator under warehouse lighting are both requirements, and they are only in conflict if one surface has to do both jobs. Separating them is what a Fluke meter, a weighbridge terminal and an aircraft panel each arrived at independently. It also gives theming an obvious home: an enclosure that changes colour with the time of day is not an enclosure.

  11. D119

    Nothing translucent is laid over text

    No translucent overlay, wash, streak or gradient may sit above running text or a readout. The cover lens is confined to a masked corner region.

    Why It has already broken once: a draft ran a white wash diagonally across the whole instrument face and measurably cost contrast. Gloss bought with legibility is the worst trade available on this screen, and the pressure to make that trade recurs every time something needs to look richer.

  12. D120

    The finish is blasted: isotropic, matte, sparkling at the edges

    Bead-blasted Type III hardcoat. No directional grain anywhere. Noise at two octaves. Micro-sparkle thresholded from the same field and held at the perimeter. Peak specular 5%.

    Why Four drafts built a brushed surface before the finish was checked, and brushed is the wrong part: an anodised instrument fascia is blasted, which is isotropic. The consequences are not cosmetic — they decide whether the highlight is a band or a field, whether the texture has direction, and where the sparkle sits.

  13. D121

    One fixed lamp; only edges move; the pointer moves the viewer

    One lamp in viewport space. Panel bodies are uniformly lit; the four chamfers carry a Fresnel baseline plus a swing weighted by how squarely each faces the lamp. The pointer applies a bounded parallax standing in for a viewer leaning, never relocating the light. Scroll is the primary motion. The lamp is driven by a critically damped spring integrated against real elapsed time.

    Why Brightness on a matte surface is a function of the surface normal, and a flat panel has one normal everywhere — so a travelling highlight across the body is a point light held an inch away, and no amount of tuning makes it look otherwise. Hanging the lamp off the cursor is a torch in your hand rather than a lit room. Both errors shipped in earlier drafts and both were diagnosable from first principles rather than from taste.

  14. D122

    Two materials, one lamp: metal encloses, nylon protects and labels

    Anodised aluminium and ripstop nylon, lit by the same lamp. Nylon appears where the system is padded, carried or tagged — boots, straps, woven tags — and never carries a number.

    Why The company sells nitrile gloves, hair nets and protective equipment and is called Nylonite; a textile in the system is the trade rather than a flourish. What keeps it from becoming decoration is a boundary with no exceptions. Sharing the lamp is what makes them read as two finishes in one room rather than two unrelated textures, and their different sheen behaviour — a fibre bundle is a cylinder, a blast pit is not — tells them apart in motion.

  15. D123

    Materials own their CSS; primitives accept no class or style

    No utility-class framework. Tokens are plain CSS custom properties; each material is one stylesheet owning its whole recipe; primitives expose closed enums and never a `className` or `style` prop. The light solver binds to `data-material` attributes.

    Why A utility framework's affordance is composition at the call site, which is exactly what a material must forbid — there is no subset of the anodise recipe that is still anodised. Adopting one would mean hand-writing the recipe anyway and configuring a second spacing and colour vocabulary to duplicate tokens that already exist. Runtime CSS-in-JS is ruled out separately: custom properties are already the dynamic channel and are far cheaper at sixty frames a second.

  16. D124

    Spacing belongs to the parent, and the laws are lint rules

    Components set no outer margin; separation is the parent's `gap`. The material system's laws are enforced in CI, and the TypeScript token constants are generated from `tokens.css` with a test that fails when the committed file differs.

    Why Every law in this document is one somebody will have a good local reason to break — amber is convenient for warnings, a wash makes a panel look richer, a margin fixes one screen. Written down, they last about a quarter. The checks worth having first:

  17. D125

    The literal hardware comes off; the physics stays

    No relief groove, no bezel catch, no chamfer rings, no riser under a key. The material keeps its light model, its grain, its occlusion and its edge behaviour.

    Why Those four were hardware traced from photographs of hardware, and they are what made a considered material read as a decade-old interface. The line worth holding is between *how a surface behaves* and *what a surface has bolted to it*: a blasted finish scattering light is the first, a moulded key cap is the second. Everything that survived is derivable from a light and a roughness; everything removed had to be drawn from memory of an object.

  18. D126

    Four colour channels, and amber is not one to borrow

    Nitrile violet means *act*. Steel means *state*. Amber means *a finding*. Green means *an action that just completed*. No channel does two jobs and none is reassigned.

    Why There was no way to say sealed, or open, or nearly out, and the obvious fill was a muted amber — which is verbatim the erosion D115 exists to prevent. Both halves of that are true at once: the gap was real and the reserved colour was the wrong thing to fill it with. Steel is cool and industrial and cannot be mistaken for the violet that means act.

  19. D127

    Absence is drawn, and the drawing is dimensioned

    Empty and low-content states carry material weight. A container with nothing in it is drawn as a container, from the dimensions the record actually holds.

    Why An empty carton rendered as a line of faint text made the emptiest state and the fullest state occupy the same amount of nothing, which is not what an empty box on a bench looks like. And ornament measures something or it is decoration: drawing a *generic* carton would have been a picture, where drawing the preset's own proportions is a measurement.

  20. D128

    Edges are occlusion; no edge carries colour

    A raised surface is defined by the dark contact shadow it casts, plus at most one catch along the lit edge. No bright line traces a shape, and no edge is tinted.

    Why Three passes each failed the same way. A solid border runs corner to corner at full strength, which is what a line does. A gradient masked to a hairline around all four sides is that border drawn more expensively. And dispersion on the cover glass — cool where light enters, warm where it leaves — is real optics and also an orange line down one side. **A coloured border does not stop being a coloured border because it was derived.** Occlusion defines a silhouette at any strength and can never glow.

  21. D129

    Square corners with soft light

    Radii are zero. Controls keep two pixels. `corner-shape` is not used.

    Why Squircles were the wrong reading of a request for something organic: what was wanted was light that falls off, not corners that round over — and rounding them as well made this the same soft UI as everything else. Sharp geometry under soft light is coherent and rarer. Two pixels on an input is not a radius anybody reads as one; it stops a corner pixel looking like a defect.

  22. D130

    The bundle ships with the API, and the document is never cached

    The client is built into the server image and served from the same origin, at the root. Content-hashed assets are cached for a year and marked `immutable`; `index.html` and every client route are `no-cache`.

    Why The session is a cookie and the client sends `credentials: "same-origin"`, so a separately-hosted bundle trades a working sign-on for a SameSite problem. The mount is not `/` because a root SPA fallback answers an unmatched API path with a page instead of a 404, and that 404 has already been load-bearing in a diagnosis.

  23. D131

    A gate that renders the page

    CI runs the built bundle in a headless browser and asserts what came out: every material carries a layer, no decorative layer escapes what it decorates, the states the design system draws are reachable from a fixture, and both themes resolve. Screenshots are kept as an artefact.

    Why The other gates read source and all stayed green through three defects at once — a component that rendered nowhere, a layer painting across the whole chassis, and a rule shipping the opposite of its own comment. Two of those had been *reported as fixed*.

  24. D134

    The Floor shell docks the primary action, and the gate renders it at the size of the device

    `FloorShell` is a column that fills the viewport: a thin header, a scrolling body, and a **dock** pinned to the bottom holding the primary action. The dock is a slot, it is opaque, and the render gate visits every Floor screen at a handheld viewport rather than at the bench's.

    Why This is the whole of the postural difference D109 asserts. A bench monitor is read top-down at eye level; a handheld is held at waist height and driven with the thumb of the hand holding it, so the reachable third of the screen is the bottom third and the top is where the least reachable control on the device lives. Putting the difference in the shell is what stops it being decided again, differently, on every screen.

  25. D135

    A capture session is not in the URL

    The subject being captured lives in `useCapture` and not in the path. The route table stays; the router does not arrive with this screen.

    Why *"The first screen needing an identifier in its URL replaces it. Capture will be that screen."* It turns out to be the screen that argues the other way. D133 makes the whole session one act, so nothing the operator types is durable until Record — and a link that reopened `/capture/17e1…` after a refresh would faithfully restore the subject while silently dropping the three figures beside it. **A URL that promises a resumability the model deliberately refuses is worse than no URL**, because it is a promise the operator has no way to check.

  26. D144

    The API answers under `/api`, so the screens can have the root

    Every JSON endpoint moves under `/api`. The server-rendered documents stay at `/app` until they narrow to `/print` (D113). The client will take the root.

    Why Seven of the nine names the client wants are free at the root today, so this could have been solved by choosing different words twice. That solution decays: it holds only while everyone adding an endpoint remembers to check a list kept somewhere else. A prefix makes the collision impossible to write, which is the difference between a convention and a structure.

  27. D147

    The front door is what is waiting for you, at this site, now

    `/` reads `GET /api/work` and draws the work waiting at the caller's site, grouped by job as D110 groups the rail. One endpoint serves both this screen and the rail's badges.

    Why Two would be two computations of one fact, free to disagree — the failure this repository has recorded more times than any other. And scoping server-side means **the client cannot render a total it was never sent**, which is a stronger guarantee than a rule about what the client ought to filter.

  28. D148

    The rail is drawn from one list, and the list is checked

    D110's work rail is built. Its destinations are one exported list grouped by job — Outbound, Integrity, You — carried on Bench and Desk, absent on Floor, with badges from the same `GET /api/work` the landing screen reads.

  29. D149

    Presence is static, focus is claimed: the locator reaches the chrome

    D111's locator is in the chrome on every screen behind a session, except the one that owns the scanner. **Whether it is drawn is decided by the route manifest; whether it holds the caret is decided at runtime, and the answer is always no.**

  30. D150

    Finding an order is a screen, and a commitment is a path

    `/orders` replaces `/app/orders`, and `/pack/:fulfilment` carries a commitment in the URL. `const FULFILMENT` is gone.

  31. D151

    The pack queue moves, and its grouping is one function

    `/pack` is the queue and `/pack/:fulfilment` is the bench. The four groups come from `packing::stage`, a pure function of two numbers that **the server-rendered page and the JSON endpoint both call**.

    Why The obvious port copies the grouping into the client. That would be two definitions of "ready to pack", free to disagree, and which one an operator saw would depend on which screen they opened. `stage(picked, committed)` is tested beside itself, the maud page now calls it, and the two screens were checked against real data giving the same answer — three ready and one packed, on both.

  32. D152

    The gate enforces, and a phone gets the work before the menu

    `/sign-in` is React, every screen behind a session is gated, and signing out is an act rather than a page. The rail is drawn once and follows the work on a narrow screen instead of preceding it.

  33. D153

    The field asks in the unit the instrument reads

    Capture asks for lengths in **centimetres** and weight in **kilograms**, sends both with their unit, and shows stored lengths back in centimetres. Nothing is scaled on the client.

  34. D159

    The chrome is one component, and below 48rem it stacks rather than wraps

    All four shells draw the same `Chrome`. Below 48rem it stacks — identity, then where-and-who, then the locator across the full width — and **nothing is dropped to make room**. Above 48rem it is one line, which is where it already was.

  35. D161

    The frame is mounted once, and a screen draws only its work

    `LightRoom`, `SessionProvider`, `Gate`, the chrome and the shell are rendered above the route switch, in `Router` and `Framed`. A screen's `render` returns its work and nothing else. The two regions a shell owns but a screen's state fills — Floor's dock (D109/D134) and Desk's evidence panel (D111) — are drawn by the screen and portalled into containers the shell always draws.

    Why The shell now outlives the screen, and the dock's buttons and the evidence panel's contents are the *screen's* state. Passing them upward would mean either the shell holding state it does not own, or the router calling a screen for its return value rather than rendering it — and the second is not a thing React does, because a function that calls hooks and is not rendered as a component has no instance to keep them in. A portal moves the DOM and nothing else: the dock is still a child of the screen for context, for events and for `useCapture`, and it lands where the shell put the container.

  36. D162

    Below 62rem the rail is a sheet, and the chrome that opens it is pinned

    On Bench and Desk below 62rem, the work rail is drawn as a full-screen opaque sheet opened by a **Menu** key in the chrome, and the chrome is `position: sticky` at those widths. Above 62rem nothing changes: the rail is the column it has always been and neither the key nor the dialog semantics exist. Floor and Plain have no rail and are untouched.

  37. D165

    The app is a shell around the client, not a second client

    The iOS and Android app is a Tauri 2 shell that runs the same React bundle a browser runs. Three seams are rebound for a device — where the server is, which HTTP client reaches it, and what credential it carries — and nothing else. React Native is refused.

    Why Argued from an internal document, so it is not published.

Back to the film