Records table
Reach for this when many records need columns, sorting and grouping in one scannable grid — columns: carries every bit of semantics, groupBy: and density: do the rest. The shape most screens take: a records table under the app shell, with grouping, sorting and per-row actions live. Change grouping to see the table regroup without touching the data. A verb on this screen has three homes and none of them is the bar: the primary one is the Fab (fab), view verbs like Export are the view-options surface's header affordance, and a record's own actions belong to the pushed record surface — see Detail's hosting knob.
Spec of record: design-system/components/data/DataTable.d.ts
Live
Real widgets, built from the real kit. Turn a knob and it rebuilds; the theme bar above applies the design system's own tokens — including anything you changed under Customize — and the device selector uses the same sizes the golden baselines gate on, so what you see here is what CI holds the widget to.
Press Focus on any preview to take it to the full window — at a desktop width that renders most devices at 1:1, which is when the widget's own controls become clickable. The bar always states the device and the scale, so a shrunk preview says it is shrunk. Esc leaves.
Which nav the compact posture mounts — the drawer behind the hamburger, or a bottom bar.
The primary tier, and on a records destination the ONLY loud action the bar's neighborhood has. The scaffold renders it in the thumb zone at every window class; a screen never places it itself.
Actions in the drawer's own footer. The drawer carries them at EVERY window class — at 960 and wider it exists only because they do, since the expanded shell otherwise mounts a standing inline rail instead.
Shows when you open the drawer — the hamburger below 960, an edge swipe at 960 and wider, where the rail head's menu button toggles the rail instead. The actions sit in the drawer's own footer; the resting frame is identical either way. — press Focus to make the preview interactive.
The app's identity mark, and nothing else — it never opens a picker. At 960 and wider it sits beside the menu button in the rail's head at the same place in both postures, overhanging the collapsed rail so the bar beside it steps in. Below 960 the drawer's header band states it.
The scope — one value of the app's own kind (here an Area) — as the FIRST item of the bar's subtitle, ahead of the meta. With a picker it is a quiet text button with a chevron; on a phone the whole title block opens the picker as a bottom sheet. A pushed record's bar shows it read-only.
Global commands with no page ("Send feedback", "Help") pinned in the rail's footer under the footer destinations — the same row as a destination, but never lit and never counted in the destination index.
An organization's or partner's logo at the very bottom of the rail and the drawer — identity, never a control. Collapsed it shows its compact square, or nothing when the host gives none.
The second nesting level. Two are sanctioned; the inner band paints the lighter --group-1 tint and indents 16px.
Shows when you set `grouping` first — a "then by" with no "by" is not a partition — press Focus to make the preview interactive.
Per-band aggregates over the numeric columns. inline trails the label; aligned puts each value in its own cell — the Excel subtotal read. Needs a grouping.
Shows when you set `grouping` first — rollups are per-band totals, and without grouping there are no bands — press Focus to make the preview interactive.
Freezes leading columns against horizontal scroll.
Shows when you open the ⋯ at the end of any row — press Focus to make the preview interactive.
Selection mode is enabled by the delete hook — checkboxes exist to feed the bulk bar.
Set to 0 for the empty state.
Shows when you drop `rowCount` to 0, or select rows — the noun is what the empty state and the bulk bar call a record — press Focus to make the preview interactive.
Dart for these values
screen_records() What you can do in it
Capability the preview already carries that no knob can express — you reach it by using the widget. Worth knowing before concluding something is missing: most of this is only clickable once the preview is at Focus size.
- **Search** — type in the field; the table narrows live. Collapsing search never clears the query, which survives as a removable
search:…chip. - **Filter** — the funnel opens the view-options pane. Facets are type-appropriate: enums and text offer distinct values, numbers a min/max, dates a From/To.
- **Chips** — every applied filter renders as a removable chip, and Clear all removes the lot.
- **Narrowed to zero** states the narrowing and offers Clear, rather than the author-a-record empty state.
- **Sort** — tap a column header to cycle ascending, descending, cleared.
- **Group bands collapse** — tap a band to fold its rows away. Nested bands collapse independently, keyed on the group path, so "East loop" under "Open" and "East loop" under "Blocked" are separate.
- **Columns** — the Columns tab hides columns and drag-reorders them; both persist through one
TableViewPrefschannel. - **Resize** — drag the separator after any header cell. Double-width content fits to content.
- **Select** — with
selectableon, the checkbox rail and the tri-state select-all raise the bulk bar. - **Export** — the view-options pane's one header affordance, acting on what the narrowing produced.
What it is made of
The widgets this screen assembles, with their full API below. They used to live on a second entry of their own; one thing you choose gets one page.
RecordsScreen widget
THE FLOORPLAN for a records view — one whole screen over a collection, through one or more switchable bodies (`design-system/components/floorplans/RecordsScreen.d.ts`). **The app supplies rows, columns, labels, row → detail and its actions, and turns knobs; this floorplan owns everything…
Full dartdoc
THE FLOORPLAN for a records view — one whole screen over a collection, through one or more switchable bodies (`design-system/components/floorplans/RecordsScreen.d.ts`). **The app supplies rows, columns, labels, row → detail and its actions, and turns knobs; this floorplan owns everything structural** — the bar on every state (empty included), the primary action's placement, the error / loading / empty / narrowed-to-zero faces under that chrome, the ONE pane on ONE edge that scrolls itself, the docked-vs-pushed detail posture by window class, and theme/density correctness. No knob reaches any of it. It ASSEMBLES the kit's records tier rather than re-implementing it: RecordsTableScaffold (table), RecordsMapView (map), RecordsChrome around AppList / AppCard / AppGallery (list, cards, gallery), and RecordsDetailPane for the docked record. Every knob resolves ONCE into an immutable config published through an inherited scope that the private bodies read — never a hand-threaded prop chain in which one link can be dropped (the standard-view floorplans design, D4.3). **Three hosting postures, resolved from the tree and never from a knob:** * **destination** — an AppScaffold header seam is available, so the chrome rests in THAT bar and the shell's own destination declaration owns the title, the sync control, the scope and the Fab. * **pushed** — no seam: the floorplan hosts itself in a navless body-mode AppScaffold and owns its bar (title, syncStatus, back), its Fab (primaryAction) and its chrome, which still rests IN that bar rather than in a toolbar row under it (`Scaffold.d.ts` RECORDS CHROME HOSTING). * **embedded** — mounted inside another floorplan's body (a records view in a detail screen's `related` slot, D7): no bar, no Fab, the chrome in-body, and it owns only its own chip row and its own pane. ```dart RecordsScreen<ObservationRow>( title: 'Observations', rows: rows, // null = loading · [] = empty rowKey: (r) => r.id, columns: observationColumns, filters: const ['status', 'area'], bodies: const [RecordsBody.table, RecordsBody.map], primaryAction: ActionSpec(label: 'New', onInvoke: (_) => create()), empty: const RecordsEmpty(title: 'No observations yet'), open: RecordsOpen.dock( detail: (row) => ObservationDetail(row: row), onNarrow: (row) => context.push('/observations/${row.id}'), ), syncStatus: const SyncStatusButton(), // required; null = no channel ) ```
RecordsScreendefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| activeStat | String? | kRecordsActiveStatUncontrolled | THE ACTIVE DOOR, **CONTROLLED WHEN PROVIDED** — pair it with onActiveStatChange; omitted, the floorplan keeps its own, seeded by initialActiveStat. Null controls it to "no door active". A key that names no door is no door, read live. |
| badge | String? Function(T row)? | null | The GALLERY body's corner badge for one tile — `AppGallery.badge`'s own signature, null for no badge on that tile. Present, it OVERRIDES the id the cardFor projection puts there; absent, that projection stands, and with no cardFor either the tile carries no badge. **It is UPLOAD STATE, not… |
| band | FloorplanBand? | null | The surface's ONE TRANSIENT BAND — a confirmation that clears itself ("Report started", "3 records deleted"), NOT a standing notice. `Scaffold.d.ts`'s own band props, forwarded verbatim, so every clause stated there binds unchanged: the band draws only while a message is in flight, an action with no… |
| banner | Widget? | null | An operational band under the chrome (a stale-data notice, a sync fault). One at a time. |
| barActions | List<Widget> | const [] | Surface-scoped bar actions — what the surface IS or what MODE it is in, never data-scoping. At most two, inline-start of sync. |
| bodies | List<RecordsBody> | const [RecordsBody.table] | The bodies offered, in switch order; the first is the default body. More than one renders the body switch as a tab of the view-options pane; one renders no switch. `'dashboard'` LEFT this union upstream at pin e04afca: it needed tiles, their placement and the bus (DashboardTileDefs over a… |
| bodyLabels | Map<RecordsBody, String> | const {} | THE HOST NAMES A BODY (`RecordsScreen.d.ts` `bodyLabels`, steady-core #1972): a per-body override of the body switch's word — the item's label and its accessible name, the one place either kit names a body — and nothing else. The body's kind, its place in bodies, whether it is offered, initialBody… |
| bulkActions | List<RecordsBulkAction<T>> | const [] | The bulk bar's own verbs, before Delete (THE BULK ACTIONS ARE A SLOT). Empty: Delete alone. |
| bulkSelection | bool | false | Whether the selection affordances render — the checkbox rail in the table body and the AppBulkBar on a non-empty selection. Independent of onDeleteRows, which is what earns the bar's Delete verb. |
| busy | String? Function(T row)? | null | The row's LIVE busy indicator — **list body only** — a sweep label replacing the row's ⋯ menu AND rowAction while a background job is in flight. Non-null outranks both; `null` or absent renders the row's ordinary trailing content unchanged. Never a percentage — an indeterminate AppProgress sweep,… |
| cardFor | AppCardGridItem Function(T row)? | null | The CARDS body's card for one record, and the GALLERY body's tile through a stated projection. Null keeps the column-role derivation, which is what lost both launchers their per-tile glyph. AppCardGridItem is this kit's `Omit<CardProps, 'posture'>` — the slot bundle, posture-free, so the KIT still… |
| cards | RecordsCardsSlot? | null | The CARDS body's own configuration — its layout (browse, or the category grid) and its track floor (`RecordsScreen.d.ts` THE CARDS AND GALLERY BODIES TAKE A SIZE, #1378). A SLOT, not knobs: it configures one body's content and would be meaningless on the other five. Null renders the cards body… |
| cellMark | AppCellMark? Function(T row, String columnKey)? | null | The table body's per-cell mark (`DataTable.d.ts` THE PROPOSED MARK). Table body only, under any quickEdit. |
| cellMarkLabel | String? | null | THE MARK KEY TAKES THE HOST'S WORDS (`RecordsScreen.d.ts` `cellMarkLabel`, 2026-10-31): forwarded to the table body as AppDataTable.cellMarkLabel — the mark key's word and a marked cell's accessible description. Null (the default) keeps "Proposed". Table body only. |
| collectionKey | String? | null | The ACTIVE sibling collection's key — the tab the collections row selects (`RecordsScreen.d.ts`, THE COLLECTION SWITCH — A TAB ROW OVER SIBLING COLLECTIONS, #1720). Null (the default) is no collection switch. **CONTROLLED ONLY**: required with collections and onCollectionChanged, and absent without… |
| collections | List<RecordsCollection> | const [] | THE COLLECTION SWITCH — the SIBLING collections this screen moves between, in tab order (`RecordsScreen.d.ts`, THE COLLECTION SWITCH — A TAB ROW OVER SIBLING COLLECTIONS, #1720). * **Two or more earn the row**, an underline AppTabs named by title with the active collection (collectionKey) its… |
| columns * | List<RecordsColumn<T>> | — | Every column the bodies may show; the table renders them, list and cards read their title / supporting / meta roles from them, the gallery reads the first image column, and the map reads the first lat-long one. |
| commit | RecordsCommit? | null | THE COMMIT SEAT (`RecordsScreen.d.ts` `commit`, 2026-10-31): the surface's one commit with the host's count, and an optional quiet destructive action, at the band row's inline end. The surface's one accent: beside primaryAction the seat wins and no Fab is drawn (a reported misuse). Under the… |
| compactColumns | List<String>? | null | The column KEYS that survive below 600 of this SURFACE's own measured width — the reduced set `DataTable.d.ts`'s adaptation strategy assigns to its host, which under a floorplan IS the floorplan (THE COMPACT COLUMN SET IS THE FLOORPLAN'S TO PASS). Null (the default) changes nothing: every column… |
| dashboard | RecordsDashboardSlot? | null | The DASHBOARD body's content — the host-constructed bus, the tile definitions, the authored board configuration, the prefs write report and the body's own docked record. ONE slot, ignored by every other body; absent, RecordsBody.dashboard is not offered. |
| defaultGroups | List<RecordsGroupRule> | const [] | defaultSorts' TWIN for grouping: the RULES applied until the user picks their own, outermost first, each an offered option's key and its band order. Empty (the default) is ungrouped. **A restored `TableViewPrefs.groups` wins over it INCLUDING WHEN THAT LIST IS EMPTY** (`RecordsScreen.d.ts`, THE… |
| defaultSorts | List<AppTableSort> | const [] | The sort KEYS applied until the user picks their own (THE SORT IS A LIST OF KEYS, at most kMaxSortKeys); a restored prefs sort wins, and so does a restored CLEAR (`TableViewPrefs.sorts` of `[]`). Empty (the default) is unsorted: the rows in the order given. It orders EVERY body that has an order —… |
| density | RecordsDensity? comfortable · compact | null | Forces the screen's density. Null follows the host. |
| description | String? | null | THE TITLE MAY CARRY A DESCRIPTION (`RecordsScreen.d.ts` `description`, steady-core #1778): one sentence about what the page is, drawn as `DetailScreen`'s header subtitle line — the body role in DesignTokens.ink60, wrapping (never truncated), at most 62ch — and the FIRST thing under the bar, because… |
| empty * | RecordsEmpty | — | The empty face's copy. |
| emptyAction | ActionSpec? | null | The ONE action on the empty face. Not the narrowed-to-zero face, whose action is always Clear filters. |
| error | FloorplanError? | null | The load failed: `title` is the cause, `detail` one quiet line under it. Non-null outranks every other face; null means no error. |
| exportPlacement | RecordsExportPlacement pane · bar | RecordsExportPlacement.pane | Where the ONE overflow affordance rests — the table's scope-gated Export and overflowActions following it (`RecordsScreen.d.ts` EXPORT IS SCOPE-GATED → THE EXPORT MAY REST IN THE BAR, #902). RecordsExportPlacement.pane (the default, and today) is the view-options pane's header;… |
| fabBesidePane | bool | true | Whether the Fab STAYS UP while a record pane is DOCKED at >=960 (`RecordsScreen.d.ts` THE FAB RIDES THE RECORDS REGION, 2026-09-30, #932). True (the default): the collection beside the pane is still the records view the Fab creates in, so the Fab stays over it, anchored to the collection's END EDGE… |
| filterRail | RecordsFilterRail? | null | THE STANDING FILTER RAIL (`RecordsScreen.d.ts`, steady-core #1239): the Filter tab's OPTIONS facets, standing. Null (the default) is no rail. * **One filter state.** A rail facet IS its column's options facet: ticking a value is ticking it in the Filter tab, so the chip row, the match count, the… |
| filters | List<String> | const [] | The filterable column keys, in facet order — each becomes a facet of the Filter tab. EMPTY (the default) earns no Filter tab: an affordance that opens an empty panel is worse than none. A key naming no column is ignored rather than throwing, the same tolerance a restored pref gets. |
| gallery | RecordsGallerySlot? | null | The GALLERY body's own configuration — which line captions a tile and the tile's extent (THE CARDS AND GALLERY BODIES TAKE A SIZE, #1378). Null renders the gallery body exactly as today. |
| groupOptions | List<RecordsGroupOption<T>>? | null | The groupings the Group tab offers, in slot order. Present, this REPLACES the set derived from columns; null, that derivation stands (`RecordsScreen.d.ts`, GROUPING IS THE SCREEN'S DECISION, NOT THE COLUMNS'). A derived set cannot express a partition the rows do not store as one field — a month BAND… |
| grouping | RecordsGrouping | const RecordsGrouping() | Whether grouping is offered, and how deep. |
| hostUndo | RecordsHostUndo? | null | The host's latest write outside cell editing, with its Undo (`RecordsScreen.d.ts` A HOST WRITE CARRIES ITS UNDO): a new RecordsHostUndo.id shows its message on the band with "Undo", the table body keeps it in its one history, and a ctrl/⌘ Z the surface's children left unanswered (outside a text… |
| hydration | SyncHydration? | null | THE HYDRATING FACT as a value (`SyncStatus.d.ts` `SyncHydration`), passed by the host from its sync layer — app_state's `hydratingProvider` (`RecordsScreen.d.ts` THE HYDRATING FACE, prop `hydration`). The floorplan hands it to its dashboard body's tiles, which state it as the first of their empty… |
| initialActiveStat | String? | null | The active door on first mount when activeStat is not controlled; a key that names no door is ignored. Null (the default): no door active. |
| initialBody | RecordsBody? table · list · cards · map · gallery · dashboard · timeline · outline | null | The body shown on first mount when no restored prefs name one. Null takes the first of bodies. |
| initialPrefs | TableViewPrefs? | null | Restored view state, read ONCE — **the first time it is DEFINED**, which is mount when it is defined there and the null → non-null transition otherwise (`RecordsScreen.d.ts`, `initialPrefs` ADOPTS THE FIRST TIME IT IS DEFINED). It was read-once-ON-MOUNT, and a host whose prefs come from an async… |
| initialQuickSlice | String? | null | initialBody's twin for the quick slices: the slice selected on first mount when no restored TableViewPrefs.quickSlice names one this screen offers. Null takes the first of quickSlices; a key not among them is ignored, and with fewer than two slices there is no row and nothing to select… |
| loading | bool | false | The canonical loading signal (THE LOAD FACES). True renders the skeleton face whatever rows holds — a REFRESH over rows already on screen states itself here rather than by dropping them to null. Default false. |
| lockedColumns | int? | null | How many leading columns join the table's frozen region. Null takes the table's own width-dependent default (0 below large, 1 at large). |
| map | RecordsMapSlot<T>? | null | The MAP body's own channels — the basemap (and with it the widget-test seam), the fallback center, the re-frame epoch, the per-row mark and the body's meta line. Ignored by every other body; null renders the map body exactly as it does today. |
| mapCluster | bool | true | Map body only: pins cluster into count badges at low zoom. |
| mapLocate | bool | false | Map body only: renders the Locate control. The fix comes from the host's own location capability (LocationCaptureScope); with no such scope above this screen the control does not render, because a control that can never answer is worse than none. |
| mapZoomControls | bool | false | Map body only: renders the zoom in/out controls. |
| onActiveStatChange | ValueChanged<String?>? | null | Every door change the band asks for — a press, a second press that clears it. Fires under either regime. |
| onBack | VoidCallback? | null | The leading back affordance of this floorplan's OWN bar — the pushed posture. Null renders NONE. Ignored as a scaffold destination (the shell owns that leading) and when embedded (no bar). |
| onCellCursor | ValueChanged<AppCellAddress?>? | null | The table body's cell cursor, or null (`DataTable.d.ts` `onCellCursor`); only an editing table has one. Table body only. |
| onCellEdits | AppCellEditsCallback<T>? | null | QUICK EDIT's commit seam (`DataTable.d.ts` `onCellEdits`): every write the table makes, as one batch of per-row patches. Read only under quickEdit. |
| onCellHover | ValueChanged<AppCellAddress?>? | null | The table body's hovered data cell, or null (`DataTable.d.ts` `onCellHover`; `RecordsScreen.d.ts` THE TABLE BODY HEARS AND MARKS). Table body only, under any quickEdit. |
| onCollectionChanged | ValueChanged<String>? | null | A pick of ANOTHER tab in the collections row, with its key. The row does not move until the host passes the new collectionKey; a pick of the active tab reports nothing. On a pick with a record OPEN, onSelectionChanged is asked for `null` FIRST — the record belongs to the old collection. |
| onCreateAt | void Function(MapPoint point, Map<String, String> prefill)? | null | Map body only: presence enables the Fab → reticle placement flow, and it fires with the placed point in the map's space — a GeoPoint under geo, a normalized ImagePoint under RecordsMapSlot.image (the contract's `LatLongPair | ImagePoint`, 2026-09-27; the sealed MapPoint is this kit's union) — and… |
| onDeleteRows | Future<void> Function(List<T> rows)? | null | Presence enables the bulk bar's Delete. |
| onPrefsChanged | void Function(RecordsViewPrefs prefs)? | null | Fires after every view-state change with the screen's PERSISTED view — the contract's `RecordsViewPrefs`, for the host's prefs store. NARROWING-FREE (#754): search and filters are session state and never reach this record, so a host persists exactly what it is handed. And it holds ONLY WHAT THE USER… |
| onRefresh | Future<void> Function()? | null | THE REFRESH GESTURE (`RecordsScreen.d.ts` — `onRefresh`, #502). Null (the default) renders nothing new; present, a TOUCH or STYLUS pull down from the top of the table, list, cards or gallery body's own scroller — the empty and hydrating faces included, since that is exactly where a user pulls —… |
| onRetry | VoidCallback? | null | The error face's Retry. Null renders no Retry — the face states the cause alone rather than offering a control that cannot act. |
| onSelectionChanged | void Function(Object? key)? | null | Fires whenever the OPENED record changes, under BOTH regimes — a row or a pin opened, the pane's ✕ or Escape, a re-tapped pin, a view-control panel taking the edge, a body switch that cannot carry the selection. Null is "nothing open". This is the half open's push channel never had, and the half a… |
| onUndoDelete | VoidCallback? | null | The delete band's ONE action (DELETE STATES ITS UNDO, OR IT OFFERS NONE): present, the band carries Undo and calls this to restore the rows just deleted; absent, the band states the count and offers no action — a host decision now, rather than a missing channel. |
| open | RecordsOpen<T>? | null | HOW A RECORD OPENS — RecordsOpen.push or RecordsOpen.dock; see RecordsOpen. Null (the default) opens nothing, and every body's rows are inert. Under RecordsScreenPresentation.embedded a dock never docks: a pane inside a pane is the doubling that posture refuses, so every open narrows. |
| openAction | bool | false | Whether the table body's row actions lead with an "Open" action that opens the row's record exactly as its tap does (`DataTable.d.ts` AN OPEN ROW ACTION BESIDE THE GESTURE IS OPT-IN, 2026-10-14). Default false: the row's tap and ↵ open the record, and no Open rides beside them. Ignored when the… |
| optionsOf | AppTableOptionsOf? | null | An enum / enumList cell editor's options per row (`DataTable.d.ts` `optionsOf`, a declared rule's gated choices, #1900). Read only under quickEdit. |
| outline | RecordsOutlineSlot<T>? | null | The OUTLINE body's binding — how the rows name their parents. ONE slot, ignored by every other body; absent, RecordsBody.outline is not offered (`RecordsScreen.d.ts`, THE OUTLINE BODY, AS A BODY AND A SLOT). |
| overflowActions | List<ActionSpec> | const [] | The surface's overflow verbs (Export), rendered as the ONE quiet header affordance of the view-options pane — never a bar `⋯`. |
| paneCollapsible | bool | false | THE PANE HIDES, AND THE TABLE TAKES ITS WIDTH (`RecordsScreen.d.ts` `paneCollapsible`, 2026-10-31). True draws "Hide pane" in the docked pane's chrome row before the expand, at >=960 on the in-flow side mount. Hidden, the collection takes the surface's width and THE SELECTION STAYS — the row keeps… |
| paneRestingShare | double? | null | THE DOCK RESTS AT THE HOST'S SHARE (`RecordsScreen.d.ts` `paneRestingShare`, 2026-10-31): the docked record pane's RESTING width as a FRACTION of the pane host, held to kRecordsPaneShareMin .. kRecordsPaneShareMax, never under kAppDetailPaneWidth, the collection keeping its 480 and the gutter;… |
| paneRestingWidth | double? | null | The docked record pane's RESTING width in logical px for this screen (`RecordsScreen.d.ts` `paneRestingWidth`; `DetailView.d.ts` A SCREEN'S OWN RESTING WIDTH, #1803) — the width a record docks at until the reader drags the seam or widens the pane, and the width the seam's reset returns to. A screen… |
| paneTitle | String Function(T row)? | null | The docked pane's ACCESSIBLE NAME for the open record — "Obs #101", not the noun every record shares. Null takes the capitalized recordNoun. It names the REGION, not the content: the pane's own body (typically a `DetailScreen`) carries the record's title in its own header, and this is the label the… |
| panels | List<RecordsPanel> | const [] | Host panels, each an extra TAB of the one view-options pane after Filter · Group · Columns. Never a second pane. |
| peek | Widget Function(T row)? | null | Map body only: the peek card for an opened pin. |
| presence | Widget? | null | THE PRESENCE STACK (`RecordsScreen.d.ts` `presence`; `Presence.d.ts` EVERY FLOORPLAN CARRIES IT AS A KNOB, steady-core #1331): the `AppPresenceStack` of the scope this collection joins. The HOST builds it; the screen never joins a channel. As a scaffold DESTINATION it is CONTRIBUTED into the shell's… |
| presentation | RecordsScreenPresentation auto · embedded | RecordsScreenPresentation.auto | The screen's hosting posture. RecordsScreenPresentation.auto (the default) is the DERIVED one and covers both bar-owning postures: a scaffold destination's contribution, or this floorplan's own bar on a bare route. It also still reads a NESTED tree as embedded — the outer floorplan knows what it is… |
| primaryAction | ActionSpec? | null | The surface's one accent action — the Fab, in EVERY posture but embedded (`RecordsScreen.d.ts` `primaryAction`). Pushed, this screen's own scaffold paints it. As a scaffold DESTINATION it is CONTRIBUTED to the shell's Fab slot and wins over the destination's own AppScaffoldDestination.primaryAction… |
| quickEdit | bool | false | QUICK EDIT (`RecordsScreen.d.ts`, 2026-10-09, #1217): the TABLE body edits cells in place under `DataTable.d.ts` CELL EDITING; the floorplan decides only WHETHER, never how. A GATE, and a real one: ONE column set serves a read-only face and an editing face, and a host flips this on permission. Off… |
| quickSlices | List<RecordsQuickSlice<T>> | const [] | The QUICK SLICES — author-defined named subsets ("All · Mine", "Open · Closed") offered as an AppTabs row that HEADS THE COLLECTION, in tab order (`RecordsScreen.d.ts`, THE QUICK SLICES — A PINNED TAB ROW OVER NAMED SUBSETS). * **Two or more earn the row**; zero or one renders no row and applies no… |
| recordNoun | String | 'record' | The singular noun the kit's own copy uses ("No observations match."). The kit derives the plural. |
| refOptions | AppTableRefOptions? | null | A ref / refList cell editor's options per row (`DataTable.d.ts` `refOptions`). Read only under quickEdit. |
| retapCloses | bool | true | Whether a tap — or `↵` on the cursor row — on the row, list row, card or gallery tile whose record is DOCKED (>=960) CLOSES the pane exactly as its ✕ does (`RecordsScreen.d.ts` A RE-TAP CLOSES THE DOCKED RECORD, 2026-10-01, #970): `onSelectionChanged(null)` reports, open does not fire, a… |
| rollups | AppRollupPlacement? inline · aligned | null | Where per-band rollups render in a grouped table body, or null for none. |
| rowAction | Widget? Function(T row)? | null | The row's ONE always-visible primary verb — **list body only** — beside the ⋯ menu, before it (`List.d.ts`'s own `rowAction`, reaching this floorplan for the first time). Absent renders none; a row busy marks in flight renders neither this nor the ⋯ menu. |
| rowActions | List<AppRowAction<T>> Function(T row)? | null | Per-row actions — the row's trailing `⋯` menu in every body. |
| rowDanger | bool Function(T row)? | null | Blocked/error rows take the danger edge. |
| rowEditable | bool Function(T row)? | null | A whole-row edit lock (`DataTable.d.ts` `rowEditable`). Read only under quickEdit. |
| rowKey * | Object Function(T row) | — | The row identity accessor — the kit's typed shape of the contract's `rowKey` field name (this kit reads values through closures, never a string-keyed map). |
| rowLoading | bool Function(T row)? | null | A ROW STILL ARRIVING (`RecordsScreen.d.ts`, 2026-10-30): a row this names is IN the collection — counted, sorted, banded and narrowed like any row — but its record has not arrived (an import's item still being read). The table and outline bodies draw an AppSkeletonLine in each of its EMPTY cells (a… |
| rowValues | Map<String, Object?> Function(T row)? | null | The row as the field rules' values map (`DataTable.d.ts` `rowValues`) — required by the table once any column edits. Read only under quickEdit. |
| rows * | List<T>? | — | The records, already scoped by the host's sync layer. Null = still loading (the skeleton face — the shorthand for loading, ORed with it); `[]` = loaded and empty (the empty face). |
| scope | AppScaffoldScope? | null | The active scope, the FIRST item on this floorplan's own bar's subtitle (`AppBar.d.ts` THE SCOPE ITEM, #1186 § 11) — the shell's own AppScaffoldScope type. Omit for an unscoped app. It applies only on a BARE ROUTE, outside any AppScaffold. |
| searchable | bool | true | Whether the search affordance renders at every window class. False leaves the view-options affordance resting alone before sync. |
| selectedKey | Object? | kRecordsSelectionUncontrolled | The OPENED record's key — what the docked pane shows, what the map body marks, what the table highlights — **CONTROLLED WHEN PROVIDED** (`RecordsScreen.d.ts`, ONE SELECTION, NOT ONE PER BODY). Pass it (`null` = nothing open) and this floorplan renders exactly what it holds and mints no selection of… |
| stats | List<RecordsStat<T>> | const [] | THE STAT DOORS (`RecordsScreen.d.ts` `stats`, 2026-10-31): the band of stats above the surface (Charts.d.ts THE KPI BAND), in order. A door stat narrows by its RecordsStat.matches, ANDed AFTER the quick slice and BEFORE search and facets. Never a chip, never cleared by Clear filters, never in… |
| summary | bool | false | THE SUMMARY ROW (`RecordsScreen.d.ts`, DataTable.d.ts THE SUMMARY ROW): the table body's pinned row of every shown column's resolved rollup over the narrowed rows. Forwarded to the table body (AppDataTable.summary); no other body draws one. Default false. |
| syncStatus * | Widget? | — | The shell's sync control, always last inline-end. REQUIRED BUT NULLABLE: null states that the surface has no sync channel and renders no control; omitting it is a compile error, so no screen loses the control by forgetting it. In the DESTINATION posture the shell's own AppScaffold.syncStatus renders… |
| timeline | RecordsTimelineSlot<T>? | null | The TIMELINE body's binding — leading columns, field map and links. ONE slot, ignored by every other body; absent, RecordsBody.timeline is not offered. |
| title * | String | — | The view's name, in the bar's headline role. Not the app's. In the DESTINATION posture the shell's own AppScaffoldDestination.title is what renders — one bar, one title — and this states the same thing for the postures where the floorplan owns the bar. |
AppDataTable widget
The design-system DataTable — the flagship, fully generic over the row type T. Any data, zero domain knowledge: columns carry every bit of semantics via typed accessor closures over T.
Full dartdoc
The design-system DataTable — the flagship, fully generic over the row type T. Any data, zero domain knowledge: columns carry every bit of semantics via typed accessor closures over T. There is no metadata blob, no string-keyed schema lookup, no `Object?`-map dataflow — the table is map-free by construction (`app-quality-first.md`). Anatomy (leading to trailing per row): an optional row-expand chevron (when renderExpanded is set), an optional selection AppCheckbox (when selectable), the typed cells (one per column, rendered by AppColumn.type), and the optional trailing always-visible rowActions. A pinned header row states the column labels (DesignTokens.label), a tri-state select-all checkbox (when selectable), and draggable separators between columns for resizing. A AppColumn.sortable header is a tap affordance writing the controlled sorts key list through onSortsChange (THE SORT IS A LIST OF KEYS); each keyed header suffixes a small accent chevron mark, with its ordinal while two or more keys apply. The table never reorders the rows inside a band — the caller sorts them to match the keys it holds — while it orders the BANDS itself (BAND ORDER). Selection grammar (constitution): a selected row paints a 2px DesignTokens.edge accent start-edge over a DesignTokens.rowSel tint; THE ACTIVE ROW (activeRowKey, the cursor) takes the DesignTokens.accentTint wash instead and sets its AppColumn.em column at the em-strong weight (Contained, #893); a rowDanger row paints the same edge in DesignTokens.danger. Danger and selection are mutually exclusive — selection wins. Grouping: groupBy carries ordered levels, outermost first, at most kMaxGroupLevels; rows sharing a level's label render under a collapsible band — DesignTokens.group0 at level 0, DesignTokens.group1 below, depth read from the indent, the depth guides and the label weight — in each level's BAND ORDER. With rollups set, each band also carries its columns' resolved aggregates (AppColumn.rollup, rollupChoices). Expansion: renderExpanded returns a detail widget shown in a DesignTokens.expandBg well below the row when its chevron is toggled. Empty: when rows is empty, renders an AppEmptyState built from emptyLabel + emptyActionLabel / onEmptyAction instead of a bare header. Loading: loading renders the table's OWN loading face — the header band kept (inert) over placeholder rows at the density's row height — ahead of the empty face and whatever rows holds (`DataTable.d.ts` THE TABLE'S OWN LOADING FACE). Adaptation strategy: **morph** — density scales via DesignTokens.rowV, and below the table breakpoint a host passes **fewer columns** (id + primary + status). The table never squishes or auto-hides columns; horizontal scroll is the fallback when the passed columns exceed the width. At the **large** window class (≥1200) the recommended column budget rises to ~10 and lockedColumns defaults to 1 — the identity column, without which a wide horizontal scroll leaves every visible cell on a row nobody can name. ```dart AppDataTable<WorkOrder>( columns: [ AppColumn(key: 'id', label: 'ID', type: AppCellType.id, value: (r) => r.id, width: 90), AppColumn(key: 'title', label: 'Work item', em: true, value: (r) => r.title), AppColumn(key: 'status', label: 'Status', type: AppCellType.enumValue, value: (r) => r.status, danger: (r) => r.status == 'Blocked', width: 110), AppColumn(key: 'where', label: 'Location', type: AppCellType.latLong, value: (r) => r.geo, onNavigate: (r) => showOnMap(r.id)), ], rows: workOrders, selectable: true, selected: selection, onSelectionChanged: (s) => setState(() => selection = s), rowDanger: (r) => r.status == 'Blocked', renderExpanded: (r) => WorkOrderDetail(r), rowActions: (r) => [ AppRowAction(label: 'Assign', glyph: KitGlyph.person, onSelected: assign), AppRowAction(label: 'Delete', glyph: KitGlyph.trash, danger: true, onSelected: delete), ], emptyLabel: 'No records match.', emptyActionLabel: 'Clear filters', onEmptyAction: clearFilters, ) ```
AppDataTabledefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| activeRowKey | Object? | null | The single ACTIVE row, by rowKey identity — the cursor grammar (`DataTable.d.ts` `activeKey`, pin `d61d0dd`): the row paints THE ACTIVE ROW (Contained, #893) — the 2px accent start-edge over the DesignTokens.accentTint wash (it was the rowSel tint), its AppColumn.em column at the em-strong weight —… |
| cellMark | AppCellMark? Function(T row, String columnKey)? | null | THE PROPOSED MARK (`DataTable.d.ts`, 2026-10-30): the host's mark for one cell, read for every painted cell on every build. The host OWNS it: an edit through the table reaches the host through onCellEdits and the host's next answer says whether the value is still its proposal. A mark is not a lock:… |
| cellMarkLabel | String? | null | THE PROPOSED MARK, THE HOST'S WORDS (`DataTable.d.ts` `cellMarkLabel`, 2026-10-31): the mark key's word in place of "Proposed" and a marked cell's semantics hint in place of "proposed", read verbatim, so the key and the description never disagree ("Read by AI, not checked"). Null or blank keeps both… |
| columnRules | bool | false | Opt-in vertical hairlines between columns in the HEADER BAND only — data rows stay open (spec D1's `columnRules`, DataTable.d.ts). Recommended at medium/expanded width; leave off for a narrow compact column set. Default false. |
| columns * | List<AppColumn<T>> | — | The columns, in display order. Each carries a typed accessor over T. |
| controller | AppTableController? | null | THE HANDLE (`DataTable.d.ts` THE HOST SEAMS, 2026-10-16, #1492): the host's AppTableController, which this table attaches while mounted — its one horizontal scroll and the commit seam's host door (AppTableController.submitEdits). Null: no host reaches in. |
| defaultBandsCollapsed | bool | false | THE BANDS MAY START FOLDED (`DataTable.d.ts` `defaultBandsCollapsed`, 2026-10-21, #1641). Default false: every band starts open, as before. True starts EVERY band at EVERY level collapsed, so a grouped table at rest reads as its outermost bands and their counts, one band to a line, and opening a… |
| density | AppDensity comfortable · compact · condensed | AppDensity.comfortable | The row-density tier (spec: "density-scaled rows"; the `--row-v` ladder in `rhythm.css`). Comfortable keeps the touch floor; AppDensity.compact and the table-only AppDensity.condensed trade rhythm for rows — fine-pointer postures by the constitution, so the sub-44 row pitch is deliberate there while… |
| emptyActionLabel | String? | null | Empty-state single action label. Renders only when onEmptyAction is also non-null. |
| emptyActionNavigates | bool | false | THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, #1749): the empty face's action pushes another screen and writes nothing, so it reports a link to assistive technology. Declared, never inferred from onEmptyAction. Default false. |
| emptyLabel | String | 'No records.' | Empty-state title — what the view would show. Rendered via AppEmptyState when rows is empty. |
| emptyMessage | String? | null | Empty-state elaboration under the title — what the view shows, stated flat (the floorplan contract's `empty.detail`). Null renders the title alone, which is every existing consumer. |
| groupBy | List<AppGroupBy<T>> | const [] | ORDERED grouping levels, outermost first (`DataTableProps.groupBy`), at most kMaxGroupLevels — a fifth asserts in debug and is clamped away. Every partition goes through the band engine (buildBandTree): each level orders its OWN bands by its AppGroupBy.order (value, declared, count or a column's… |
| hostUndo | AppTableHostUndo? | null | A HOST WRITE JOINS THE HISTORY (`DataTable.d.ts`, 2026-10-30): the host's latest write outside cell editing. A new AppTableHostUndo.id records one entry in the table's one undo history, in time order with its own batches (clearing redo); ctrl/⌘ Z reaching it calls AppTableHostUndo.onUndo once. The… |
| initialColumnWidths | Map<String, double>? | null | Restored per-column width overrides (logical px, by AppColumn.key) — the `initial*` half of the width channel. Applied over the column's own AppColumn.width/flex share; the user's drags then adjust from here. A READ-ONCE SEED: read at mount, never re-seeded. |
| initialExpanded | Set<T> | const {} | Rows expanded on first build (deep links, specimens) — the `initial*` idiom (the table owns expansion state thereafter). Ignored when renderExpanded is null. Only rows present in rows are honored. |
| loading | bool | false | THE TABLE'S OWN LOADING FACE (`DataTable.d.ts`, prop `loading`, #474). True keeps the HEADER BAND — the columns are already known, and they are the structure a reader orients by — over placeholder rows at the density's own row height inside the frame, one bar per column at that column's width. A… |
| lockedColumns | int? | null | Count of LEADING columns frozen against horizontal scroll (spec D2, `DataTable.d.ts`). The frozen region is always contiguous and leading by construction — there is no per-column lock flag. It carries the select/expand control rail and the row's start-edge with it, and closes on a full-height lock… |
| onCellCursor | ValueChanged<AppCellAddress?>? | null | THE HOST HEARS WHERE THE READER IS: THE CELL CURSOR's cell of an EDITING table wherever it goes (the keys, a tap, Tab, a commit's move, select-all, a row leaving under it), or null once it clears. Fires on change only; a read-only table, which has no cell cursor, never calls it. |
| onCellEdits | AppCellEditsCallback<T>? | null | THE ONE COMMIT SEAM (`DataTable.d.ts` CELL EDITING, 2026-10-09, #1217). Every write the table makes — a typed edit, a toggle, a paste, a fill, a clear, an undo — arrives here as ONE batch, one AppCellEdit per touched row, its cells merged into one patch that carries what the row's reset rules… |
| onCellHover | ValueChanged<AppCellAddress?>? | null | THE HOST HEARS WHERE THE READER IS (`DataTable.d.ts`, 2026-10-30): the DATA cell under a fine pointer, in any table, or null once the pointer is over no column's cell (the rail, the well, the row actions, a band, a rollup or summary row, the header, or out of the table). Touch has no hover. Fires ON… |
| onColumnWidthsChanged | ValueChanged<Map<String, double>>? | null | Fires with the complete ABSOLUTE override map after every width change — each drag step, each fit-to-content, AND each override dropped because its column's declared width moved (reported only when something was actually dropped, after the frame) — the `onChanged` half of the width channel, shaped… |
| onEditReport | ValueChanged<AppEditReport>? | null | THE REPORT (`DataTable.d.ts` THE CLIPBOARD): a batch that skipped or refused a cell, and every paste, fill or clear of more than one cell, with the table's sentence and an Undo — for the host's modeline. The same words reach a polite live region whether or not this is wired. |
| onEmptyAction | VoidCallback? | null | Empty-state single action callback. Renders only when emptyActionLabel is also non-null. |
| onRollupChoose | void Function(String columnKey, String? choice)? | null | When wired, at a FINE POINTER an aligned rollup cell — and the summary row's — is a button opening the column's aggregate MENU: "None", then the column's offered kinds and custom rollups by name, ✓ on the current one (`DataTable.d.ts` `onRollupChoose`). The pick is reported here as `(columnKey,… |
| onRowTap | void Function(T row)? | null | Fires when a row's body (any non-control cell) is tapped — opens the record. Null renders non-tappable rows (checkboxes / actions / expand chevron still work). |
| onSelectionChanged | ValueChanged<Set<T>>? | null | Fires with the next selection set when a row checkbox or the select-all header toggles. Null (with selectable true) renders read-only checkboxes. |
| onSortsChange | ValueChanged<List<AppTableSort>>? | null | Fires with the next key list when a AppColumn.sortable header is tapped. A PLAIN tap makes that column the only key and cycles it — ascending, descending, then none (`[]`); a SHIFT-click at a fine pointer edits the list in place — appends a new column ascending (not past kMaxSortKeys), flips an… |
| optionsOf | AppTableOptionsOf? | null | An enum / enumList editor's options, read PER ROW with that row's values (AppTableOptionsOf, `DataTable.d.ts` `optionsOf`): an option it retires is offered only while held, and a paste of it is refused. Null: each editor field's own options. |
| refOptions | AppTableRefOptions? | null | A ref / refList editor's options, read PER ROW with that row's values, so a cascade narrows each row by its own values (AppTableRefOptions). The copy writes a ref's LABEL from it and a paste matches a label or a key against it; present, the editor's picker offers the same set. Null: each editor… |
| renderExpanded | Widget Function(T row)? | null | Builds the detail widget shown in a DesignTokens.expandBg well when a row's expand chevron is toggled. Null renders no expand affordance. **The well is VIEWPORT-width, not table-width — build something that reflows.** The well is non-columnar, so on a table wide enough to scroll horizontally it PINS… |
| rollupChoices | Map<String, String?> | const {} | The user's aggregate per column, by AppColumn.key (`DataTableProps. rollupChoices`, ROLLUPS ARE THE USER'S CHOICE): a kind's name, a custom rollup's key, or `null` (the user chose none). A column ABSENT from the map shows its rollup's default; a choice the column no longer offers reads as absent. A… |
| rollups | AppRollupPlacement? inline · aligned | null | Where each column's AppColumn.rollup renders on its band. Null (the default) renders no rollups, whatever the columns declare. Requires groupBy — a rollup with nothing to aggregate over has no band to render on. |
| rowActions | List<AppRowAction<T>> Function(T row)? | null | Trailing always-visible row actions per row. One action renders as a ghost AppIconButton; two or more as a `⋯` AppMenu. Never hover-revealed. |
| rowDanger | bool Function(T row)? | null | Per-row danger predicate — true paints the row's 2px danger start-edge (blocked/error). Overridden by selection (a selected row shows the accent edge instead). Null never paints a danger edge. |
| rowEditable | bool Function(T row)? | null | A whole-row lock: false makes every cell of the row LOCKED — the cursor still lands on it, because copy needs no editability. Null: every row may edit. |
| rowKey | Object Function(T row)? | null | Stable per-row identity (the DS `rowKey`). Selection membership and the table's own expansion state are keyed on the RETURNED value, so an edited copy of a row arriving over a background sync — a fresh instance that no longer compares equal — stays selected and stays expanded. Null falls back to the… |
| rowSelectable | bool Function(T row)? | null | THE UNSELECTABLE ROW (`DataTable.d.ts` THE HOST SEAMS, 2026-10-19, #1573). Null (the default): every row is selectable. A row this refuses draws NO checkbox — its rail cell stays, empty, so the grid holds — is skipped by select-all and its tri-state count, by a shift-extend and by the keyboard's… |
| rowValues | Map<String, Object?> Function(T row)? | null | The row as the field rules' VALUES MAP (`DataTable.d.ts` THE VALUES MAP IS THE ROW): an editor's value is `rowValues(row)editor.snakeName`, in the values map's wire shape (`FieldSpec`), and every rule — `showIf`, `editableIf`, a validator's cross-field read, a derive — reads this map. REQUIRED… |
| rows * | List<T> | — | The rows to render. Empty renders the AppEmptyState. |
| selectable | bool | false | When true, renders the leading checkbox column + a tri-state select-all header checkbox. Requires onSelectionChanged to actually mutate. |
| selected | Set<T> | const {} | The controlled selection set (row identity is the row instance itself — T must have value/identity equality suitable for a `Set`). Ignored when selectable is false. |
| sorts | List<AppTableSort> | const [] | THE CONTROLLED SORT — an ordered list of keys (`DataTable.d.ts` THE SORT IS A LIST OF KEYS): the first key orders and each later key breaks the ties of the ones before, at most kMaxSortKeys. The table only RENDERS it — each keyed header's accent chevron, and its 1-based ordinal while two or more… |
| summary | bool | false | THE SUMMARY ROW (`DataTable.d.ts`, 2026-10-19): a row pinned under the last row, outside the cell navigation like a band, carrying each column's RESOLVED rollup (AppColumn.rollup, rollupChoices) over EVERY row in rows — the narrowed set, never only the expanded bands — in its own cell, with the row… |
| tapSelection | bool | false | The opt-in TAP-SELECTION grammar (`DataTable.d.ts`; the dashboard table tile's contract — default off, existing consumers unchanged). With it on, row taps drive selected/onSelectionChanged: a plain tap selects only that row; a plain tap on a selected row deselects it (re-tap always removes, at any… |
AppGantt widget
Records on a time axis (`Gantt.d.ts`): AppDataTable with one timeline column whose cells are the rows' bars and whose header is the date ruler.
AppGanttdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| activeRowKey | Object? | null | Forwarded to AppDataTable.activeRowKey; the active bar wears the selected ring. |
| columns * | List<AppColumn<T>> | — | The leading columns, all locked by default: the outline column, and the date editors the drag and the non-drag path need. |
| criticalPath | bool | false | Hatches zero-float bars and inks the critical links. Default false. |
| defaultCollapsedKeys | Set<Object> | const {} | Summaries collapsed on mount, by row key. Under groupBy a summary is a context row whose collapse is its band's own, and none starts collapsed. |
| defaultScale | GanttScale day · week · month | GanttScale.week | The scale on first mount while scale is null. Default week. |
| density | AppDensity comfortable · compact · condensed | AppDensity.comfortable | Forwarded to AppDataTable.density. |
| emptyMessage | String? | null | Forwarded to AppDataTable.emptyMessage. |
| fields * | TimelineFieldMap<T> | — | The field map. |
| groupBy | List<AppGroupBy<T>> | const [] | THE BANDS KEEP THE OUTLINE (`Gantt.d.ts`, #1573): the table's grouping levels (AppDataTable.groupBy — the same band order, empty bucket and depth cues), partitioning the SCHEDULED LEAVES. Each band keeps the outline: a leaf sits under its ancestor summaries, repeated in every band holding one of… |
| links | List<GanttLink> | const [] | The dependencies, by row key. |
| loading | bool | false | Forwarded to AppDataTable.loading. |
| lockedColumns | int? | null | Default: the leading columns' count. |
| onCellEdits | AppCellEditsCallback<T>? | null | THE ONE COMMIT SEAM, forwarded to AppDataTable.onCellEdits. A drag commits through it as one batch. |
| onDeleteLinks | FutureOr<void> Function(List<Object> keys)? | null | "Remove the link" in a conflict: the broken links, by key. |
| onRowTap | void Function(T row)? | null | Opens a record. Forwarded to AppDataTable.onRowTap; in an EDITING table, whose tap is the cells', a tap on the row's track opens it instead. |
| onScaleChange | ValueChanged<GanttScale>? | null | Reports every scale change. |
| onSelectionChanged | ValueChanged<Set<T>>? | null | Forwarded to AppDataTable.onSelectionChanged. |
| outlineColumn | String? | null | The key of the leading column that carries the outline. Default: the first. |
| readOnly | bool? | null | No drag at all. Null: true at compact touch, otherwise false. |
| rowEditable | bool Function(T row)? | null | Forwarded to AppDataTable.rowEditable. |
| rowKey * | Object Function(T row) | — | Row identity: what TimelineFieldMap.parent and links name. |
| rowValues | Map<String, Object?> Function(T row)? | null | Forwarded to AppDataTable.rowValues. |
| rows * | List<T> | — | The records, in the host's order (THE ROWS AND THE OUTLINE). |
| scale | GanttScale? day · week · month | null | Controlled scale. Null for the Gantt's own, seeded by defaultScale. |
| schedule | GanttSchedule<T>? | null | THE SUPPLIED SCHEDULE: draw this schedule instead of the Gantt's own, with no drag. Null: the day-based Gantt. TimelineFieldMap.start and TimelineFieldMap.finish are not read for the track while it is set. |
| selectable | bool | false | Forwarded to AppDataTable.selectable. |
| selected | Set<T> | const {} | Forwarded to AppDataTable.selected. |
| showFloat | bool | false | Draws the float rails. Default false. |
| tableController | AppTableController? | null | The table's handle. Null: the Gantt holds its own. |
| timelineLabel | String | 'Timeline' | The timeline column's header name. |
| today | DateTime? | null | Today's day. Default: the device's local date. |
Types its knobs take
These are not things you choose — they are the shapes the knobs above expect. They live under this entry rather than in the navigation for exactly that reason.
ActionSpec support
One app-agnostic action — the runtime primitive behind every action button. Pure affordance metadata plus an onInvoke callback.
| Field | Type | Default | What it does |
|---|---|---|---|
| enabled | bool | true | Whether the action is invokable. A disabled action renders grayed out and does not fire onInvoke. |
| glyph | KitIconRef? | null | The action's design-system glyph. Wins over icon when both are set — the same two-channel contract every glyph-bearing kit API carries. |
| icon | IconData? | null | The action's Material glyph, for a consumer that has not moved to glyph yet. |
| isDestructive | bool | false | Whether the action is destructive (delete, discard, revoke). Purely a visual treatment — hosts render the affordance in the danger color role. Any confirmation dialog is the consumer's onInvoke responsibility; the action layer stays behavior-blind. |
| label * | String | — | User-visible label — also the `IconButton` tooltip, the overflow menu-item text, and the `Semantics` label. |
| navigates | bool | false | THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, steady-core #1673): true declares that invoking it does ONE thing — it opens another screen (a pushed route, a layer) and writes nothing. The host reports the control, or the ⋯ item it folds into, as a link (`Semantics(link: true)`); paint,… |
| onInvoke * | ActionInvokeCallback | — | Invoked on tap. See ActionInvokeCallback. |
| placement | ActionPlacement | ActionPlacement.secondary | Where the action renders. See ActionPlacement. |
| shortLabel | String? | null | The primary slab's SHORT-VERB face (refinements D4: "New", never "New Observation" — the noun is the screen title). Read only where the action renders as the header-band slab; label stays the descriptive accessible name everywhere else (the compact FAB's tooltip, overflow rows). Null falls back to… |
| visible | bool Function(BuildContext context)? | null | Optional visibility predicate. `null` (the default) means always visible. When non-null, hosts re-evaluate the predicate on every build and omit the affordance entirely when it returns false — independent of enabled (which grays but does not hide). Receives BuildContext so predicates can read theme,… |
AppCardGridItem support
One record's posture-free slot bindings for AppCardGrid — the grid chooses the posture per window class and builds the AppCard itself.
| Field | Type | Default | What it does |
|---|---|---|---|
| actions | List<AppCardAction> | const [] | See AppCard.actions. |
| danger | bool | false | See AppCard.danger. |
| enabled | bool | true | See AppCard.enabled. False renders the card disabled: no tap, the quiet ink and border. |
| footerActions | List<AppCardAction> | const [] | See AppCard.footerActions — rendered in the media posture only. |
| glyph | KitIconRef? | null | The category plate's glyph (AppCard.glyph) — required by AppCardGrid.category, whose plate falls back to it when image is absent or fails. The browse and rail postures do not render it. |
| glyphColor | String? | null | The category plate's palette color (AppCard.glyphColor): its tint under the glyph in its ink. Null, or a name the palette does not hold, is the accent plate. The browse and rail postures do not render it. |
| id | String? | null | See AppCard.id. |
| image | String? | null | See AppCard.image. Collapses to the 56px thumb in the compact swap. |
| imageSemanticLabel | String? | null | See AppCard.imageSemanticLabel. |
| mediaAspect | AppCardMediaAspect | AppCardMediaAspect.wide | See AppCard.mediaAspect — the browse grid's media posture reads it; the rail pins AppCardMediaAspect.wide, and the record and category postures have no media block to shape. |
| mediaFit | AppCardMediaFit | AppCardMediaFit.cover | See AppCard.mediaFit — the media block and the record thumb, at both sides of the swap and on the rail; the category plate ignores it. |
| meta | String? | null | See AppCard.meta. |
| metaFormat | FormatRule<Object?>? | null | See AppCard.metaFormat. The browse and rail postures read it; the category posture ignores it. |
| navigates | bool? | null | See AppCard.navigates. Null leaves the card undeclared, which a host that states a default (`RecordsCardsSlot.navigates`) may fill; a card that states its own, true or false, stands. |
| onOpen | VoidCallback? | null | Per-item open handler. Wins over AppCardGrid.onOpen when both are set. |
| state | AppCardState? | null | THE CATEGORY STATE (AppCard.state, `Card.d.ts`, #1920): one live state about the place the card opens, under its content. Read by AppCardGrid.category only. |
| status | String? | null | See AppCard.status. |
| supporting | String? | null | See AppCard.supporting. |
| supportingFormat | FormatRule<Object?>? | null | See AppCard.supportingFormat. The browse posture reads it; the rail draws no supporting line and the category posture ignores it. |
| title * | String | — | See AppCard.title. |
| titleFormat | FormatRule<Object?>? | null | See AppCard.titleFormat (`Card.d.ts` A CARD'S LINES WEAR THEIR FORMAT RULE, #1006). The browse and rail postures read it; the category posture ignores it. |
AppColumn support
One column of an AppDataTable — the sole carrier of domain semantics. A column is pure typed metadata over the row type T: a stable key (identity for column-width state — never rendered), a header label, a type that decides the cell grammar, and a typed value accessor closure that extracts the…
| Field | Type | Default | What it does |
|---|---|---|---|
| danger | bool Function(T row)? | null | Per-row danger predicate for an AppCellType.enumValue cell — when it returns true, the status word paints DesignTokens.danger (e.g. a `'Blocked'` status). Null never paints the cell danger. |
| editor | FieldSpec? | null | THE CELL'S EDITOR (`DataTable.d.ts` CELL EDITING, 2026-10-09, #1217) — the form kit's own field spec, rendered by the SAME editor registry the form renders (kitFieldEditors), at cell size and with no visible label (this column's label names it and stays its accessible name). Its FieldSpec.snakeName… |
| em | bool | false | Emphasized cell text — the row's primary column reads in DesignTokens.bodyEm instead of DesignTokens.body. Applies to AppCellType.text cells. |
| flush | bool | false | THE FLUSH CELL (`DataTable.d.ts` THE HOST SEAMS, 2026-10-16, #1492): a column whose render draws a SURFACE rather than a value — a timeline, a bar track — gives the render the WHOLE TRACK: the column's width and the row's height at its density, with no inline padding and no alignment. renderHeader… |
| formatRules | List<FormatRule<T>>? | null | FORMAT RULES (`FormatRules.d.ts`, `DataTableColumn.formatRules`, #977): conditional color, glyph and text treatment, the FIRST holding rule winning, declared ONCE here and honored wherever this column's value is drawn — the cell, the detail records section built on the column, the records chip row's… |
| formatRulesOf | FormatRulesOf<Object?>? | null | THROUGH A REFERENCE (`FormatRules.d.ts`, `DataTableColumn.formatRulesOf`, #1032): on a column that draws a reference, the REFERENCED table's rules and the host's lookup to the referenced row, so the value wears its target's mark. Walked through resolveFormatOf after formatRules, only when none of… |
| key * | String | — | Stable identity for this column, used to key its resizable width state. Never rendered — label is the visible header. Must be unique within a table. |
| label * | String | — | The header text, rendered in DesignTokens.label (micro-label). IT WRAPS TO AT MOST TWO LINES, THEN ELLIPSES (2026-09-25). A column label is not always a noun: an origin app's Yes/No column can be a whole QUESTION, and seating that on one line costs a 535px column to display the word "Yes", while… |
| maxWidth | double? | null | The CEILING of this column's content-fit width (and of fit-to-content) — a column whose values can run long stops here and ellipsizes. Null is 480. It never caps a declared width or a drag. |
| minWidth | double? | null | Per-column width floor in logical pixels — the narrowest this column may be content-fit or drag-resized down to (default 56). Under pressure a text-family column gives way to `min(its fit, minWidth ?? 140)`. Mirrors the DS `minWidth` (DataTable.d.ts). |
| numberFormat | AppNumberFormat? | null | How an AppCellType.number or AppCellType.percent value reads — decimals, grouping, unit, currency (`DataTable.d.ts` NumberFormat, which states every rule). Null: the value's own digits, never a trailing `.0`, ungrouped. Declare the SAME format on the `AppDetailField` and the `FieldSpec` that show… |
| onNavigate | void Function(T row)? | null | Navigation handler for a navigable cell type (AppCellType.link, AppCellType.latLong) — the cell paints its accent, focusable, tappable form when this is wired and its inert form when it is null (a disclosure affordance that goes nowhere is a lie). Ignored by every other type. Takes the ROW, not the… |
| options | List<FieldOption>? | null | AN OPTION'S LABEL READS IN THE CELL (`DataTable.d.ts` `DataTableColumn.options`, 2026-10-20, #1635): the SAME list the field's form editor takes (FieldSpec.declaredOptions), so a value reads one way on the form, the cell and the detail. Read by AppCellType.enumValue, AppCellType.enumList and (since… |
| refLabel | String? Function(String id)? | null | AppCellType.ref / AppCellType.refList: a stored KEY's label (`DataTable.d.ts` `DataTableColumn.refLabel`, EVERY FORM TYPE HAS A READ FACE, #1645) — the same resolver the field's form editor reads (`RefFieldConfig.refLabel`), so a reference reads one way on the form, the cell and the detail. An… |
| render | Widget Function(T row)? | null | Full custom cell builder. When non-null it wins over type entirely — the caller owns the whole cell widget. Use sparingly; the canonical grammars (type) cover the common cases. |
| renderBand | Widget Function(AppTableBand<T> band)? | null | THE BAND CELL (`DataTable.d.ts` THE HOST SEAMS, 2026-10-19, #1573): on a flush column, the content of that column's own cell on every group band at every level — columnar, no padding, the band's height — in place of the aligned rollup text. A timeline draws a band's span bar here. Ignored on a… |
| renderHeader | Widget Function()? | null | THE HEADER RENDER (`DataTable.d.ts` THE HOST SEAMS, 2026-10-16, #1492): the header cell's content in place of the label, which stays the column's NAME everywhere else — the header's accessible name, the view pane's column list, every cell's name. Such a column is never sorted from its header (no… |
| rollup | AppColumnRollup<T>? | null | This column's per-band aggregate, rendered when AppDataTable.rollups is set. Null contributes no aggregate. |
| seated | bool | false | SEATED (`DataTable.d.ts` TWO POSTURES PER COLUMN): every EDITABLE cell of the column shows its editor's FACE at rest — the field box at cell size in a quiet DesignTokens.hairS hairline at every pointer posture (#1375), the field border while a pointer hovers it and the field's own focus treatment… |
| sortable | bool | true | Whether this column's header is a sort affordance. Tapping a sortable header writes the table's controlled key list (THE SORT IS A LIST OF KEYS) via AppDataTable.onSortsChange. Inert unless the table also supplies AppDataTable.onSortsChange. |
| status | AppStatusMarkTone? Function(T row)? | null | STATUS (`DataTable.d.ts` `DataTableColumn.status`, Contained, 2026-09-29, #893) — maps a row's stored status onto its tone, and the cell renders the STATUS MARK (AppStatusMark) instead of the bare word: AppStatusMarkTone.pending a hollow DesignTokens.ink40 square, AppStatusMarkTone.active an… |
| type | AppCellType | AppCellType.text | How the cell renders. Defaults to AppCellType.text. Ignored when render is supplied. |
| unitOf | AppNumberUnit? Function(T row)? | null | THE ROW'S OWN UNIT (`DataTable.d.ts` `DataTableColumn.unitOf`, #884): this row's unit, read from the row (`(r) => AppNumberUnit(r.gaugeUom)`). A present result replaces numberFormat's unit for that row before the one formatter runs; null or blank leaves it standing; with no numberFormat the unit… |
| units | List<QuantityUnit>? | null | AppCellType.quantity: the unit choices the field's form editor takes (`FieldSpec.units`, `DataTable.d.ts` `DataTableColumn.units`, #1645), read for each unit code's label (`150 psig`) and for the ORDER the column sorts its units in (an unlisted unit after them, by code). Ignored by every other type… |
| value * | Object? Function(T row) | — | Typed value accessor — extracts this column's value from a row. Its return type pairs with type (a `String` for AppCellType.id / AppCellType.text / AppCellType.enumValue / AppCellType.person, a `num` for AppCellType.number / AppCellType.progress, a `String` or `DateTime` for AppCellType.date, an… |
| width | double? | null | Fixed start width in logical pixels (still resizable by dragging the header separator). Null makes the column CONTENT-FIT (`DataTable.d.ts` `DataTableColumn.width`, 2026-09-30, #923): the widest of its header's two-line floor and its first sampled values as they render, clamped to `[minWidth ?? 56,… |
AppEditReport support
A batch's account of itself (`DataTable.d.ts` THE REPORT): the table's sentence for the host's modeline — "Pasted 118 of 121 cells · 2 locked · 1 invalid" — and the counts behind it, carrying undo. Raised for every batch that skipped or refused a cell, and for every paste, fill or clear of more than…
| Field | Type | Default | What it does |
|---|---|---|---|
| applied * | int | — | Cells written. |
| dropped | int | 0 | Cells a paste could not land because they fell past the table's edge. |
| invalid | int | 0 | Cells refused as INVALID — text that does not parse, or a value the field's checks refuse. |
| kind * | AppEditReportKind | — | What the batch was. |
| locked | int | 0 | Cells skipped as LOCKED. |
| message * | String | — | The kit's sentence, in the field-manual voice (appEditReportMessage). |
| moved | int | 0 | Undo / redo only: cells skipped because their value moved, or their row went, since the batch wrote them. 0 for every other kind. |
| undo | VoidCallback? | null | Undoes this batch, replaying its previous values as a new batch. Null on an undo's or a redo's own report; once the batch has left the history it does nothing. |
AppGroupBy support
One nesting level of a grouped surface (`DataTableGroupBy`). The spec expresses a level as a row FIELD NAME or a computed accessor; the kit reads values through typed closures, so a level is a closure plus a stable key.
| Field | Type | Default | What it does |
|---|---|---|---|
| appliesTo | bool Function(T row)? | null | A BAND MAY END (`DataTable.d.ts`, #1573): the rows this level partitions. When it refuses EVERY row of a band (of the whole table, at level 0), that band is TERMINAL — its rows sit directly in it, and neither this level nor a deeper one partitions them. A row refused beside rows it takes falls in… |
| declaredOrder | List<String>? | null | A workflow order for this level's labels (Not started → In progress → Done), read by AppBandOrder.declared — the level's default order when this is non-empty. |
| emptyLabel | String | kEmptyBandLabel | The empty bucket's label. Default `'—'`. |
| groupOf * | String? Function(T row) | — | The band label this row falls under. Rows returning equal strings share one band; the string IS the band's label. `null` (or `''`) puts the row in the level's EMPTY BUCKET: one band labeled emptyLabel, always LAST among its siblings whatever the order. |
| key * | String | — | Stable identity for this level. Never rendered — a band is labeled with the VALUE groupOf returns, not with this. Unique across levels. |
| order | AppBandOrder? | null | How this level orders its bands. Null: AppBandOrder.declared when declaredOrder is non-empty, else value ascending (defaultOrder). |
| orderOf | Object? Function(T row)? | null | The value a band ORDERS by under AppBandOrder.value — the least value among its members, by the kit's per-type comparator. Null: the band's label orders. It is how a "Sep 2026" band sorts as a date and a host's own rank sorts as a number: the label is for reading, this is for ordering. |
AppRowAction support
A trailing, always-visible row action on an AppDataTable row. Mirrors `RowAction` (DataTable.d.ts).
| Field | Type | Default | What it does |
|---|---|---|---|
| danger | bool | false | Paints the action DesignTokens.danger — a destructive row action. Visual only; confirmation is the callback's responsibility. |
| disabled | bool | false | Grays the action (DesignTokens.ink40) and makes it non-firing — the spec's `disabled` on the row-action shape (List.d.ts `ListRowAction.disabled`). Hosts render it as a null-`onPressed` AppIconButton in the single-action ghost form and an `enabled: false` AppMenuItem in the `⋯` menu form. |
| glyph | KitIconRef? | null | Leading design-system glyph — shown directly in the single-action ghost button, and as the menu item's leading glyph in the `⋯` form. Wins over icon when both are set. |
| hidden | bool | false | The action does not apply to THIS row (the table builds actions per row): an inline one leaves its SLOT EMPTY so the icons stay in columns down the table; a menu one is left out of the row's menu. Default false. |
| icon | IconData? | null | A Material glyph, for a consumer that has not moved to glyph yet. |
| inline | bool | false | PROMOTES the action to an ALWAYS-VISIBLE icon beside the `⋯` (`DataTable.d.ts` INLINE ROW ACTIONS, 2026-09-30, #926). Needs a glyph or icon: an inline action with neither stays in the `⋯`. The inline actions sit first, in declared order, and the `⋯` holds only the rest. |
| label * | String | — | Tooltip + accessible name (single-action form) and menu-item text (the `⋯` form). |
| navigates | bool | false | THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, steady-core #1673): choosing it only opens another screen and writes nothing. The inline or direct icon and the `⋯` item each report a link. Defaults to false. |
| onSelected * | void Function(T row) | — | Fires with the row when the action is chosen. The consumer owns any confirmation (a destructive action confirms in its own callback). |
AppScaffoldScope support
The active SCOPE — which slice of the data the user is working in — of whatever KIND the app declares (Project, Organization, Site, Crew…). `AppScaffold.scope` states it as the FIRST item on the view bar's subtitle line, before the destination's meta (`North Plant ⌄ · 8 records`), at every window…
| Field | Type | Default | What it does |
|---|---|---|---|
| kind * | String | — | What KIND of scope this is ("Project", "Organization") — said in the accessible name and its tooltip, never drawn in the bar. |
| label * | String | — | The active scope's own name ("North Plant") — the text the bar draws. |
| onSelect | void Function(BuildContext anchor)? | null | Opens the scope picker, handed the scope item's OWN BuildContext as the `anchor` it drops from (`showAppRefPicker(anchor, …)`) — on a phone, the title block's, and the picker then presents as the bottom sheet (`Select.d.ts` THE PRESENTATION RULE). Null states the scope as plain text, not a control. |
| parent | String? | null | What the scope sits under ("Northwind") — NEVER drawn: it joins the accessible name, `Project: North Plant (Northwind)`, and is the host's picker's to use as a group header. |
| tooltip | String? | null | The host's own words for the item, in place of `kind: label (parent)`: its accessible name AND its tooltip, one string (`AppBar.d.ts` THE SCOPE ITEM's `title`, #1950). |
AppTableHostUndo support
The host's latest write outside cell editing (`DataTable.d.ts` `DataTableHostUndo`, A HOST WRITE JOINS THE HISTORY): a new id records ONE entry in the table's one undo history, in time order with its own batches, and ctrl/⌘ Z reaching it calls onUndo once. The entry is live only while id is still…
| Field | Type | Default | What it does |
|---|---|---|---|
| id * | Object | — | The write's identity, compared with `==`. |
| onUndo * | VoidCallback | — | Puts the write back. Called at most once per id. |
AppTableSort support
One sort KEY — one column, one direction (`DataTableSort`). A table's sort is an ORDERED LIST of them (`AppDataTable.sorts`, THE SORT IS A LIST OF KEYS): the first key orders, and each later key breaks the ties of the ones before.
| Field | Type | Default | What it does |
|---|---|---|---|
| ascending * | bool | — | True orders ascending; false descending. Empty values sort LAST either way. |
| columnKey * | String | — | The `AppColumn.key` of the sorted column. |
FloorplanBand support
A floorplan's ONE TRANSIENT BAND — the contract's `ScaffoldHeaderBandProps` on both floorplans that declare one, stated once here rather than twice. `DetailScreen.d.ts` and `RecordsScreen.d.ts` each type their `band` member as that shared props type and forward it VERBATIM, so a Dart type per…
| Field | Type | Default | What it does |
|---|---|---|---|
| actionLabel | String? | null | Label for the ONE action beside message (the Undo pattern). |
| message | String? | null | The transient message. Null draws nothing. |
| onAction | VoidCallback? | null | Callback for that single action. |
GanttLink support
One dependency between two rows, by their keys.
| Field | Type | Default | What it does |
|---|---|---|---|
| from * | Object | — | The predecessor row's key. |
| key * | Object | — | The link's own identity: the host's row key for it. |
| kind | GanttLinkKind | GanttLinkKind.fs | Which ends the link joins. |
| lag | int | 0 | Whole days, and may be negative (a lead). |
| to * | Object | — | The successor row's key. |
GanttSchedule support
THE SUPPLIED SCHEDULE (`Gantt.d.ts`, steady-core #1596): a schedule computed elsewhere (a server's critical path in working minutes), which the Gantt draws as given. With one, AppGantt never runs analyzeSchedule or planSchedule, derives no float, critical flag or driving link of its own, and never…
| Field | Type | Default | What it does |
|---|---|---|---|
| broken | bool Function(GanttLink link)? | null | Whether a link's relationship does not hold. Absent: none is broken. |
| dataDate | DateTime? | null | Where an unfinished actual ends. Absent: at the forecast's start. |
| driving | bool Function(GanttLink link)? | null | Whether a link drives its successor. Absent: none does. |
| lagLabel | String? Function(GanttLink link)? | null | A link's lag in the host's words, or null for none. Absent: the Gantt's own "+2d" from GanttLink.lag. |
| row * | GanttRowSchedule? Function(T row) | — | A non-summary row's schedule, or null: a row with no bar, which sits in the Unscheduled lane. |
MapPoint support
A coordinate in one of the map's two spaces. Sealed so a `switch` over it is exhaustive — the compiler catches a mode that forgets a capability.
RecordsBulkAction support
One bulk-bar verb — the contract's `RecordsBulkAction` (`RecordsScreen.d.ts`, THE BULK ACTIONS ARE A SLOT). This kit hands the selected ROWS, as RecordsScreen.onDeleteRows does, in the body's shown order.
| Field | Type | Default | What it does |
|---|---|---|---|
| disabledFor | bool Function(List<T> rows)? | null | Disables the verb for this selection; never hides it. |
| label * | String | — | The verb, sentence case ("Link in sequence"). |
| onSelect * | void Function(List<T> rows) | — | Called with the selected rows in the body's shown order. The selection stays. |
RecordsCardsSlot support
The CARDS body's own configuration — the contract's `RecordsCardsSlot` (`RecordsScreen.d.ts` THE CARDS AND GALLERY BODIES TAKE A SIZE, #1378). Ignored by every other body; absent, the cards body renders as today.
| Field | Type | Default | What it does |
|---|---|---|---|
| cardExtent | double? | null | AppCardGrid.cardExtent, forwarded: under browse the media grid's track floor at >=600 (null: the window-class tracks; below 600 the record column is unchanged), under category its track floor (null: 220). |
| layout | RecordsCardsLayout | RecordsCardsLayout.browse | RecordsCardsLayout.browse (the default) or the category grid. Under RecordsCardsLayout.category a card with no glyph or image — every card of the column-role derivation, which reaches no mark — draws the plate's KitGlyph.file, and `status`, `danger`, the screen's `rowDanger` and the `⋯` actions have… |
| navigates | bool | false | Opening a card only opens the record's screen and writes nothing: every card that opens is marked a link to assistive technology (`Card.d.ts` `navigates`; `ActionStrip.d.ts` THE ACTION THAT ONLY OPENS A PAGE; #1734), as a navigation hub's cards are. A `cardFor` card that sets… |
RecordsCollection support
One SIBLING COLLECTION — a tab in the row that heads the surface — the contract's `RecordsCollection` (`RecordsScreen.d.ts`, THE COLLECTION SWITCH — A TAB ROW OVER SIBLING COLLECTIONS, #1720). It is NAVIGATION, not a filter: a quick slice narrows ONE collection's rows, where this names WHICH…
| Field | Type | Default | What it does |
|---|---|---|---|
| count | int? | null | The collection's SIZE as the host states it — the whole collection, never narrowed (`Tabs.d.ts` THE COUNT). The active tab's count is the same total: the narrowed figure is the chip row's match count. `0` renders; null draws nothing. |
| key * | String | — | The collection's identity — what RecordsScreen.collectionKey names and RecordsScreen.onCollectionChanged reports. Never the label. |
| label * | String | — | The tab's text ("Types", "Attributes"). |
RecordsColumn support
A filterable column of a records surface — the display column (AppColumn, the sole carrier of cell grammar) plus the paint-free filter metadata the records filter pane needs. Kept map-free: filterValue (when set) and column.value are typed accessors over T, never a string-keyed lookup.
| Field | Type | Default | What it does |
|---|---|---|---|
| column * | AppColumn<T> | — | The display column — carries the label, cell type, and value accessor. |
| compactVisible | bool? | null | Whether this column belongs to the REDUCED set a records table renders below the medium window class (the spec's adaptation contract: the host passes fewer columns below 600 — id + primary + status; horizontal scroll is the fallback, never the primary posture). Null = no declaration. When no column… |
| filterValue | Object? Function(T row)? | null | Typed raw value for filtering, when column.value returns a display-formatted string (e.g. a formatted date). Falls back to column.value when null. |
| filterable | bool | true | Whether this column is offered as a facet in the filter pane. Free-text columns typically set this false and rely on the search field. Read by the PATTERNS (`RecordsTableScaffold`, `RecordsMapView`, `RecordsChrome`). |
RecordsCommit support
THE COMMIT SEAT (`RecordsCommit`, 2026-10-31): the surface's one commit, carrying the host's count, and its quiet destructive companion. The floorplan counts nothing and writes nothing.
| Field | Type | Default | What it does |
|---|---|---|---|
| busy | bool | false | Default false. True holds the slab inert and announced busy, and draws the kit's progress sweep in place of detail. |
| danger | RecordsCommitDanger? | null | The quiet destructive action beside the commit, or null for none. |
| detail | String? | null | One quiet line before the buttons: "Register takes every ready item." |
| disabled | bool | false | Default false. True keeps the slab visible and inert. |
| label * | String | — | The slab's words; the host says the count: "Register 12". |
| onCommit * | FutureOr<void> Function() | — | The commit. While a returned future is pending the slab is inert (one commit at a time); a failure re-arms it, and the host words it on the screen's band. |
RecordsDashboardSlot support
The DASHBOARD body's content — the contract's `RecordsDashboardSlot` (`RecordsScreen.d.ts`, THE DASHBOARD BODY RETURNS, AS A BODY AND A SLOT). Ignored by every other body; absent, RecordsBody.dashboard is not offered.
| Field | Type | Default | What it does |
|---|---|---|---|
| boardLabels | Map<String, String> | const {} | The board tab row's labels, keyed by board id. |
| bus * | DashboardBus | — | The HOST-constructed bus — boards, narrowing, every scope, the active board and the prefs record. |
| fit | DashboardFit | DashboardFit.viewport | How the board spends the plate's height (`Dashboard.d.ts` EVERY BOARD FILLS ONE VIEW): DashboardFit.viewport — the default, on any board, fixed or arrangeable — solves the row unit so the rows fill the plate exactly, shrinking until the first tile reaches its KIND's minimum and only then scrolling,… |
| fixed | bool | false | A FIXED-LAYOUT board: zero resting bar actions, no coaching, no undo, no persisted view state (RecordsDashboard.locked). |
| onPersistPrefs | ValueChanged<DashboardPrefs>? | null | The WRITE half of the prefs pair; a fixed board never reports. |
| pane | RecordsDashboardPane? | null | The body's own docked record. Null, no pane. |
| seam | DashboardSeam | DashboardSeam.carded | The plate seam (`Dashboard.d.ts` THE THREE SEAMS): carded (the default), welded or open. |
| selectionChips | DashboardSelectionChips | DashboardSelectionChips.plate | Where a row-tap selection's key chip renders (`Dashboard.d.ts` A ROW TAP NEED NOT MOVE THE BOARD): the chip row by default, or the head of the Filter tab, where a tap and its clear never move the plate. With no facet column there is no Filter tab, and it falls back to the row. Or… |
| tileIndex | DashboardTileIndex | DashboardTileIndex.none | Whether the tile heads carry an index (`Dashboard.d.ts` THE INDEX IS A BOARD KNOB): none by default since the Contained dashboard (#895) — no slot and no reserved width — or the plate ordinal ("01"), the opt-in for a board a team talks about by number. |
| tiles * | List<DashboardTileDef> | — | The tile definitions, in index order. Geometry is the bus's. |
RecordsEmpty support
The empty face's copy — the contract's `empty`.
| Field | Type | Default | What it does |
|---|---|---|---|
| detail | String? | null | One elaborating line under it. Null renders the title alone. |
| title * | String | — | The face's title ("No observations yet"). |
RecordsFilterRail support
THE STANDING FILTER RAIL's slot (`RecordsScreen.d.ts`, `RecordsFilterRail`) — options facets of RecordsScreen.filters, standing as a card at the collection's inline start while the collection keeps its measure, else folded into one chip row heading the collection.
| Field | Type | Default | What it does |
|---|---|---|---|
| facets * | List<RecordsRailFacet> | — | The facets, top to bottom. None (or none that survive the options check) is no rail at all — standing or folded. |
| foldedFacet | String? | kRecordsRailFirstFacet | The facet (by RecordsRailFacet.column) that becomes the FOLDED chip row. Omitted: the first facet. `null`: the rail folds into the Filter tab alone, with no row. |
| head | Widget? | null | The host's own control at the rail's head. Never narrowing state of the rail's, and never the switch between sibling collections, which is RecordsScreen.collections (THE COLLECTION SWITCH): a head vanishes whenever the rail folds. Not drawn while the rail is folded. |
RecordsGallerySlot support
The GALLERY body's own configuration — the contract's `RecordsGallerySlot` (`RecordsScreen.d.ts` THE CARDS AND GALLERY BODIES TAKE A SIZE, #1378). Ignored by every other body; absent, the gallery body renders as today.
| Field | Type | Default | What it does |
|---|---|---|---|
| caption | RecordsGalleryCaption? | null | Which line captions a tile. Null is today's projection: RecordsGalleryCaption.meta under `cardFor`, RecordsGalleryCaption.title under the column-role derivation. |
| tileExtent | double? | null | AppGallery.tileExtent, forwarded — the max tile cross-extent. Null: the gallery's own 112. |
RecordsGrouping support
Whether grouping is offered, and how deep — the contract's `grouping`.
| Field | Type | Default | What it does |
|---|---|---|---|
| allowed | bool | true | Whether the Group tab renders at all. False also makes restored `groups` inert: an affordance that cannot be reached must not leave bands the user has no way to clear. |
| maxLevels | int | kMaxGroupLevels | How many rules the Group tab's rule list holds (1..4) — the "+ Add subgroup" button hides at it. |
RecordsGroupOption support
One user-selectable grouping of a records surface (`RecordsGroupOption`) — AppGroupBy plus the label the Group tab's rule list shows, so the floorplan and the table speak ONE vocabulary and an option passes to `AppDataTable.groupBy` untranslated. Its key is what `RecordsViewPrefs.groups` stores and…
| Field | Type | Default | What it does |
|---|---|---|---|
| appliesTo | bool Function(T row)? | null | |
| declaredOrder | List<String>? | null | |
| emptyLabel | String | null | |
| groupOf * | String? Function(T row) | — | |
| label * | String | — | The option's name in the Group tab's rule list and the modeline ("grouped by date"). Never the band's label — a band is labeled by groupOf's return — and never key, which is identity. |
| order | AppBandOrder? | null | |
| orderOf | Object? Function(T row)? | null |
RecordsGroupRule support
One grouping level the user (or `defaultGroups`) chose (`RecordsGroupRule`, GROUPING, SORT AND ROLLUPS v2): an offered option's key and the order its bands take. order null = the option's own default (`AppGroupBy.defaultOrder`).
| Field | Type | Default | What it does |
|---|---|---|---|
| key * | String | — | The `RecordsGroupOption.key` this level groups by. |
| order | AppBandOrder? | null | The order this level's bands take, or null for the option's default. |
RecordsHostUndo support
The host's latest write outside cell editing, with the band's words (`RecordsScreen.d.ts` `RecordsHostUndo`, A HOST WRITE CARRIES ITS UNDO): a new id shows message on the band with "Undo", the table body keeps it in its history, and a ctrl/⌘ Z the surface left unanswered runs it.…
| Field | Type | Default | What it does |
|---|---|---|---|
| id * | Object | — | |
| message * | String | — | The band's message, in the host's words: "Accepted 4 values". |
| onUndo * | VoidCallback | — |
RecordsMapSlot support
The MAP body's own channels, gathered into ONE slot — the contract's `RecordsMapSlot` (THE MAP BODY'S CHANNELS ARE ONE SLOT). One slot rather than five knobs, because `RecordsScreenKnobs` is the parity gate's machine-read input and every property there is a knob of the SCREEN: a body's own…
| Field | Type | Default | What it does |
|---|---|---|---|
| center | LatLng? | null | The camera's FALLBACK center, for a map with nothing to frame (no rows, or no row with a parseable point). It never overrides the mount framing: with pins to fit, the FIT wins. Null keeps the kit's own fallback. |
| frameEpoch | int? | null | Programmatic re-frame — a CHANGE fits the camera to the visible pins (AppMapView.frameEpoch, forwarded verbatim). Only a change acts, and a bump with zero pins is a no-op. |
| image | ImageMode? | null | The map's coordinate space — the contract's `map.mode` and `map.image`, FORWARDED VERBATIM (`RecordsScreen.d.ts` THE MAP BODY TAKES THE MAP'S COORDINATE SPACE), as this kit spells them: an ImageMode IS `mode: 'image'` with its drawing — a raster (ImageMode.new) or one PDF page (ImageMode.pdf); null… |
| imagePoint | ImagePoint? Function(T row)? | null | Under image, the row's point — normalized 0–1 over the drawing's natural size (ImagePoint). Null draws no pin, as an unparseable `latLong` does. REQUIRED with image (asserted): an image map with no point accessor is a host error, as a geo map with no `latLong` column is. |
| layers | MapLayers? | null | THE LAYERS CONTROL — `MapView.d.ts`'s own `MapLayers`, FORWARDED VERBATIM (THE MAP'S FLOATS ARE SMALL CARDS, #894): the host's named basemaps — and, since A DRAWING IS A LAYER (#1084), its drawings — the current one and the choice. Two or more options render the stack's Layers group, in either mode;… |
| legend | List<RecordsMapLegend>? | null | THE PIN LEGEND (`MapView.d.ts` THE LEGEND DECODES THE PINS, #593): one group per channel of the pin palette, each bound to a facet of the screen's `filters`, so tapping a key toggles the Filter tab's own facet. Null or empty draws none. |
| meta | String? | null | The map's quiet meta annotation — "12 of 31 located", the honest statement of the rows this body cannot place. Forwarded verbatim, so AppMapView.meta's VALUE-slot rule binds: `''` is NOT PROVIDED and paints no empty chip over the body, null omits. REFUSED: deriving the sentence. |
| pin | RecordsPinMark Function(T row)? | null | The row's mark — glyph, color and accessible label, resolved from the TYPED row. Null gives every pin the kit's default mark. |
| seedValue | String? Function(String column, String applied)? | null | A DISPLAY VALUE MAPS BACK (`RecordsScreen.d.ts` THE MAP'S CREATE CARRIES THE FILTERS, #1087): maps a facet's one applied value — often its display words — to the stored value `onCreateAt`'s prefill hands over for that column; null leaves the column out. Null: verbatim. |
| tiles | MapBasemapChannel? | null | The basemap, forwarded verbatim — `MapView.d.ts`'s own `MapBasemapChannel`, and with it the widget-test seam. GEO ONLY. |
RecordsOpen support
HOW A RECORD OPENS — the contract's `open` (`RecordsScreen.d.ts` `RecordsOpen`, #1639). ONE value carries the whole open path, so a docked detail can never be declared without the route it needs below 960, and a pushed screen can never carry a detail it would never render.
| Field | Type | Default | What it does |
|---|---|---|---|
| detail * | Widget Function(T row) | — | |
| onFallback | void Function(T row)? | null | |
| onNarrow * | void Function(T row) | — |
| Field | Type | Default | What it does |
|---|---|---|---|
| onOpen * | void Function(T row) | — |
RecordsOutlineSlot support
How a records table's rows name their parents — the contract's `RecordsOutlineSlot` (`RecordsScreen.d.ts`, THE OUTLINE BODY).
| Field | Type | Default | What it does |
|---|---|---|---|
| column | String? | null | The key of the column whose cell carries the outline. Null: the first shown column. |
| defaultCollapsedKeys | Set<Object> | const {} | Rows collapsed on mount. |
| parent * | Object? Function(T row) | — | The row's parent, by its row key, or null for a top-level row. A key that names no narrowed row makes the row a root. |
RecordsPanel support
One host-contributed view-control panel of a records surface — the Group and Columns panels a table adds beside the built-in Filter. The chrome owns the posture (in-flow pane ≥960, modal sheet below), the control's state grammar, and the one-open-at-a-time rule; the host owns only the panel's body…
| Field | Type | Default | What it does |
|---|---|---|---|
| applied | bool | false | Whether this panel currently narrows/alters the view — the control's applied grammar (paper + accent border + accent mark). |
| badge | int? | null | Data-font count on the control while applied (hidden-column count). |
| body * | Widget Function(BuildContext context, VoidCallback? dismiss) | — | Builds the panel content. `dismiss` is non-null ONLY in the modal posture, where it pops the hosting route — a body with a terminal action (the table's Reset column widths) uses it to close the sheet, while the in-flow pane stays open because live-apply has nothing to commit. |
| glyph * | KitIconRef | — | The control's mark. |
| id * | String | — | Stable identity — the open-panel key, and the control's widget key (`records-<id>-button`). Never rendered. |
| title * | String | — | The panel heading, and the control's accessible title ("Group"). |
RecordsQuickSlice support
One author-defined QUICK SLICE — a named subset of the records, offered as a tab in the row that heads the collection — the contract's `RecordsQuickSlice` (`RecordsScreen.d.ts`, THE QUICK SLICES — A PINNED TAB ROW OVER NAMED SUBSETS). It is not a facet preset and not a saved view: a preset cannot…
| Field | Type | Default | What it does |
|---|---|---|---|
| key * | String | — | The slice's identity — what TableViewPrefs.quickSlice stores and RecordsScreen.initialQuickSlice names. Never the label. |
| label * | String | — | The tab's text ("All", "Mine"). |
| matches * | bool Function(T row) | — | Whether a row belongs to this slice. ANDed in front of search and facets. |
RecordsStat support
One stat in THE STAT DOORS' band, and with door one FILTER DOOR (`RecordsScreen.d.ts` `RecordsStat`, 2026-10-31): Charts.d.ts KpiStat plus the door's predicate, typed on the row as a quick slice's is.
| Field | Type | Default | What it does |
|---|---|---|---|
| caption | String? | null | |
| door | bool | null | |
| label * | String | — | |
| matches | bool Function(T row)? | null | The door's predicate: while this door is active a row shows only when it matches, ANDed after the quick slice and before search and facets. Null: the door narrows nothing, and its press is only reported. |
| tone | KpiStatTone | null | |
| value * | String | — |
RecordsTimelineSlot support
The TIMELINE body's binding — the contract's `RecordsTimelineSlot` (`RecordsScreen.d.ts`, THE TIMELINE BODY, AS A BODY AND A SLOT): the Gantt's field map and links over this screen's rows and the leading columns it names. Ignored by every other body; absent, RecordsBody.timeline is not offered.
| Field | Type | Default | What it does |
|---|---|---|---|
| criticalPath | bool | false | AppGantt.criticalPath, forwarded. |
| defaultCollapsedKeys | Set<Object> | const {} | AppGantt.defaultCollapsedKeys, forwarded. |
| defaultScale | GanttScale | GanttScale.week | AppGantt.defaultScale, forwarded. |
| fields * | TimelineFieldMap<T> | — | The Gantt's field map (AppGantt.fields). |
| leading * | List<String> | — | The keys of the screen's columns the Gantt leads with, in order; the first carries the outline. At compact only the first shows. |
| links | List<GanttLink> | const [] | The dependencies (AppGantt.links). |
| onDeleteLinks | FutureOr<void> Function(List<Object> keys)? | null | AppGantt.onDeleteLinks, forwarded. |
| onScaleChange | ValueChanged<GanttScale>? | null | AppGantt.onScaleChange, forwarded. |
| readOnly | bool? | null | AppGantt.readOnly, forwarded. Without RecordsScreen.quickEdit the timeline is read-only whatever this says: nothing could commit a drag. |
| schedule | GanttSchedule<T>? | null | AppGantt.schedule, forwarded (`Gantt.d.ts` THE SUPPLIED SCHEDULE): a schedule computed elsewhere, drawn as given, with no drag. |
| showFloat | bool | false | AppGantt.showFloat, forwarded. |
| today | DateTime? | null | AppGantt.today, forwarded. |
RecordsViewPrefs support
A records screen's PERSISTED VIEW — the contract's `RecordsViewPrefs` (`RecordsScreen.d.ts`), member for member, and what `RecordsScreen.onPrefsChanged` reports. **Narrowing is not in it** (#754). Search and filters are session state: a narrowing that survived a restart would hide records with no…
| Field | Type | Default | What it does |
|---|---|---|---|
| body | RecordsBody? | null | The body the user last CHOSE, or null for never chosen — then the screen's `initialBody` (else the first of `bodies`) applies. A body the screen no longer offers drops at restore as if absent. |
| columnOrder | List<String>? | null | The user's column ORDER by key, in display order — or null for never reordered, so the declared order applies. Resolved against the screen's columns at render: unknown keys drop, and a column the stored order never named follows it in declared order (THE COLUMNS TAB REORDERS). |
| columnWidths | Map<String, double> | const {} | The column widths the user dragged, in logical px by column key. |
| groups | List<RecordsGroupRule>? | null | The grouping rules the user chose, outermost first — or null for never chosen, so `defaultGroups` applies. `[]` is the user's explicit none, and it beats the default. |
| hiddenColumns | Set<String> | const {} | The columns the user hid, by key. |
| paneWide | bool? | null | Whether the user WIDENED the docked record pane with its chrome row's toggle (`DetailView.d.ts` THE DOCKED PANE RESTS AT A SHARE OF ITS SURFACE, AND WIDENS IN ONE PRESS, #1803) — or null for never pressed. True docks every record opened in this view at the seam's whole reach. Written only from the… |
| paneWidth | double? | null | The DOCKED record pane's width the user dragged its seam to, in logical px (`DetailView.d.ts` THE SEAMS ARE DRAGGABLE, #928) — or null for never dragged, so the pane RESTS (`RecordsScreen.paneRestingWidth`, else the share rule, #1803). Written only from the user's own drag, like columnWidths; every… |
| quickSlice | String? | null | The quick slice the user last picked, by its key — or null for never chosen, so `initialQuickSlice` (else the first slice) applies. |
| rollups | Map<String, String?>? | null | The user's aggregate per column, by column key (`DataTable.d.ts` `rollupChoices`): a kind's name, a custom rollup's key, or `null` for none — or null for never chosen. Written only once the user picks; a column absent from it shows its rollup's default. |
| sorts | List<AppTableSort>? | null | The user's sort keys, in THREE states: null = never chosen (`defaultSorts` applies, and a later change to it reaches this user); `[]` = the user CLEARED the sort (no sort applies and `defaultSorts` does not return); a non-empty list = the user's own, at most `kMaxSortKeys`. |
| Field | Type | Default | What it does |
|---|---|---|---|
| json * | Map<String, Object?> | — |
TableViewPrefs support
The complete, JSON-serializable device-local view state of a table: column order, per-column width overrides, the hidden-column set, density, the sort keys, active filters, search text, the grouping rules and the rollup choices. This is the composition-policy "config record" the host threads into…
| Field | Type | Default | What it does |
|---|---|---|---|
| body | String? | null | The BODY the user last had on a multi-body records surface, by its enum name (`table`, `map`, …) — the floorplan contract's `RecordsViewPrefs.body`. Null = never switched, so the surface's own `initialBody` stands; a stored name the surface no longer offers is ignored at restore for the same reason.… |
| columnOrder | List<String> | const [] | The user's column display order, by `TableColumnSpec.effectiveId`. Empty = the surface's declared order stands. Consumers merge, never assert: ids the surface no longer carries are dropped, and columns this list has never seen render after it in declared order — so a persisted order survives a… |
| columnWidths | Map<String, double> | const {} | Per-column width overrides (logical px), keyed by `effectiveId`. |
| density | TableDensity | TableDensity.comfortable | The row density tier. |
| filters | Map<String, ColumnFilter> | const {} | Active per-column filters, keyed by `effectiveId`. |
| groups | List<RecordsGroupRule>? | null | The ordered grouping RULES, outermost first (`RecordsViewPrefs.groups`) — or null when the user has never chosen. **Three states, not two**, exactly as sorts: null means never chosen and the surface's declared default grouping stands, `[]` means the user explicitly picked none, and a non-empty list… |
| hiddenColumns | Set<String> | const {} | The hidden (toggled-off) columns, by `effectiveId`. |
| paneWide | bool? | null | Whether the user WIDENED the docked record pane — the floorplan contract's `RecordsViewPrefs.paneWide` (#1803). Null = never pressed. |
| paneWidth | double? | null | The DOCKED record pane's width the user dragged its seam to, in logical px — the floorplan contract's `RecordsViewPrefs.paneWidth` (`DetailView.d.ts` THE SEAMS ARE DRAGGABLE, #928). Null = never dragged, so the pane rests (#1803). Kept as stored: the pane clamps it to the surface at layout, so a… |
| quickSlice | String? | null | The QUICK SLICE the user last chose on a records surface, by the slice's key — the floorplan contract's `RecordsViewPrefs.quickSlice`. Null = never chosen, so the surface's own `initialQuickSlice` (or its first slice) stands; a key the surface no longer offers is ignored at restore. It is the one… |
| rollups | Map<String, String?>? | null | The user's aggregate per column (`RecordsViewPrefs.rollups`): a kind's name, a custom rollup's key, or `null` for the user's none — or null for never chosen. A column absent from it shows its rollup's default. |
| searchText | String | '' | Restored free-text search query ('' = no search). |
| sorts | List<AppTableSort>? | null | The user's sort KEYS, first key leading (THE SORT IS A LIST OF KEYS) — or null when the user has never chosen. **Three states, not two** (`RecordsScreen.d.ts`, THE VIEW RECORD HOLDS ONLY WHAT THE USER CHOSE): null is never chosen, so the surface's `defaultSorts` applies and a later change to it… |
| Field | Type | Default | What it does |
|---|---|---|---|
| json * | Map<String, Object?> | — |
TimelineFieldMap support
Binds an AppGantt to ANY record type (`Gantt.d.ts` TimelineFieldMap). A member left out removes its affordance, and nothing is invented: no progress means no progress fill, and no write fields mean no drag.
| Field | Type | Default | What it does |
|---|---|---|---|
| finish * | DateTime? Function(T row) | — | The finish day (inclusive), or null. |
| finishField | String? | null | See startField. |
| label * | String Function(T row) | — | The bar's label: the accessible sentence's first words and the conflict dialog's name for the row. |
| manual | bool Function(T row)? | null | A manual row is never pushed by a plan. |
| manualField | String? | null | The editor field key that pins a row ("Keep" in a conflict). |
| milestone | bool Function(T row)? | null | Declares a milestone. |
| parent | Object? Function(T row)? | null | The row's parent key (THE ROWS AND THE OUTLINE). Absent: a flat list. |
| progress | double? Function(T row)? | null | Progress from 0 to 1, or null. |
| start * | DateTime? Function(T row) | — | The start day, or null. |
| startField | String? | null | The editor field keys a date write targets: a leading column's FieldSpec.snakeName each. Both are needed for any drag. |
| status | AppStatusMarkTone Function(T row)? | null | The status tone the bar wears. Absent: pending. |
| statusLabel | String Function(T row)? | null | The status WORD for the accessible sentence. Absent: the tone's word. |
Where it lives
| Class | File |
|---|---|
| RecordsScreen | packages/app_widgets/lib/src/floorplans/records_screen.dart |
| AppDataTable | packages/app_widgets/lib/src/kit/data/data_table.dart |
| AppGantt | packages/app_widgets/lib/src/kit/data/gantt/gantt.dart |