222. A showcase is a window, a bar and seven screens
Date: 2026-08-30
Status
Accepted. Restructures the gallery ADR-0110 created.
Context
ADR-0110 split one sidebar of every widget in the toolkit into a gallery of screens, because a single pane stopped being able to hold the catalog somewhere around the eleventh control. That was right, and the rule it used to decide what went where — one screen per widget family — did not survive the catalog growing to fifty-one widgets. Twelve screens later it had produced a gallery that nobody could read:
Controls,ValuesandTextwere three tabs you had to visit in turn to see what one screen’s worth of chrome looks like. Nobody builds a window of only checkboxes.OverlaysandNotificationswere the two halves of one comparison — should this float or should it sit in the layout? — with a tab between them, so the comparison could not be made.Tabs,ScrollingandChooserswere all “how do I get around”, filed under three different widget names.- Twelve screens, ten digits.
Ctrl+1… ran out, and ADR-0110’s own note admitted that the last screen was reachable by the strip and the menu and not by a key.
Every screen was also a plain column, so each was a single tall stack that the
gallery’s viewport scrolled. On a maximized window that is one narrow ribbon of
content down the left of a very wide screen, and masonry — built for the Charts
screen, which was the only one using it — was sitting right there.
And the content was Item 1, Row 3, Series 1, Peak. A label that is a
placeholder cannot show whether a sortable header, a shared crosshair or a
wrapped paragraph reads; only whether it draws.
Finally, the window itself was a title bar with a theme button on it. There was
no application menu anywhere — menubar existed and the only one in the
repository was a demo inside a screen, which is a widget being shown rather
than a widget being used.
Decision
Seven screens, each a question rather than a widget family. Basic,
Panels, Overlays, Forms, Navigation, Collections, Charts. Seven is
inside ten, so every screen has an accelerator — which is the second thing the
restructure bought and the one that closes ADR-0110’s open note.
Every screen is a Wall: a heading, a line of prose and a masonry of cards.
One shape for seven screens, and a type rather than a convention because it was
a convention first and four screens drifted off it. A card is as tall as its
contents and no two are the same height, which is the case masonry exists for
(ADR-0196).
Every card is a card or a group-box. A wall of bare columns is a wall
with no edges in it; the panel is what makes a group of controls one thing.
A document supplies the cards it can and Java appends the rest to the same
masonry. Four screens are basic.kdl, panels.kdl, overlays.kdl and
forms.kdl, each with a masonry at its root, and Panes.wallOf refuses a
document whose root is anything else — a column wrapped round it during an edit
is a perfectly good document that quietly grows a second wall with different
columns. The Java cards are the ones markup genuinely cannot write: an expression
(disabled when the count is zero), a list the application edits (banners), a
channel that hands values back (multiple, autocomplete, tree), and series
data.
The window is three bands: a menubar, a bar, and the gallery. File, Edit
and Help, built in Java because half their rows need a Host — opening a dialog,
floating a HUD, closing a window — and a Runnable passed to a constructor needs
no name at all. The bar under it is a document: two startup readings, the leagues
counter, and a toggle that is the global light switch.
The theme is one fact in two spellings. app.theme is a name, because a
radio-group, a segmented and a select pick from a list; app.light is a
boolean, because a switch’s value is a boolean by definition — Toggle.resolved
reads source.get() instanceof Boolean and falls back to its own flag otherwise,
so binding it to "light" gives a switch that never moves. Both are written in
pickTheme and nowhere else, which is four lines and the single route every
theme control in the window goes through.
The content is Middle-earth. Not decoration: a table of nine companions with
a Kindred column that repeats and a Leagues column that does not shows a
sortable header doing something that Row 1…Row 6 cannot, and real names are
of wildly different lengths, which is what a layout has to survive.
The window opens maximized (ADR-0221), because a wall of cards is a layout whose subject is how much fits.
Alternatives considered
- Keeping twelve screens and adding a second row of tabs. Two rows of tabs is a menu with the wrong widget, and it does not fix the comparison between a banner and a toast being a tab apart.
- Making every screen Java, or every screen a document. All-Java loses §9’s whole argument — a designer moving a card without a compiler. All-document is impossible: five of the cards need an expression or a channel markup does not have. The split is the interesting part, so it stays and each absence is documented where it is.
- One
Widgetper screen that returns aMasonry, with the document’s cards merged inside it. What this does. The rejected version was a screen that returned its document’s wall and a second wall of its own — simpler to write and visibly wrong, because a masonry packs by column height and two walls cannot agree on where a column ends. - A card per notification kind, sharing one state object. Rejected in favour of a stateful widget per card: the hidden set only affects the resident banners and the spawned list only the stack, so two independent states mean neither card rebuilds when the other changes — and three cards can go under three different columns where one tall widget can only go under one.
- A platform menu bar. On three of the four targets there is not one. A
menubaris drawn by this toolkit, styled by the same stylesheet and routed through the same router, soF10, the arrows and the accelerators are one implementation (ADR-0163). The one menu handed to the desktop is the tray’s, which is why that one is an ordinaryMenuvalue. - Dropping
WindowActionsnow that the menu holds handlers directly. Tried, and put back:overlays.kdlpressesapp.open-menuby name, and only a registry can turn a string into a call. The two halves of §9 are now visible side by side in one window, which is better than either alone.
Consequences
- Twelve golden images become eleven, at 1200×900 instead of 900×560. The old size was chosen for a single-column screen; a two- or three-column wall needs the width or the picture is of a layout the application never shows.
- A narrow golden joins them.
masonry’s columns are a count and not a media query, so two columns at 1200 are two columns at 720 — half as wide and twice as tall.gallery-basic-narrowasserts they still fit, because a card with a minimum width would overflow rather than wrap and §10’swrapis not built. Set.ofis banned from anything a golden image prints. Its iteration order is randomized once per JVM, so a caption built withString.join(", ", checked)came out one way on one run and the other way on the next. One thousand pixels of caption failed the Collections image at random until the tree’s initial checked set became aLinkedHashSet.SectionHeaderbecomes public and becomes the screen title. Atext.screen-titledid the job for eleven screens and stopped being honest: a class is something any node can wear and a heading is a kind of node. Being an element type is what lets the stylesheet say “a heading inside an affixed section takes a surface” without a second class travelling beside it.Scrollingbecomes a card and keeps the nested-scroll ban. It used to be the one screen the gallery did not wrap in a viewport, because §2.4 bans nested same-axis scrollers. It still is — but as a card in a two-column wall, so the screen fits without needing to scroll at all. The wall is what made the ban affordable.- Section names are one word each. A section’s name becomes its
#section-<name>and its button’s#jump-<name>, and#jump-bag endis not a selector. Lower-casing is the whole of the transformation, which keeps the ids something a stylesheet and a test can both write down. - The two
statisticcards carry no card title. A statistic has its own label, so a titled card printed the same words twice and the wall read as though two of its nine cards had stuttered. - What is now expensive to reverse: the wall. Seven screens, four documents and every test that finds a card by id assume a masonry of cards. Going back to a column per screen is a rewrite of the same size as this one.