| 320 |
320 |
|
//! Those are presentation, they stay the renderer's, and [`Track`] carries none
|
| 321 |
321 |
|
//! of them. What changed is the conclusion, not the principle.
|
| 322 |
322 |
|
//!
|
| 323 |
|
- |
//! A kanban board and a calendar have not been measured the same way. They are
|
| 324 |
|
- |
//! open questions rather than settled refusals, and whoever needs one should
|
| 325 |
|
- |
//! count the missing members first.
|
|
323 |
+ |
//! ## The other two, measured 2026-08-15
|
|
324 |
+ |
//!
|
|
325 |
+ |
//! The same sentence refused a kanban board and a calendar. Both were counted
|
|
326 |
+ |
//! the way the timeline should have been, and neither came out where the
|
|
327 |
+ |
//! refusal put it.
|
|
328 |
+ |
//!
|
|
329 |
+ |
//! **Kanban: one member, and it is [`Region::Columns`].** Held against
|
|
330 |
+ |
//! goingson's `tasks-kanban.js`, every card fact was already sayable — title,
|
|
331 |
+ |
//! project, due date, the blocked and unblocks badges, subtask progress, the
|
|
332 |
+ |
//! open action and the context menu are `Row`'s existing parts. A column is a
|
|
333 |
+ |
//! heading, a count and a list. What nothing could say was that the columns are
|
|
334 |
+ |
//! *peers*: [`Arrangement`] offers list-detail and sidebar-content, and a board
|
|
335 |
+ |
//! described as either is a lie about the screen. Dragging a card between
|
|
336 |
+ |
//! columns never entered into it — a drop's effect is "set status", a discrete
|
|
337 |
+ |
//! action `Row`'s menu already carries, and the drag itself is affordance.
|
|
338 |
+ |
//!
|
|
339 |
+ |
//! **Calendar: no members, and no consumer.** A month grid renders today as a
|
|
340 |
+ |
//! [`Table`](crate::Column): seven weekday columns, weeks as rows, blanks for
|
|
341 |
+ |
//! the offset. goingson's monthly review reached this conclusion before this
|
|
342 |
+ |
//! note did and describes its month as a list of days that had something on
|
|
343 |
+ |
//! them, marking today with an ordinary badge. What the tree actually contains
|
|
344 |
+ |
//! is two completion heatmaps — one scalar per day — and no calendar at all:
|
|
345 |
+ |
//! `events-calendar.js` was deleted 2026-08-11 in favour of a segmented list,
|
|
346 |
+ |
//! and no MNW template mentions one. So the refusal was defending a screen
|
|
347 |
+ |
//! nobody has. If one is built, measure again; the facts already fit and only
|
|
348 |
+ |
//! the grid's shape would be in question.
|
|
349 |
+ |
//!
|
|
350 |
+ |
//! The pattern worth keeping from all three: one sentence refused three things
|
|
351 |
+ |
//! for one reason, and the reason was wrong three different ways. Count the
|
|
352 |
+ |
//! members.
|
| 326 |
353 |
|
//!
|
| 327 |
354 |
|
//! [`Region::Bespoke`] remains for the genuinely app-owned, and its
|
| 328 |
355 |
|
//! justification does not depend on the withdrawn claim. The
|
| 1836 |
1863 |
|
Pane,
|
| 1837 |
1864 |
|
/// Two panes side by side, where the left chooses what the right shows.
|
| 1838 |
1865 |
|
Split,
|
|
1866 |
+ |
/// Peer regions across, all of them equals.
|
|
1867 |
+ |
///
|
|
1868 |
+ |
/// A kanban board's columns, and the shape [`Split`](Self::Split) is not:
|
|
1869 |
+ |
/// a split's two panes stand in a master-detail relationship, where the
|
|
1870 |
+ |
/// left chooses what the right shows. These choose nothing about each
|
|
1871 |
+ |
/// other. Each is a whole region and the set is the arrangement.
|
|
1872 |
+ |
///
|
|
1873 |
+ |
/// # What it does not carry
|
|
1874 |
+ |
///
|
|
1875 |
+ |
/// **How many.** The children say, and a count here would be a second
|
|
1876 |
+ |
/// source for something the description already states by containing them.
|
|
1877 |
+ |
///
|
|
1878 |
+ |
/// **How wide.** Peers are equal by definition, so there is no [`Share`] to
|
|
1879 |
+ |
/// state. A board whose columns wanted different widths would be a
|
|
1880 |
+ |
/// different member, and no app has one.
|
|
1881 |
+ |
///
|
|
1882 |
+ |
/// **What happens when there is no room.** Scroll across, wrap, or collapse
|
|
1883 |
+ |
/// to one column at a time: all three are right on some host, none is
|
|
1884 |
+ |
/// derivable from the description, and every one of them is presentation.
|
|
1885 |
+ |
/// A terminal that stacks them vertically is honouring this, not degrading
|
|
1886 |
+ |
/// it.
|
|
1887 |
+ |
///
|
|
1888 |
+ |
/// # Why it is not an `Arrangement`
|
|
1889 |
+ |
///
|
|
1890 |
+ |
/// [`Arrangement`] is the page's shape, and a board is usually a region
|
|
1891 |
+ |
/// *inside* a page that also has a band over it. Naming it here composes;
|
|
1892 |
+ |
/// naming it there would make a screen either a board or a list-detail and
|
|
1893 |
+ |
/// never a band above a board. It also keeps [`Arrangement::share`]
|
|
1894 |
+ |
/// meaningful, which a peer arrangement has no answer for.
|
|
1895 |
+ |
Columns,
|
| 1839 |
1896 |
|
/// A set of panes, one visible at a time, with a [`Selector::Tabs`] above.
|
| 1840 |
1897 |
|
TabGroup,
|
| 1841 |
1898 |
|
/// Content over a scrim, taking input until dismissed.
|
| 1927 |
1984 |
|
pub const fn depth(self) -> Depth {
|
| 1928 |
1985 |
|
match self {
|
| 1929 |
1986 |
|
Self::Band | Self::Sidebar | Self::Split | Self::TabGroup => Depth::Flat,
|
|
1987 |
+ |
// Flat, and it is the container rather than the columns. Each
|
|
1988 |
+ |
// column is its own region and brings its own depth; a well here
|
|
1989 |
+ |
// would put a second edge around a row of wells.
|
|
1990 |
+ |
Self::Columns => Depth::Flat,
|
| 1930 |
1991 |
|
// A pane is looked into, the same as a table body or a tag tree.
|
| 1931 |
1992 |
|
Self::Pane => Depth::Well,
|
| 1932 |
1993 |
|
Self::Modal => Depth::Raised,
|
| 1979 |
2040 |
|
| Self::Sidebar
|
| 1980 |
2041 |
|
| Self::Pane
|
| 1981 |
2042 |
|
| Self::Split
|
|
2043 |
+ |
| Self::Columns
|
| 1982 |
2044 |
|
| Self::TabGroup
|
| 1983 |
2045 |
|
| Self::Modal => None,
|
| 1984 |
2046 |
|
}
|
| 3421 |
3483 |
|
assert!(!Selector::Toggle.abutting());
|
| 3422 |
3484 |
|
}
|
| 3423 |
3485 |
|
|
|
3486 |
+ |
#[test]
|
|
3487 |
+ |
fn columns_are_peers_and_a_split_is_not() {
|
|
3488 |
+ |
// The distinction the member exists for. A split's two panes stand in a
|
|
3489 |
+ |
// master-detail relationship; columns choose nothing about each other.
|
|
3490 |
+ |
// Both are flat, so depth cannot tell them apart and the doc has to.
|
|
3491 |
+ |
assert_eq!(Region::Columns.depth(), Depth::Flat);
|
|
3492 |
+ |
assert_eq!(Region::Split.depth(), Depth::Flat);
|
|
3493 |
+ |
assert_ne!(Region::Columns, Region::Split);
|
|
3494 |
+ |
}
|
|
3495 |
+ |
|
|
3496 |
+ |
#[test]
|
|
3497 |
+ |
fn columns_carry_no_count_and_no_share() {
|
|
3498 |
+ |
// The two things a board is always asked to carry and must not. How
|
|
3499 |
+ |
// many is what the children say; how wide is settled by "peers are
|
|
3500 |
+ |
// equal".
|
|
3501 |
+ |
//
|
|
3502 |
+ |
// The guard is the binding itself and it is a compile-time one: adding
|
|
3503 |
+ |
// a field to `Columns` stops this line compiling, which is a better
|
|
3504 |
+ |
// failure than any assertion about it. Written out rather than inlined
|
|
3505 |
+ |
// for exactly that reason.
|
|
3506 |
+ |
let columns: Region<'_> = Region::Columns;
|
|
3507 |
+ |
assert_eq!(columns.name(), None);
|
|
3508 |
+ |
}
|
|
3509 |
+ |
|
|
3510 |
+ |
#[test]
|
|
3511 |
+ |
fn columns_are_described_and_the_escape_hatch_is_still_one_member() {
|
|
3512 |
+ |
// A board's contents are ordinary description all the way down, so a
|
|
3513 |
+ |
// renderer that does not lay them across still draws every column.
|
|
3514 |
+ |
// Stacking them vertically is honouring this member, not degrading it.
|
|
3515 |
+ |
assert!(Region::Columns.described());
|
|
3516 |
+ |
assert!(!Region::Bespoke { name: "timeline" }.described());
|
|
3517 |
+ |
}
|
|
3518 |
+ |
|
| 3424 |
3519 |
|
#[test]
|
| 3425 |
3520 |
|
fn a_span_never_has_zero_minutes_however_it_is_asked_for() {
|
| 3426 |
3521 |
|
// Every renderer divides by this. A caller passing a backwards or empty
|