Five things that said they did something, and didn't
The Starting Point
Two steps to land: a dashboard made of panels each module contributes, and the tail end of the theming work from the day before.
What I actually spent the day on was a pattern I hadn't noticed until it happened five times: software that claims to do something it doesn't. Not bugs exactly — every one of these shipped, passed review, and sat there looking completely reasonable.
What Actually Happened
1. An empty state promising a feature nobody had built
The dashboard read:
Nothing here yet. Modules contribute panels to this page once they are installed and switched on.
That sentence had been there since core was written. There was no registry. There was no way for a module to contribute anything. It was a promise written at the moment somebody imagined the feature, and then left in place as the honest description of a page that did nothing.
The fix was to build the registry, which is what the day was for anyway. But it's worth sitting with: a placeholder that describes the intended behaviour reads exactly like a placeholder that describes the actual behaviour. Nobody reviewing that page would have caught it. You'd have to go looking for the code behind the sentence.
2. A dropdown offering a default that stored nothing
On the record-type screen: "Named by: The first field", as the default option.
It stored null. And null resolved to a column called title that no runtime type has. So the writer copied nothing onto the record, and every list, picker, link and audit entry read #41.
The control was offered, accepted, and did nothing. The screen said one thing and the code did another, and the gap only showed when somebody looked at a list of records they'd just created and asked why they were all called hashtag-a-number.
3. A heading that said "in order" with no way to change the order
The new column-picker screen: "Columns, in order", a tick list, and no reordering control whatsoever. Rows rendered in the stored order and ticking one put it at the end.
The heading was accurate about what the data meant and silent about what you could do. It got written, looked at, and shipped.
The first time I actually used it, my question was four words: so how do I manage order here?
4. A dashboard section called "Waiting on you" with nothing waiting
Our project dashboard had a section headed Waiting on you. Nine rows. Two merged pull requests, a historical finding, a note about CI being paused, an answered gate, and three rows left over from a phase that had closed a week earlier.
Not one thing in it was waiting on anybody.
That one's worse than it sounds, because it's self-reinforcing: a list of finished work under that heading teaches you to stop reading the heading. By the time something genuinely is waiting, the section has been noise for a fortnight and nobody looks.
It now holds nine things that actually are decisions, ordered by how much each blocks, each saying what it blocks and what the options are. The finished work moved to a section called Lately, which is what it always was.
5. The comment that was the bug
This is my favourite, because the comment is the failure.
Adding a panel to the dashboard wrote it at y: 500 — far below everything — with this beside it:
'y' => 500,
// Below everything: the grid settles it upwards on the next
// render, so "at the bottom" needs no arithmetic here.
That's true in the editor, where GridStack compacts the layout. It is false on the page everybody reads, which is a plain CSS grid at stored coordinates — deliberately, so the most-loaded screen in the platform waits for no JavaScript.
So row 501 at 5.5rem a row is exactly where the panel went. About nine metres below the fold.
I clicked Arrange, added the new panels, saved, and they didn't show. Then I clicked Arrange and saved again, and there they were. Opening the editor compacted it. The second save wrote the compacted rows back. The bug fixed itself if you did it twice, which is the most annoying kind.
The comment was the reasoning, the reasoning was wrong, and the comment made the wrong reasoning look considered. A bare 'y' => 500 might have made somebody ask. An explanation beside it closed the question.
What Worked
A colour setting that gave back something near what I asked for.
The dashboard's sub-menu bar had been built as a computed shade of the menu bar, on the reasoning that two tiers of one navigation should look related. I wanted black, white, black in that order, set exactly that, and got something near it. It should set the colour I set, not a shade of it.
The general version is worth keeping: a control that quietly returns something near the value you typed is a control you stop trusting. Relatedness is a real thing to want, but it's for the person choosing the colours to decide, not the code.
The text colour on each bar is still derived rather than chosen — but that's a different case. You can't choose your way into an unreadable menu, and nobody wants to pick a contrast ratio.
And a dependency question that had a better answer than "reuse what exists".
The obvious way to build a panel dashboard was to reuse the reporting module: it already has a twelve-column grid, drag and resize, widgets, PDF export. Everything.
But core can't depend on reporting. Reporting requires core, and it can be switched off — while the dashboard is the page everybody lands on. And most dashboard panels aren't reports anyway: Account::query()->count() doesn't need a data source, a query and a widget config to say 110.
So core owns the surface and modules declare panels into a registry — the fifth registry of exactly that shape in this platform. Reporting will contribute one panel type ("a saved report") without core ever learning what a report is.
Two things moved into core to make it work: the grid library, and a 367-line chart drawer that turned out to have no imports at all. It was a drawing primitive sitting in a reporting namespace. Any module can draw a chart now without pulling in a reporting dependency by the back door.
What I Learned
Every one of these five passed review. They're not sloppy — they're the specific failure mode of describing intent in a place that reads like description of behaviour. An empty state, a dropdown label, a heading, a section title, a code comment. All five are places where you write what you mean, and all five are read as what is.
The pattern I'd take to any codebase:
- When you write a placeholder that describes a feature, write the ticket in the same commit. Otherwise the placeholder becomes the feature's only trace.
- A default option in a dropdown is a behaviour, not a label. If it stores nothing, something has to resolve the nothing, and that resolution is code somebody must write.
- If a heading uses a word like "order", "priority" or "recent", something on that screen must let you act on it — or the heading is describing the database rather than the page.
- A comment explaining why an unusual value is fine deserves more suspicion than the value alone. I'd have caught
y: 500faster with no comment at all. - Section headings on a status dashboard need pruning on a schedule, or they silently become archives and people stop reading them.
And the one about corrections: three of today's five were found by me using the thing. Not by tests, not by review — by clicking a button and noticing that what happened wasn't what the screen said would happen. That's the whole argument for a walk-through per step, and it's why the two steps that merged today without one are on the dashboard as owed, rather than quietly counted as done.
Tech Notes
DashboardPanelRegistry— modules declare; core assembles, arranges, renders. Same shape as navigation, settings, record types and data sources.- Three layout scopes resolving in order: personal → deployment → platform default. Each droppable to fall back to the next, which is why there are two reset buttons and they say different things.
- A panel that throws renders as an empty panel saying so. It's the most-loaded page in the platform; one module's bad afternoon shouldn't take it down.
- Panels arrange by drag and by width/height selects, because the people who most need a short dashboard are often the ones who can't drag.
- 1,197 tests green across thirteen packages. Three of today's new tests exist specifically because a missing assertion let a sentinel value look reasonable.
Zero Focus Dev
Breaking things, learning fast, sharing it all.