CLCTN · PLACES · THE FIRST APP-LEVEL DIMENSION · SEPTEMBER 2026
Where is it? The question no collection app answers.
Wine forced the question, but wine is not the answer. A bin field on the wine type solves it for one type and leaves thirteen others asking the same thing. A place is not a property of a bottle — it is a thing in the world that holds objects, and it holds objects of every type at once.
That makes it the first concept in the app that cuts across collections rather than living inside one. Types are vertical; places are horizontal. The living-room shelf holds eighty-four records, thirty-one books and a shoebox of comics, and no screen in the app can currently say so.
The hard requirement is that it must cost nothing to ignore. One wine fridge is a complete and legitimate cellar, and that person must never meet a rack, a bin, or a setup screen.
01 · THE LADDER — ONE FEATURE, FOUR COLLECTORS
Same field, same data model, four amounts of ceremony. Nobody chooses a tier and nothing announces itself — you climb by typing more, and you can stop at any rung forever.
{{ l.k }}
{{ l.who }}
{{ l.b }}
WHAT THE FIELD READS
{{ l.path }}
{{ l.note }}
02 · PLACES ARE MADE BY TYPING, NEVER BY MANAGING
The single decision that keeps this from becoming a chore. There is no "set up your cellar" screen and there never will be. Places come into existence the first time you name one on an object, exactly like a tag — and from the second object onward the field is autocomplete, so the real cost is one tap.
THE FIELD, THIRD BOTTLE OF THE EVENING
WHERE IT IS
Rack 3
TYPED 6 CHARS
{{ s.path }}
{{ s.holds }}
{{ s.tag }}
Typing a separator creates depth on the fly — Cellar / Rack 3 / Bin 12 makes three nested places in one go, and each rung is independently searchable afterwards. Nobody has to know that is what happened.
{{ r.k }}
{{ r.t }}
{{ r.b }}
03 · THE ENTITY
Small on purpose. A place is a name, a parent, and some optional facts about the environment. Everything else is derived by counting the objects that point at it.
PLACE · TABLE
{{ schema }}
{{ e.k }}
{{ e.b }}
04 · OCCUPANCY — A PLACE YOU CANNOT COUNT IS JUST A LABEL
A path tells you where one object is. It cannot tell you what is in Rack 3, whether the fridge is full, or how much of the loft you have actually got through. Counting is not a feature layered on top of places — it is most of the reason to have them.
Nothing below is stored. Every number here is a query over objects pointing at a place and its descendants.
{{ c.k }}
{{ c.v }}
{{ c.b }}
ROLLUP · A VIEW, NEVER A COLUMN
{{ rollupCode }}
MODE 01 · OPEN
No capacity known
A shelf holds what fits on it. No bar, no percentage, no sense that a number is missing — just what is there, split by type.
115 objects
{{ r.n }} {{ r.c }}
MODE 02 · COUNTED
Capacity, no fixed positions
A wine fridge is rated for 48, a longbox for 300. Exceeding it is reported plainly rather than warned about — the rating is wrong more often than the cellar is.
41 of 48
MODE 03 · SLOTTED
Capacity and a layout
An album page has twenty slots in a fixed grid, so it is drawn as itself. Which slot is free becomes a glance, and the address stops being a string.
SAFE › ALBUM 2 › PAGE 7 · 14 OF 20
{{ s.label }}
Places
4 places · 1,041 objects placed
{{ p.name }}
{{ p.meta }}
{{ c.t }}
{{ p.fillLabel }}
{{ p.total }}
Unplaced
no action needed
1,204
{{ o.k }}
{{ o.b }}
05 · TYPOS — KEEPING THE STRING, LOSING THE RISK
The honest weakness of typing: one slip makes a twin place, and the object is effectively gone — it is somewhere, but not anywhere you will look. The fix is not to take the typing away. It is to make the common slip impossible at the data layer, catch the uncommon one at the point of creation, and make the survivors trivial to merge.
Four layers everyone gets without knowing, and two switches for people who want the structure nailed down.
THE SLIP, CAUGHT BEFORE IT HAPPENS
{{ typedGuess }}
{{ s.path }}
{{ s.sub }}
{{ s.tag }}
{{ s.path }}
{{ s.sub }}
{{ s.tag }}
THE MECHANISM
{{ normCode }}
{{ l.k }}
{{ l.t }}
{{ l.b }}
{{ o.k }}
{{ o.t }}
{{ o.b }}
{{ o.demo }}
{{ o.demoNote }}
THE STANCE
{{ stance }}
06 · WHAT EVERY TYPE CALLS IT
One manifest key — placeLabel — and the engine is written once. The type does not declare whether it has places; every type does. It declares only what its owners call the thing, because a record lives in a crate and a bottle lives in a bin and calling both of them "location" is how apps end up feeling like databases.
The right-hand column is the honest test of whether this generalises. It does — and in five of these types it answers a question that is currently unanswerable in any app the user could be coming from.
TYPE
PLACELABEL
TYPICAL PATH
WHAT IT LETS THEM ASK
{{ t.type }}
{{ t.label }}
{{ t.path }}
{{ t.asks }}
07 · THE SIMPLIFICATION THIS BUYS
Once a place is a real thing, it can carry the facts about the environment — and those facts stop being repeated on every object inside it. Wine's storage field disappears. The fridge knows it is temperature-controlled; the four hundred bottles inside it should not each be asked.
BEFORE — PER OBJECT
{{ beforeCode }}
Asked once per bottle, answered identically four hundred times, and silently wrong the day the fridge breaks.
AFTER — ON THE PLACE
{{ afterCode }}
Answered once. Changing it updates every object at once — and a comic in a damp garage now gets the same benefit for free.
AND ONE MORE, TIED TO CAPTURE
A sweep session already knows the type, because you are standing at one crate. It should know the place for the same reason. Set the place once at the start of a sweep and every object captured is stamped with it — forty records land already shelved, and the field that would have been forty taps becomes one. Session context was worth having for matching; it turns out to be worth just as much for placement.
08 · WHERE IT SURFACES
{{ s.k }}
{{ s.t }}
{{ s.b }}
09 · MATURITY — PARKED, WITH ONE CONSTRAINT WORTH REMEMBERING
Agreed on the principle: maturity is derived and never stored, the same way value is derived from paid. The technical shape can wait. The one thing worth writing down now, because it constrains the manifest rather than the implementation: if maturity is to be sorted and filtered — and the whole point of the drinking window is a "drink tonight" shelf — then it has to be expressible in SQL, not just in Swift.
Which means the window must be stored as plain integer columns rather than an opaque blob, and the band is a CASE over those columns and the current year — cheap enough to be a generated column or a view, and indexable either way. Store two or four integers, compute the five bands in the query, and the "special attribute" you were worried about turns out to be ordinary data with an unusual reader.
The only genuinely awkward part is that the answer changes at midnight on New Year's Eve without anything writing to the database — so any cached count of "ready to drink" has to be invalidated by date, not by mutation. Small, but the kind of thing that is annoying to discover later.
10 · STILL OPEN
{{ o.k }}
{{ o.q }}
{{ o.b }}