#[non_exhaustive]pub enum Region<'a> {
Band,
Sidebar,
Pane,
Split,
Columns,
TabGroup,
Modal,
Bespoke {
name: &'a str,
},
Widget {
name: &'a str,
},
}Expand description
A named part of a screen.
The thing makeover-geometry deliberately does not name: it names the space
between things by relationship, and nothing named the things. Six named
members, taken from what the two webview apps actually use, plus
Region::Bespoke for the parts no description should reach. Both apps’
layout.css currently names exactly two things, .raised and .well, so
this layer is absent rather than divergent, which makes it the cheapest of
the schemas to add and the easiest to over-build.
#[non_exhaustive] arrives with Region::Widget, the pairing RowPart
made at 0.9.0 and Readiness at 0.12.0, and for the same reason: the
member after this one should not be a lockstep event across three renderers.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Band
A full-width strip with a title slot and an actions cluster, either of
which may be empty. goingson’s .page-header, Balanced Breakfast’s
.header and .detail-header are all this, differing only in which
slots they fill.
Sidebar
A persistent column beside the content, holding navigation.
Pane
A region of content with its own scroll.
Split
Two panes side by side, where the left chooses what the right shows.
Columns
Peer regions across, all of them equals.
A kanban board’s columns, and the shape Split is not:
a split’s two panes stand in a master-detail relationship, where the
left chooses what the right shows. These choose nothing about each
other. Each is a whole region and the set is the arrangement.
§What it does not carry
How many. The children say, and a count here would be a second source for something the description already states by containing them.
How wide. Peers are equal by definition, so there is no Share to
state. A board whose columns wanted different widths would be a
different member, and no app has one.
What happens when there is no room. Scroll across, wrap, or collapse to one column at a time: all three are right on some host, none is derivable from the description, and every one of them is presentation. A terminal that stacks them vertically is honouring this, not degrading it.
§Why it is not an Arrangement
Arrangement is the page’s shape, and a board is usually a region
inside a page that also has a band over it. Naming it here composes;
naming it there would make a screen either a board or a list-detail and
never a band above a board. It also keeps Arrangement::share
meaningful, which a peer arrangement has no answer for.
TabGroup
A set of panes, one visible at a time, with a Selector::Tabs above.
Modal
Content over a scrim, taking input until dismissed.
Bespoke
A region this crate names the place of and nothing else. The app owns what goes in it.
The escape hatch, and the thing that keeps the description honest about its own limits. A day-plan timeline, a kanban board, a calendar and the paint interaction over the timeline are not describable here and are not going to become describable: a description expressive enough to produce a timeline is a widget library wearing a description’s name.
But a screen containing one still has to be a screen. Without this member the description covers only the boring screens, and the four that make goingson worth using would need a second, undescribed path beside the router. Two paths is how the vocabulary starts drifting from the app again, which is the exact failure this crate exists to end.
So the description says “a thing called day-plan goes here” and stops.
The name is opaque: this crate never interprets it, and no renderer is
expected to know what it means beyond handing the space over.
Widget
A named assembly of things the vocabulary already says.
The third tier, between a primitive and Bespoke.
Stated by Max 2026-08-12 answering the carousel: “something in between a
primitive and a bespoke interface, like a widget, which is just an
assembly of primitives.” Full note: wiki widget-tier.
§What separates it from the two members either side
A primitive is a thing every renderer draws from scratch, and the test
it has to pass is that every host has an honest answer. A carousel fails
that test — a terminal has no carousel — which is the same refusal
Node::Html got and is why the carousel sat unsayable for months.
Bespoke fails it from the other side. Bespoke is for
what one app owns and nobody will build twice, and it carries no
contents: the description names the place and stops. A carousel is
furniture any app would have, and every part of it — an ordered set of
frames, a position, prev and next, a strip of position indicators — is
already sayable. Only the assembly had no name.
So this member is the pair the other two are not: a name and contents. The contents are the assembly, in the region’s own body, said in members that already exist.
§Why the name does not have to be understood
A renderer that recognises the name draws it the way its host does it: a carousel in a webview, a pager with a count in a terminal, a selector in egui. A renderer that does not recognise it walks the body, which is primitives all the way down and which it can already draw.
That is what lets the widget set be open without every renderer
knowing every widget. An unrecognised widget degrades to its assembly
instead of failing, so a second or third party can name one without
three renderers releasing in lockstep to accept it. Contrast
Bespoke, which no renderer can degrade: there is
nothing under it to fall back to.
§What it does not do
A widget is an assembly of things the vocabulary already says, so it buys no expressive power. Anything needing a member the vocabulary does not have is a finding about the vocabulary, and the answer to a finding is to add the member. A widget is never the way a primitive gets added by the back door.
This used to say “it does not make a timeline describable, and the
refusal in the crate header stands unchanged”. The timeline is
describable as of 2026-08-15 – see Track – and it got there the
way the paragraph above says it should have: by adding the two members
that were missing, not by dressing the screen up as an assembly.
Implementations§
Source§impl<'a> Region<'a>
impl<'a> Region<'a>
Sourcepub const fn described(self) -> bool
pub const fn described(self) -> bool
Whether this crate can say anything about the region’s contents.
A renderer walks the description and hands every region it understands
to the right drawing code. This is how it tells the two apart, and the
reason it is a method rather than a matches! at each renderer: there
is exactly one opaque member and there should stay exactly one.
Widget is described, and that is the whole of what
separates it from Bespoke here. Both carry a name
this crate never interprets; only one of them carries contents under it.
A renderer that does not recognise a widget’s name still walks its body,
so there is nothing for it to hand over and nothing it cannot draw.
Sourcepub const fn name(self) -> Option<&'a str>
pub const fn name(self) -> Option<&'a str>
The name an app gave this region, if it gave one.
Bespoke and Widget are the two
members that carry a name, for two different purposes: one says what the
app will fill the space with, the other says what the assembly under it
is called. A renderer dispatching on either wants the string without
caring which member it came from, and writing that matches! at each
renderer is how the two drift apart.