Theme
Customize the design system

Every control starts at the preset. Change one and every preview on the site follows, on this page and the next, until you reset it.

Contrast

Text against what it sits on. Below 4.5:1 fails WCAG AA for body text — the check a reviewer runs on any surface that takes a tone.

Waiting for the first preview to report its palette.

Corners

Sets all three radii at once. The individual values are under Geometry.

Brand 5

The accent seam an app actually brands itself through. Change these and you have made the decision a real `DesignTokens.graphite(...)` call makes.

Geometry 15

Square or rounded, and how heavy every line is. The radius row above the controls sets all three corners at once, because "what does square feel like" is one decision rather than three numbers.

Surfaces 17

Everything the app is painted ON — the ground behind the content (the frame paints it too), the cards over it, the well inset in a card, and the washes that show state.

Bands 10

The rail's head and the view bar beside it (one band across the top at 960 and wider), a pushed screen's bar, the compact bar and the compact nav bar — the band's fill, its rule, its hover and active faces, the resting field box on it, and the ink and accent control that sit on it. The rail's tree and its footer stay on the ground.

Text and controls 11

The ink ramp, the control outlines and the field's focus halo. Every pair the panel can break is watched by a contrast badge above.

Status 7

The three voices that mean something — danger, caution and ok — each as a foreground and a surface wash, danger with a border as well.

Data 5

The default pin head, its glyph and its halo, and the group bands. A pin's category color comes from the palette, named in the theme seed.

Map 11

The basemap's roles — land, water, parks, roads and their casings, buildings, labels — and the pending pin's gray. A raster basemap keeps its own imagery; these paint the map's plate and any kit-drawn map.

Scrollbars 6

Every scroller's bar: the thumb at rest and while hovered or dragged, the strip it rides, and the desk bar's thickness, side gap and corners. The desk half shows only while a fine pointer is attached; at touch the platform's own overlay keeps its size.

Advanced 6

Real controls that are not decisions — motion durations, the focus ring offset, the touch floor. Opens collapsed on purpose.

Take it with you

The Dart that reproduces what you are looking at. It is a real constructor call, because the configurator edits a real preset — what you copy is what an app can paste.

Screens

Reader

Reach for this when someone reads one multi-page document — a handbook, a drawing set, a scanned form — one page at a time. The floorplan owns the frame: the page strip, the stage and its zoom, the page and load faces, the keys, and the details column beside the page. You resolve each page to an image or a page of a PDF and render the details. Step through state to see each face, and turn initialDetailsOpen off to start with the stage alone. Give it a markup binding per page and it draws and edits the page's real PDF annotations: the markup layer over the page, its toolbar, the comments panel in the details column and the conflict notice on the stage's modeline. A host composing those pieces beside a page of its own wraps them in one markup scope.

Spec of record: design-system/components/floorplans/ReaderScreen.d.ts

4
Knobs

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.

screen_reader
Knobs
Theme

The document on its first page, on a page still being prepared, on a page with nothing drawable, then the two load faces.

Docks the details at 960 and wider; below that they always start closed, behind the bar's Details toggle.

Outlines where three values were read on the first page: one read (dashed), one confirmed (solid), and the title in focus, revealed as the reader opens.

With the regions on, points at the issued line the way a host does while a value is under the pointer: an accent edge in a halo of the paper, over the regions, and the view does not move.

Dart for these values
screen_reader()

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.

ReaderScreen widget

The FLOORPLAN for reading one multi-page document (`design-system/components/floorplans/ReaderScreen.d.ts`): a page strip, one page on the stage, and the host's details beside it. **Not carried as a prop: the PDF engine.** A `pdf` page draws through the ambient PdfPageRendererScope, else the…

Full dartdoc

The FLOORPLAN for reading one multi-page document (`design-system/components/floorplans/ReaderScreen.d.ts`): a page strip, one page on the stage, and the host's details beside it. ```dart ReaderScreen( title: 'Start-up and shutdown', meta: 'Operations › Procedures', syncStatus: const SyncStatusButton(), onBack: leave, pages: [ for (final p in doc.pages) ReaderPage( key: p.id, image: p.imageRef, pdf: (src: PdfFileSource(doc.localPath), index: p.index), pending: p.preparing, ), ], page: page, onPageChange: setPage, onOpenFile: openFile, barActions: download, details: (context, page) => DocumentDetails(doc: doc, page: page), ) ``` **Not carried as a prop: the PDF engine.** A `pdf` page draws through the ambient PdfPageRendererScope, else the production PdfrxPageRenderer; a widget test installs a stub renderer on that scope.

ReaderScreendefault constructor

Knob Type Default What it does
banner Widget? null A surface notice under the bar, surviving every face (`RecordsScreen.banner`).
barActions List<Widget> const [] The bar's trailing host actions, before the Details toggle and sync (Download). The reader has no download of its own.
details ReaderDetailsBuilder? null The host's details for the current page and the document. Null: no toggle and no column, unless markup is given (the column is then the comments panel).
error FloorplanError? null A failure the floorplan could not see (THE LOAD FACES).
focusedRegion String? null Default null: no region in focus. A region's ReaderRegion.key, drawn IN FOCUS and REVEALED each time it changes (REVEAL A REGION): its page first (reported through onPageChange), then its center at the stage's center at the zoom where it fills three fifths of the stage. A cut at the desk; the view…
highlight PageRegion? null Default null: nothing highlighted. THE HIGHLIGHT (`ReaderScreen.d.ts`, 2026-10-30): one box the host points at for a moment (the value under the person's pointer in its own list), outlined over the page above every region — a solid accent edge inside a halo of the paper, no wash — WITHOUT moving the…
initialDetailsOpen bool true Default true. Whether the details column stands open at mount at >=960 (THE DETAILS COLUMN). Below 960 the details always start closed.
initialPage int? null Default null, read as 0. The page shown on first mount when page is not controlled; clamps into range. Exclusive with page (asserted: CONTROLLED MEANS BOTH HALVES).
loading bool false The document is still arriving: the reader-shaped skeleton face.
markup ReaderMarkupBuilder? null Default null (no markup). The page's markup binding, or null for a page that takes none (`ReaderScreen.d.ts` MARKUP IS READ HERE AND MADE IN MARKUPSCREEN; `Markup.d.ts`), drawn and listed READ-ONLY whatever the binding's `readOnly` says: the layer on the page inside the zoom, only over a page the…
meta String? null The subtitle line: where the document lives.
onBack * VoidCallback? — The bar's Back, DECLARED, never inferred (`DetailScreen.d.ts` THE EXITS ARE DECLARED). REQUIRED BUT NULLABLE (#1639): the reader is always a pushed route, so every host states its exit; null is a deliberate "no way back" and renders none. Escape calls it too.
onMarkUp ValueChanged<int>? null Default null (no Fab). "Mark up", the reader's `Fab` (`edit`, extended at >=960) at the stage's block-end inline-end corner: the host opens `MarkupScreen` on the page it is handed, with the same bindings. The reader never navigates itself.
onNextDocument VoidCallback? null `]`: the next document in the host's list.
onOpenFile VoidCallback? null The unavailable and empty faces' "Open the file". Null: no button.
onPageChange ValueChanged<int>? null Every page change, from any input — the stepper, the strip, the keys, a swipe.
onPinTap ReaderPinTap? null A tap on a pin (THE NEAREST MARK TAKES THE TAP); never while placing.
onPreviousDocument VoidCallback? null `[`: the previous document in the host's list. Null: the key does nothing and the hint does not name it.
onRetry VoidCallback? null The error face's Retry. Null renders the cause with no Retry.
onViewportChange ValueChanged<ReaderViewport>? null The view where it RESTS after the person moved it — a gesture's end, a zoom step, a key, a wheel still for 250 ms (`kSettleDebounce`) — never per frame, never a value the host set, never a clamp.
page int? null The current page, zero-based, CONTROLLED. Pair with onPageChange. Null leaves the floorplan owning it, seeded from initialPage.
pages * List<ReaderPage> — The document's pages, in order. Empty is the empty face.
pins ReaderPinsBuilder? null THE PIN LAYER: a page's pins, drawn over the stage's page at their own size at every zoom, above the marks, turned by the file's `/Rotate` plus the binding's `rotation`. Drawn over a page drawn from its `pdf` and over an image-only page; never over a `pdf` page shown from its image fallback (PINS…
placing ReaderPlacing? null PLACING, CONTROLLED (ReaderPlacing): while active a tap on the page calls its `onPlace`, pins take no tap, a mark is not selected, and the cursor is the crosshair under a fine pointer. Escape ends it first (its `onChange` with false). Null: no place mode.
presence Widget? null THE PRESENCE STACK (`ReaderScreen.d.ts` `presence`; `Presence.d.ts` EVERY FLOORPLAN CARRIES IT AS A KNOB, steady-core #1331): the `AppPresenceStack` of the scope this document joins. The HOST builds it; the screen never joins a channel. It is this screen's own bar's AppHeaderBar.presence,…
regions List<ReaderRegion> const [] Default empty: no outlines. THE REGIONS (`ReaderScreen.d.ts`, 2026-10-30, steady-core #1885): where values were read, each a box on a page in its UNROTATED space, in the host's state — drawn as ink on the page inside the zoom, above the page and below the marks and the pins, over a page whose space…
selectedPin String? null The selected pin's MapPin.id, drawn selected. Null: none.
syncStatus * Widget? — The bar's last slot (`AppBar.d.ts` `syncStatus`). REQUIRED BUT NULLABLE: null renders none.
title * String — The bar's title: the document's name.
viewport ReaderViewport? null THE VIEWPORT, CONTROLLED (`ReaderScreen.d.ts`, 2026-10-20): applied when it changes to a value the reader did not itself report; a page change does not move it; a view past the page's edge is clamped, silently. Null leaves the reader owning the view (a page change keeps the zoom and returns the pan…

MarkupScreen widget

The FLOORPLAN for marking up one multi-page document (`design-system/components/floorplans/MarkupScreen.d.ts`). **Not carried as a prop: the PDF engine.** A `pdf` page draws through the ambient `PdfPageRendererScope`, else the production renderer, as the reader's do.

Full dartdoc

The FLOORPLAN for marking up one multi-page document (`design-system/components/floorplans/MarkupScreen.d.ts`). ```dart MarkupScreen( title: 'A-203 Level 2 general arrangement', meta: 'Projects › North yard › Drawings', syncStatus: const SyncStatusButton(), onBack: leave, pages: [ for (final p in doc.pages) ReaderPage( key: p.id, pdf: (src: PdfFileSource(doc.localPath), index: p.index), aspect: p.aspect, ), ], page: page, onPageChange: setPage, markup: bindingFor, // one MarkupBinding per page, one scope for all save: MarkupSave( onSave: (input) async => 'Saved as ${await cutRevision(input)}', pending: changesSinceLast, since: lastRevision, ), ) ``` **Not carried as a prop: the PDF engine.** A `pdf` page draws through the ambient `PdfPageRendererScope`, else the production renderer, as the reader's do.

MarkupScreendefault constructor

Knob Type Default What it does
banner Widget? null A surface notice under the bar, surviving every face.
barActions List<Widget> const [] The bar's trailing host actions, before the panel toggle, presence and sync.
details ReaderDetailsBuilder? null The Details tab's content for the current page, re-rendered on every page change. Null: no Details tab. INSTALLS THE NESTING SCOPE; the ordinary content is `AppDetailSections`.
error FloorplanError? null A failure the floorplan could not see (THE LOAD FACES).
initialPage int? null Default null, read as 0. The current page on first mount when page is not controlled; clamps into range (LIVE: a seed naming a page of a document still arriving lands on it when the pages do). Exclusive with page (asserted: CONTROLLED MEANS BOTH HALVES).
initialPanelOpen bool true Default true. Whether the side card stands open at mount at >=960; below 960 it always starts closed.
loading bool false The document is still arriving: the skeleton face in this screen's shape.
markup * ReaderMarkupBuilder — Each page's markup binding, or null for a page that takes none (THE ONE SCOPE). A host returns the same document-level values from every page's binding and varies only `rotation`.
meta String? null The subtitle line: where the document lives.
onBack * VoidCallback? — The bar's Back, DECLARED, never inferred. REQUIRED BUT NULLABLE (#1639): the editor is always a pushed route, so every host states its exit; null is a deliberate "no way back" and renders none. Escape calls it past every inner state (markup's, then the side card's).
onOpenFile VoidCallback? null The unavailable and empty faces' "Open the file". Null: no button.
onPageChange ValueChanged<int>? null Every change of the current page, from any input: the scroll, the stepper, the Pages tab, the keys, a comment on another page.
onPinTap ReaderPinTap? null A tap on a pin (THE NEAREST MARK TAKES THE TAP); never while placing.
onRetry VoidCallback? null The error face's Retry. Null renders the cause with no Retry.
onViewportChange ValueChanged<ReaderViewport>? null The view where it RESTS after the person moved it (250 ms, `kSettleDebounce`) — never per frame, never a value the host set.
page int? null The current page, zero-based, CONTROLLED. Pair with onPageChange. A page set from outside scrolls that page's top to the stage's top.
pages * List<ReaderPage> — The document's pages, in order. Empty is the empty face.
pins ReaderPinsBuilder? null THE PIN LAYER on every visible page: drawn above that page's own markup layer, each point in the page's UNROTATED space turned by the page's file `/Rotate` plus its OWN binding's rotation. Null: no layer.
placing ReaderPlacing? null PLACING, CONTROLLED (ReaderPlacing): while active a tap on a page calls its `onPlace` with that page's index. PLACING AND A DRAWING TOOL ARE EXCLUSIVE: entering it returns the toolbar to Select, and picking a drawing tool ends it (its `onChange` with false), as Escape does. Null: no place mode.
presence Widget? null THE PRESENCE STACK (`Presence.d.ts` EVERY FLOORPLAN CARRIES IT AS A KNOB): the host-built stack of the scope this document joins, this screen's own bar's AppHeaderBar.presence, before sync. Null renders nothing.
save MarkupSave? null Default null: no Save and no Fab (SAVE). Given, Save revision is the screen's Fab, and the binding's `write` leaves the Export menu.
selectedPin String? null The selected pin's MapPin.id, drawn selected. Null: none.
syncStatus * Widget? — The bar's last slot. REQUIRED BUT NULLABLE: null renders none.
title * String — The bar's title: the document's name.
viewport ReaderViewport? null THE VIEWPORT, CONTROLLED (`MarkupScreen.d.ts`, 2026-10-20): a share of the CURRENT page, as displayed, at a zoom over this screen's fit. It is applied when it changes to a value the screen did not itself report; a page set from outside lands at its point rather than at its top, and a page change…

FillInScreen widget

The FLOORPLAN for filling in a record's fields from its page (`design-system/components/floorplans/FillInScreen.d.ts`). **Not carried:** `captureDisplay` — the contract forwards `FormView.d.ts`'s display resolver, and this kit's capture tiles render through the capture scopes capture installs,…

Full dartdoc

The FLOORPLAN for filling in a record's fields from its page (`design-system/components/floorplans/FillInScreen.d.ts`). ```dart FillInScreen( title: 'Fill in · Batch 12', syncStatus: const SyncStatusButton(), onBack: leave, items: [for (final i in batch) FillInItem(key: i.id, title: i.name)], item: index, onItemChange: setIndex, pages: pagesOf(batchindex), fields: form.specs, sections: form.sectionSpecs, onLoad: (key) => loadItem(key), onSave: (values, {required basedOnRevision}) => saveItem(values), proposed: const { 'number': 'From the file name', 'revision': FillInProposal(source: 'Code', confidence: 'exact'), }, onConfirm: confirm, regions: {'revision': regionOf('revision')}, onConfirmNeeded: askThenConfirm, onConfirmRest: askThenConfirm, ) ``` **Not carried:** `captureDisplay` — the contract forwards `FormView.d.ts`'s display resolver, and this kit's capture tiles render through the capture scopes capture installs, exactly as `FormScreen` and `ImportScreen` on this stack take it.

FillInScreendefault constructor

Knob Type Default What it does
banner Widget? null A surface notice under the bar, surviving every face.
barActions List<Widget> const [] The bar's trailing host actions, before presence and sync.
capture FormCapture? null What the capture tiles stage through (`FormScreen.capture`).
confirmed List<String> const [] A CONFIRM TAKEN BACK (`FillInScreen.d.ts`, 2026-10-30): the fields whose proposal the person confirmed AT THIS ITEM, by key — the host's record, the current item's, as proposed is. Each one not proposed again and still at its loaded value takes "Undo confirm" as its inline action. Non-empty needs…
error FloorplanError? null A failure the floorplan could not see (THE LOAD FACES).
failureText String? Function(Object error)? null The words a REJECTED SAVE shows in the foot, from the error onSave threw: null or blank falls through to a SaveRefusal's reason, else "This item was not saved." The error's own message is never shown.
fields * List<FieldSpec> — The record's fields, the same for every item. Non-empty on the content face (asserted); the loading, error and empty faces never draw them, so a queue that does not know its fields yet may pass none (#1679).
highlight PageRegion? null Default null: nothing highlighted. THE HIGHLIGHT (`ReaderScreen.d.ts`, `FillInScreen.d.ts`, 2026-10-30): one box on the current item's pages the host points at, outlined over the page card above every region WITHOUT moving the view; on another page of the item it draws nothing.
initialItem int? null Default null, read as 0. The item shown on first mount when item is not controlled; clamps into range. Exclusive with item (asserted).
item int? null The current item, zero-based, CONTROLLED. Pair with onItemChange.
items * List<FillInItem> — The queue, in order (THE QUEUE). Empty is the empty face.
loading bool false The queue is still arriving: the skeleton face in this screen's shape.
meta String? null The subtitle line: where the queue lives.
onBack * VoidCallback? — The bar's Back, DECLARED, never inferred. REQUIRED BUT NULLABLE (#1639): the queue is always a pushed route, so every host states its exit; null is a deliberate "no way back" and renders none. Escape calls it too, after THE LEAVE QUESTION.
onConfirm ValueChanged<String>? null A proposed value confirmed: by its Confirm, or Enter in its field.
onConfirmNeeded ValueChanged<List<String>>? null Default null: no verb. "Confirm the needed" (THE BULK CONFIRM): handed the keys, in field order, of the open FillInProposal.needed proposals that do not take their own confirm. The host confirms them; the floorplan changes nothing.
onConfirmRest ValueChanged<List<String>>? null Default null: no verb. "Confirm the rest" (THE BULK CONFIRM): handed the keys of every other open proposal that does not take its own confirm (a plain line among them). The host confirms them.
onFieldFocus ValueChanged<String?>? null Default null: not reported. THE FIELD IN FOCUS: the field holding focus, by key, each time it changes; null when focus leaves every field (`AppFormView.onFieldFocus`, forwarded). The focus tree's, not THE REGIONS' held field.
onFieldHover ValueChanged<String?>? null Default null: not reported. THE FIELD UNDER THE POINTER: the fields card's field under a mouse or a stylus, by key, each time it changes; null when it leaves every field (`AppFormView.onFieldHover`, forwarded). The host maps it to highlight.
onItemChange * ValueChanged<int> — Every change of the current item, from any input, after THE LEAVE QUESTION. REQUIRED in both modes (#1639 FI1/FI2): pages are the CURRENT item's, so a host that is never told the item moved keeps handing the previous item's pages beside the next item's fields.
onLoad * FormLoadCallback — Loads an item's values and revision, handed the item's key.
onOpenFile VoidCallback? null The page card's unavailable and empty faces' "Open the file".
onPageChange ValueChanged<int>? null Every change of the current page.
onRetry VoidCallback? null The error face's Retry.
onSave * FormSaveCallback — Saves the current item; resolving moves to the next item.
onUnconfirm ValueChanged<String>? null "Undo confirm" on confirmed's field: the host returns the key to proposed, where Confirm stands again.
onViewportChange ValueChanged<ReaderViewport>? null The view where it rests after the person moved it.
optionsOf FieldOptionsResolver? null FormView's `optionsOf` (a declared rule's gated choices), forwarded verbatim (`FormScreen.optionsOf`, #1900). Absent: each field's own options.
page int? null The current page of the item, zero-based, CONTROLLED. Uncontrolled, an item change returns to the first page.
pages * List<ReaderPage> — The CURRENT item's pages, in order.
presence Widget? null THE PRESENCE STACK (`Presence.d.ts` EVERY FLOORPLAN CARRIES IT AS A KNOB): this screen's own bar's AppHeaderBar.presence, before sync.
proposed Map<String, Object> const {} Field key → where its proposed value came from (THE PROPOSED VALUE): the line itself (a `String`, "From the file name"), or a FillInProposal the kit writes the line from (THE SOURCE LINE) — any other value is refused (asserted). Non-empty needs onConfirm (asserted at build: a Map's emptiness is not…
refOptions FormRefOptionsResolver? null The form's option resolver, forwarded verbatim (`FormScreen.refOptions`).
regions Map<String, PageRegion> const {} Default empty: no outlines and no reveal. THE REGIONS (`FillInScreen.d.ts`, 2026-10-30, steady-core #1885): a field's key → where its value was read on the CURRENT item's pages, outlined there in the field's own state — read while it is still the proposal, confirmed otherwise, in focus for the field…
saveLabel String? null Default null: "Save and next" ("Save" on the last item). The save slab's label (THE SAVE).
sections List<FormSectionSpec> const [] The form's sections: the host's groups, forwarded verbatim.
syncStatus * Widget? — The bar's last slot. REQUIRED BUT NULLABLE: null renders none.
title * String — The bar's title: the queue's name.
viewport ReaderViewport? null THE HELD VIEW, CONTROLLED (`ReaderScreen.d.ts` THE VIEWPORT). Null: the floorplan holds ONE view across every page and item.

PdfPageRendererScope widget

Provides the PdfPageRenderer every PDF page below it is drawn through, and the warm-document cache over it. Absent, a consumer takes the production renderer (`PdfrxPageRenderer`) and one process-wide cache over it.

Full dartdoc

Provides the PdfPageRenderer every PDF page below it is drawn through, and the warm-document cache over it. Absent, a consumer takes the production renderer (`PdfrxPageRenderer`) and one process-wide cache over it. The renderer is read when a consumer starts drawing, not watched: swapping it under a mounted consumer closes the old renderer's documents, and the consumer keeps what it has drawn until it remounts — the same rule `ImageMode.pdfRenderer` states.

PdfPageRendererScopedefault constructor

Knob Type Default What it does
child * Widget — The subtree the renderer serves.
renderer * PdfPageRenderer — The engine PDF pages below this scope are drawn through.

AppMarkupScope widget

Holds the markup editing state for one binding (Markup.d.ts `MarkupScope`): the tool, the style, the selection and the undo history the layer, the toolbar and the comments panel share. A surface that shows one page at a time passes ONE controller across pages and the current page's binding, so the…

Full dartdoc

Holds the markup editing state for one binding (Markup.d.ts `MarkupScope`): the tool, the style, the selection and the undo history the layer, the toolbar and the comments panel share. ```dart AppMarkupScope( binding: MarkupBinding( annotations: rows, onChange: applyRowChanges, author: MarkupPerson(principal: me.id, name: me.displayName), ), child: Column(children: [ const AppMarkupToolbar(), Expanded(child: pageWithLayer), ]), ) ``` A surface that shows one page at a time passes ONE controller across pages and the current page's binding, so the tool, the style and the history survive a page change.

AppMarkupScopedefault constructor

Knob Type Default What it does
binding * MarkupBinding — The host's rows, callbacks and options.
child * Widget — The pieces.
controller MarkupController? null The editing state; null: the scope owns one for its life.

AppMarkupLayer widget

The marks of ONE page, drawn and edited over it (Markup.d.ts `MarkupLayer`). Mount it filling the displayed page, inside the hosting surface's zoom transform, under an AppMarkupScope: Geometry is PDF user space (PAGE SPACE); the layer maps it onto the page as displayed — turned by fileRotation plus…

Full dartdoc

The marks of ONE page, drawn and edited over it (Markup.d.ts `MarkupLayer`). Mount it filling the displayed page, inside the hosting surface's zoom transform, under an AppMarkupScope: ```dart Stack(children: [ pageRaster, Positioned.fill( child: AppMarkupLayer(page: 2, pageSize: cropBox, fileRotation: 90), ), ]) ``` Geometry is PDF user space (PAGE SPACE); the layer maps it onto the page as displayed — turned by fileRotation plus the user's rotation — and stores every mark it makes back in unrotated user space.

AppMarkupLayerdefault constructor

Knob Type Default What it does
fileRotation int 0 The file's own `/Rotate` for this page; rotation is added to it.
page * int — The zero-based page this layer draws and edits.
pageSize * Size — The page box: the CropBox before any rotation, in points.
rotation int? null Default null: the scope's binding's `rotation`. The user's quarter turns for THIS page, when the scope's binding is another page's: a surface holding ONE scope over pages turned apart (MarkupScreen.d.ts THE ONE SCOPE — the scope reads the current page's binding, each page renders turned by its own)…

AppMarkupToolbar widget

The toolbar (Markup.d.ts `MarkupToolbar`) for the binding of the nearest AppMarkupScope. Read-only: no toolbar at all.

Full dartdoc

The toolbar (Markup.d.ts `MarkupToolbar`) for the binding of the nearest AppMarkupScope. Read-only: no toolbar at all. Its order: Select · Pen · the highlight slot · the shapes slot · Text box · Note · Eraser (a radio group of `AppIconButton`s in the radio role, whose checked tool also takes the active face); the style control; Undo · Redo; Delete while a mark is selected; THE FINGER TOGGLE once a stylus has been seen or a preference is set; then, unless overflow is false, Rotate left · Rotate right when the binding takes rotations, and the Export menu when any file action is wired. part renders one half. ONE tab stop for the whole toolbar (THE ROVING STOP): Tab lands on the control that last had focus (the active tool at first), the arrow keys along the toolbar's axis and Home / End move across the enabled controls, wrapping, and Tab leaves.

AppMarkupToolbardefault constructor

Knob Type Default What it does
orientation MarkupToolbarOrientation?
vertical · horizontal
null Null: by window class (>=600 vertical, <600 horizontal).
overflow bool true False: the file actions and the rotate pair stay out of the toolbar (the surface places them, as `MarkupScreen` does).
part MarkupToolbarPart
all · tools · edit
MarkupToolbarPart.all Default MarkupToolbarPart.all. One half of the toolbar (THE TWO PARTS), so a surface with two rows to give it never scrolls Undo out of sight. Both halves read the same scope, so a pick in one shows in the other at once.

AppMarkupComments widget

The comments panel (Markup.d.ts `MarkupComments`): every mark of the document in two groups — "This page" and "Other pages" — each in creation order, a note's replies under it with a Reply composer, the foreign annotations read-only, and an author filter at its head. It lives under an AppMarkupScope…

Full dartdoc

The comments panel (Markup.d.ts `MarkupComments`): every mark of the document in two groups — "This page" and "Other pages" — each in creation order, a note's replies under it with a Reply composer, the foreign annotations read-only, and an author filter at its head. It lives under an AppMarkupScope and follows the layer's selection. Activating an entry selects its mark and BRINGS IT INTO VIEW: the layer showing the mark pans (and, when the mark is larger than the view, zooms out) the surface hosting it — `ReaderScreen`'s stage, `AppMapView`'s drawing — or, under a host's own scrollable, scrolls it there (`RenderObject.showOnScreen`). `ReaderScreen` mounts it in its details column; a host composing the pieces directly puts it beside its page and passes onGoTo to change page when an entry on another page is activated: the mark is brought into view once that page's layer mounts.

AppMarkupCommentsdefault constructor

Knob Type Default What it does
onGoTo void Function(int page, String annotationId)? null An entry on another page was activated: show that page (its mark is already selected, and its layer brings it into view once mounted). Null: the surface does not change page.
page * int — The page the "This page" group lists.

AppPageGrid widget

PAGES AS A GRID OF THUMBNAILS (`PageGrid.d.ts`). Under a mouse a click selects one page (the anchor), shift-click the range from the anchor, ctrl/⌘-click toggles one, ctrl/⌘ + shift adds the range, and a double-click is onOpen; a touch toggles.

Full dartdoc

PAGES AS A GRID OF THUMBNAILS (`PageGrid.d.ts`). ```dart AppPageGrid( label: 'Pages', pages: [ for (final p in packagePages) AppPageGridItem( page: p.readerPage, band: PageBand(color: p.partColor, label: 'Part ${p.part}'), boundary: p.startsPart, ), ], selected: selected, onSelectedChange: (keys) => setState(() => selected = keys), ) ``` Under a mouse a click selects one page (the anchor), shift-click the range from the anchor, ctrl/⌘-click toggles one, ctrl/⌘ + shift adds the range, and a double-click is onOpen; a touch toggles. The keys move a focus that never selects; Shift extends; Space toggles; ctrl/⌘ + A, Escape and Enter. Keys are handed over in page order.

AppPageGriddefault constructor

Knob Type Default What it does
boundaryLabel String 'starts a new part' What a boundary cell adds to its name.
emptyText String 'No pages.' The empty face's line.
initialSelected List<String> const [] The selection on first mount when selected is null.
label * String — The grid's accessible name ("Pages").
loading bool false The pages are still arriving: the skeleton face.
onOpen ValueChanged<int>? null Enter or a double-click on a page: its index.
onSelectedChange ValueChanged<List<String>>? null Every selection change, with the keys in page order.
pages * List<AppPageGridItem> — The pages, in order. Empty is the empty face.
selected List<String>? null The selected pages' keys, CONTROLLED. Null leaves the grid owning the selection, seeded from initialSelected.

AppDocumentPane widget

A record's page in a docked pane (`DocumentPane.d.ts`): the reader's one-page stage holding ONE view across records, the record and page steppers and the zoom pair, the regions, a highlight, and an optional read-only column of the record's values.

Full dartdoc

A record's page in a docked pane (`DocumentPane.d.ts`): the reader's one-page stage holding ONE view across records, the record and page steppers and the zoom pair, the regions, a highlight, and an optional read-only column of the record's values. ```dart AppDocumentPane( recordKey: row.id, title: row.title, pages: pagesOf(row), viewport: view, onViewportChange: (v) => setState(() => view = v), onPreviousRecord: previous, onNextRecord: next, regions: regionsOf(row), focusedRegion: cursorField, highlight: hovered == null ? null : regionOf(row, hovered), fields: fields, values: row.values, onFieldHover: (key) => setState(() => hovered = key), ) ```

AppDocumentPanedefault constructor

Knob Type Default What it does
actions List<Widget> const [] The head's trailing host actions (a "Full size"), or the seat's.
confirmed List<String> const [] The fields checked at THIS record, by key: each draws its ✓ checked while it is not proposed again and still at its value. Non-empty needs onUnconfirm.
fields List<FieldSpec> const [] THE VALUES COLUMN's fields. Empty: no column.
focusedRegion String? null GO TO A REGION: a region's key, drawn in focus and FRAMED each time it changes (`ReaderScreen.d.ts` REVEAL A REGION): its page first, then three fifths of the stage. A cut at the desk.
highlight PageRegion? null THE HIGHLIGHT: one box the host points at, outlined over the page above the regions WITHOUT moving the view. On another page it draws nothing.
onConfirm ValueChanged<String>? null A proposed value checked, by its ✓ or Enter in its field.
onConfirmAll ValueChanged<List<String>>? null "Mark all checked", drawn above the values while a shown field is still proposed: handed, in field order, every one still at its proposal that does not take its own confirm. Null: no verb.
onFieldFocus ValueChanged<String?>? null THE HIGHLIGHT FOLLOWS THE FIELD: the field holding focus (its editor or its ✓), by key, each time it changes; null when focus leaves every field. The form posture only.
onFieldHover ValueChanged<String?>? null THE FIELD UNDER THE POINTER in the values column: the row a mouse or a stylus is over, by its field's key, each time it changes; null when it leaves every row and when the column leaves.
onNextRecord VoidCallback? null THE RECORD STEPPER's next, and `]`.
onOpenFile VoidCallback? null The empty and unavailable faces' "Open the file". Null: no button.
onPageChange ValueChanged<int>? null Every page change, from any input.
onPreviousRecord VoidCallback? null THE RECORD STEPPER's previous: the host's sibling step, and `[`. Null beside onNextRecord: drawn disabled. Both null: no stepper.
onUnconfirm ValueChanged<String>? null The checked ✓ pressed again: the host returns the key to proposed.
onValueChange DocumentPaneValueChanged? null THE FORM POSTURE (`DocumentPane.d.ts`, 2026-10-31): non-null makes each value the form kit's own editor for its type, committing here — a discrete field on its pick, a typed one when focus leaves it or on Enter — with the value and the record it was made on, only when it differs from values. Null…
onViewportChange ValueChanged<ReaderViewport>? null The view where it RESTS after the person moved it — never per frame, never a value the host set, never a clamp.
page int? null The current page, zero-based, CONTROLLED. Pair with onPageChange.
pages * List<ReaderPage> — The shown record's pages, in order. Empty is the empty face.
proposed Map<String, Object> const {} THE PROPOSED VALUE, IN THE PANE: field key → where its value came from, a `String` line or a FillInProposal (FillInScreen's). The host's record of what nobody has checked. Non-empty needs onConfirm.
recordKey * String — The shown record's identity, stable across rebuilds: a change is a record switch (THE RECORD STEP). The page returns to the first unless page is controlled; the view holds.
regions List<ReaderRegion> const [] THE REGIONS: where the shown record's values were read, outlined on its pages in the host's state (`ReaderScreen.d.ts` THE REGIONS).
sections List<FormSectionSpec> const [] The values column's groups (the form's section declarations).
title * String — The record's name: the head's title and the stage's accessible name.
values Map<String, Object?> const {} The shown record's values, in the wire shape, for the values column.
viewport ReaderViewport? null THE HELD VIEW, CONTROLLED: applied when it changes to a value the pane did not report; a record switch and a page change keep it. Null: the pane holds the view itself, across records and pages.

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.

AppPageGridItem support

One page in the grid: the reader's own page, its band and its boundary.

Field Type Default What it does
band PageBand? null The color band, or none.
boundary bool false True: a new part starts at this page (THE BOUNDARY MARK).
page * ReaderPage — The page, exactly as the reader takes it; its `key` is its identity.

FieldSpec support

Per-field static metadata describing how a single column renders in a `FormView`. Pure data — no widget references, no controllers.

Field Type Default What it does
allowOtherValues bool false Whether an `enumValue` or `enumList` column accepts values outside enumValues (origin `AllowOtherValues:true` — 238/263 enums). When true an `enumValue` editor is an open combobox: a text input + filtered suggestions + a pinned `Add "{typed}"` row committing the typed value verbatim; an `enumList`…
columnType * ColumnType — Column type — drives editor dispatch in `FieldEditorRegistry`.
decimalPlaces int? null An `exactDecimal` field (and a quantity's number): at most this many digits after the point, counted as typed (`FormView.d.ts` AN EXACT DECIMAL IS ITS STRING). An intrinsic check. Null: any number of places, within `numeric`'s limit.
defaultUnit String? null A quantity field's starting unit code; null starts at the first of units.
derive FieldDeriver? null Recomputes this field's value from the live values map whenever any field changes — the in-form face of a write-time computed column. A derived field is **read-only by construction**: `FormController.isEditable` returns false for it, so a user edit can never be clobbered by the next unrelated…
editableIf FieldPredicate? null Reactive `Editable_If` — when non-null, overrides isReadOnly: the editor accepts input only while this returns true. Null falls back to `!isReadOnly`. §D1.
enumDisplay EnumDisplay EnumDisplay.buttons Display mode for `enumValue` columns (see EnumDisplay). Ignored for every other column type. Defaults to EnumDisplay.buttons.
enumValues List<String>? null Pre-defined value set for `enumValue` / `enumList` / `color` editors. Null for other column types.
exactMax ExactDecimal? null The inclusive upper bound, as exactMin.
exactMin ExactDecimal? null An `exactDecimal` field (and a quantity's number): the inclusive lower bound, compared EXACTLY (ExactDecimal.compareTo, never through a float). An intrinsic check. A quantity's bound is in the value's own unit; a bound in another unit is the host's validator.
formatRules List<FormatRule<Map<String, Object?>>>? null FORMAT RULES on the LOCKED face only (`FormView.d.ts` THE LOCKED FACE WEARS ITS RULE, #1006; `FormatRules.d.ts`): while the field is locked (read-only, editableIf false, a derived field) its value wears the first holding rule, as a detail field does. The SUBJECT is the field's value, and the ROW an…
helper String? null Optional helper text rendered below the field label. Slice 18 surfaces this in `FieldCard`; emit threads it from the source per-field description when present.
initialValue Object? Function()? null Optional seed for the field's value at form construction. A plain thunk — `() => DateTime.now()`, `() => 0.45` — invoked once when the controller builds (and again on reset). Returning `null` (or leaving it unset) starts the field empty.
inlineAction FieldAction? null Field-scoped auxiliary action rendered beneath this field's editor — see FieldAction. Null renders no affordance.
isHidden bool false Hidden fields are seeded into form state and round-trip through save, but render no editor — useful for a system key column carrying a generated id via initialValue.
isOwed bool false Required LATER, never at save (`FormView.d.ts` A FIELD MAY BE OWED, #1496): the micro-label carries the required marker and the control is announced required, but the field never gates the save, never counts toward `FormController.requiredBlankCount` and never raises the required message, and its…
isReadOnly bool false Static read-only flag — the editor is disabled whenever this is true.
isRequired bool false Static "required regardless of state" flag — drives the required-blank validation in `FormController`.
label * String — Human-readable label rendered as the editor's `labelText`.
listSeparator String? null The write separator of an `enumList` / `refList` field — the column's own spelling (`ColumnDef.listSeparator`, threaded by the field plan, `bindFieldPlan`). Null writes `kEnumListSeparator`. The source platform's item separator is a per-column property; reads are separator-tolerant.
listValue bool false An `enumList` (or a list of refs) whose value is a `List<String>`, not the joined string (`FormView.d.ts` AN ENUM LIST'S VALUE MAY BE ITS LIST, #1496): members verbatim, in held order, any text, never split, so a member may hold the separator. A held joined string is read through the list grammar…
maxDate DateTime? null The latest offered day, inclusive, as minDate.
minDate DateTime? null A `date` / `dateTime` field's earliest offered day, inclusive (`FormView.d.ts` A DATE CAN BE BOUNDED): reaches `AppDateField.min` and an intrinsic check. Null: unbounded.
noLabel String? null The no-word of a `yesNo` field (origin `NoLabel`) — where the value is READ BACK rather than edited (a detail, a cell, a review line); the form's row never shows it. Null reads "No". §D4.
numberFormat AppNumberFormat? null How a `number` / `decimal` / `percent` value reads on the field's LOCKED face (`FormView.d.ts` `FormField.numberFormat`; the rules are `DataTable.d.ts` NumberFormat's). THE LOCKED FACE READS AS THE CELL READS: while the field is not editable (read-only, editableIf false, a derived field) the box…
options List<FieldOption>? null Labeled options for an `enumValue` / `enumList` field (`FormView.d.ts` AN OPTION HAS A LABEL, AND A RETIRED OPTION IS ONLY EVER HELD; steady-core #1491). When set it wins over enumValues: the stored value is FieldOption.value, every face shows FieldOption.label, and a FieldOption.retired option is…
override FieldOverride? null The field's value comes from a WIDER SCOPE and may be overridden here (`FormView.d.ts` A FIELD MAY INHERIT ITS VALUE) — see FieldOverride. Null: an ordinary field whose value is its own, and nothing renders.
placeholderOf String? Function(Map<String, Object?> values)? null What an EMPTY value MEANS, read against the live values (`FormView.d.ts` `FormField.placeholderOf`, 2026-10-09, #1217) — a nullable override whose blank is "auto", or a typical rate the blank falls back to. The answer paints in `--ink40` inside the editor's box while the value is blank: on the form,…
ref RefFieldConfig? null Ref-column config — target table, choice-list stream, add-new builder. Null for every column that holds no ref: a `ref` column, and a LIST of refs (an `enumList` whose members are target keys — its editor is `kitRefListEditor`), carry one.
requiredIf FieldPredicate? null Reactive `Required_If` — when non-null, overrides isRequired: the field is a save-gate only while this returns true. Null falls back to the static isRequired. §D1.
resetRule ResetRule? null Reactive reset rule (origin `Reset_If` / `ResetOnEdit`). Null means the field is never auto-cleared. See ResetRule, §D1.
ruleLock FieldRuleReading? null A DECLARED RULE's lock (`FormView.d.ts` `FormField.ruleLock`, FieldRules.d.ts THE BINDING): why the rule locks the field over the live and stored values, or null while it does not. The binding sets it (withFieldRules) and ANDs the same answer into editableIf; the form reads it only for THE WORDS…
ruleRefusal FieldRuleReading? null A DECLARED RULE's save-time check (`FormField.ruleRefusal`): why the server would refuse THIS save's write of the field, or null when it permits. A refusal is the field's error line, in THE WORDS (`FormController.ruleRefusal`). The binding sets it.
section String? null Optional section grouping. Null means "no section header" — the field still renders, but in the default un-headered section. Sourced from the source form's section settings.
showIf FieldPredicate? null Reactive `Show_If` — when non-null, the field renders an editor only while this returns true over the form's current values. Null means "always visible" (the static default). A hidden-by-rule field leaves the layout, semantics, and focus tree; it does **not** null its value (only an explicit reset…
snakeName * String — snake_case column name. Matches the key produced by `RowCodec<T>.encode(row)` and serves as the form-state map key.
span FieldSpan FieldSpan.auto Layout span on tablet form factors (see FieldSpan). Phone always collapses to single column regardless of span. Defaults to FieldSpan.auto — the editor's natural width decides pairing.
stepValue num? null Step increment for a `number` / `decimal` / `percent` editor (origin `StepValue`, e.g. `number_of_people` StepValue 1; a percent steps in the whole points it shows). When non-null the editor flanks the field with −/+ 48dp targets; the field still accepts typed input. Null renders a plain numeric…
suggestedValues SuggestedValuesResolver? null Live suggestion source for an OPEN enum's combobox, and an OPEN enum list's entry surface (allowOtherValues) — typically the column's own already-used values, so free entry converges on the estate's vocabulary instead of minting near-duplicates. The editor subscribes once for its lifetime and merges…
temporal TemporalSemantic TemporalSemantic.instant What a NAIVE `dateTime` value means (`FormView.d.ts` `temporal`, the column's `ColumnDef.temporal`, threaded by `bindFieldPlan`): TemporalSemantic.instant (the default) reads it in device-local time; TemporalSemantic.wallClock keeps the time as written. Read by a data table cell's typed entry and a…
units List<QuantityUnit>? null An `exactDecimal` field that edits a QUANTITY (`FormView.d.ts` A QUANTITY IS A NUMBER AND ITS UNIT): the units it offers, in order. When set the value is a QuantityValue (`value`, `unit`), never a string; one unit is the box's suffix, several an `AppSelect` beside it. Null: a plain exact decimal.
validator FieldValidator? null Value-level validation rule. Null means "any non-blank value is acceptable" (the static default) — this field is the ONLY way a form rejects a value on grounds other than blankness. The rule runs **after** the blank / required decision and only on a non-blank value, so it never re-implements…
yesLabel String? null The yes-word of a `yesNo` field — the label of its one checkbox row, what ticking asserts (origin `YesLabel`, e.g. `report_request.is_report_package` → "Report Package"). Static: it is never re-worded per toggle. Null renders "Yes".

FillInItem support

One item of the queue (`FillInScreen.d.ts` `FillInItem`, THE QUEUE).

Field Type Default What it does
done bool false True: nothing is left to fill in; the queue menu says "Done".
key * String — Identity, stable across re-renders; what `onLoad` is handed.
title * String — The item's name: the page card's head and its entry in the queue menu.

FormCapture support

What a form's capture tiles stage through — the `FormScreen.d.ts` `capture` slot, which is `FormView.d.ts`'s `capture` resolver in the spelling THIS kit's editors read. `FormView.d.ts`'s own flutter clause names them: on this stack a capture tile resolves its value through an inherited capture…

Field Type Default What it does
canvas CanvasCapture? null Stages a `signature` / `drawing` field's rasterized pad (CanvasCaptureScope).
file FileCapture? null Stages a `file` field's pick (FileCaptureScope).
image ImageCapture? null Stages an `image` field's camera/library pick (ImageCaptureScope).
location LocationCapture? null Reads the device location a `latLong` field offers (LocationCaptureScope).
locationCenter CaptureLocationCenterResolver? null Answers where an EMPTY `latLong` field's picker opens — `FormView.d.ts` `CaptureLocationPicker.fallbackCenter` (CaptureField.d.ts THE EMPTY FIELD'S OPENING CAMERA, #1373): per field and per record over the live values, a project's centroid say. It is shown while the picker's opening locate is…

FormSectionSpec support

One form section's DECLARATION (`FormView.d.ts` THE SECTION IS DECLARED ONCE; `FormSection`, Contained, 2026-09-29, #846). Fields JOIN a section by naming its id — its key, else its label — in `FieldSpec.section`; this entry only adds the words the Contained form card states beside them.

Field Type Default What it does
collapsed bool? null The CONTROLLED collapse state of a collapsible section. Set, it is the state on every build: the head's toggle, the index's selection and a blocked save only ask through onCollapsedChanged, and the section moves when the host hands the new value back. A controlled section with no onCollapsedChanged…
collapsible bool false When true the section's title is a disclosure toggle and its fields may collapse: out of the view and the focus order, still mounted, still gating the save (React `FormSection.collapsible`). Default false.
initiallyCollapsed bool false The UNCONTROLLED seed of a collapsible section: read once, when the view first sees the section, and never re-seeded by a later value. Ignored while collapsed is set (React `FormSection.defaultClosed`).
key String? null The section's stable identity, the string its fields name in `FieldSpec.section` (`FormView.d.ts` A SECTION HAS A KEY, #1496). When set, label is the title alone, free to change, and two keyed sections with one label stay two sections. Null: label is the identity, as before (React…
label * String — The section's title (the title role); with no key, also the string its fields name in `FieldSpec.section`.
onCollapsedChanged ValueChanged<bool>? null Reports the state the USER asks for — `true` to collapse: the head's toggle, or `false` when the index selects a collapsed section or a blocked save lands in one. Controlled or not; never at mount and never for a collapsed value the host changed (React `FormSection.onCollapsedChange`).
purpose String? null One sentence stating what the section is for ("Who does the work, and where."), set in the purpose column beside the fields, or under the title when the card is stacked. Null renders the title alone.

MarkupBinding support

What a host hands a surface (or an `AppMarkupScope`) for one document's markup, as seen from one page (Markup.d.ts `MarkupBinding`).

Field Type Default What it does
actions MarkupFileActions const MarkupFileActions() The host's file actions.
annotations * List<MarkupAnnotation> — The DOCUMENT's marks, every page, in z-order (last on top). The layer draws its page's; the comments panel lists all.
author * MarkupPerson — This user: whose name a new mark carries.
canEdit bool Function(MarkupAnnotation annotation)? null Per mark: may this user edit it? Null: every mark. A refused mark selects for reading only and is never erased.
colors List<MarkupColor> kMarkupDefaultColors The swatches, in order.
conflicts List<MarkupConflict> const [] Refused changes to tell the user about, oldest first.
focusedBy Map<String, MarkupPerson> const {} Mark `id` → the OTHER person who has it in focus (FOCUSED ELSEWHERE).
foreign List<MarkupForeignAnnotation> const [] The annotations the kit does not edit, listed read-only.
onChange * ValueChanged<List<MarkupChange>> — Every user action, as one batch of row changes the host applies together.
onConflictDismiss ValueChanged<String>? null A notice left (acted on, dismissed, or overtaken by another change).
onFocusChange ValueChanged<String?>? null This user's focus: the selected mark's `id`, or null.
onPencilOnlyChange ValueChanged<bool>? null Every pencil-mode change, automatic or by the finger toggle, so the host can store the device preference (pencilOnly).
onRotate ValueChanged<int>? null Offers Rotate left / Rotate right; receives the new total.
online bool true False: the file actions that do not queue are disabled with "Needs a connection".
pencilOnly bool? null The device's PENCIL MODE preference (Markup.d.ts INPUT: PENCIL MODE), held by the host like the theme mode — per device, never per account. Null: automatic (the first stylus enters pencil mode). True: the scope opens in pencil mode with the finger toggle shown and no notice.
readOnly bool false Draws and lists, edits nothing (no toolbar, no composer, no menus).
rotation int 0 The user's quarter turns for this page, added to the file's `/Rotate`: 0, 90, 180 or 270.

MarkupController support

The shared editing state of one markup binding (Markup.d.ts `MarkupScope`). `AppMarkupScope` owns one (or takes the host's) and keeps it attached to the current binding; a surface that changes page keeps ONE controller, so the tool, the style and the history survive.

Field Type Default What it does
clock DateTime Function()? null
newId String Function()? null

MarkupSave support

SAVE (`MarkupSave`): the host's revision verb. Every mark is already a row the moment it is made, so Save never guards against losing work: it CUTS A REVISION, and the host decides what that means.

Field Type Default What it does
ask MarkupSaveAsk? null Which words a revision takes. Null (the default): one tap.
label String 'Save revision' The Fab's label, the panel's head and its slab.
onSave * MarkupSaveCallback — Cuts a revision.
pending * int — Changes since the last revision. 0 disables Save.
queues bool false True: the host queues a save made offline, so Save stays enabled offline.
since String? null The last revision's name, for "`n` changes since `since`" and "No changes since `since`". Null: "`n` changes".
suggestedName String? null The name field's prefill ("Rev D"), selected so a quick save is Enter.

PageRegion support

Where a value was read (`ReaderScreen.d.ts` `PageRegion`): a page of the document, zero-based as `page` counts, and a box on it.

Field Type Default What it does
box * PageBox — The box on that page, in its unrotated space.
page * int — The page, zero-based.

ReaderPage support

One page of the document (`ReaderPage`, A PAGE IS A SOURCE THE KIT CAN DRAW). This kit draws pdf first and falls back to image.

Field Type Default What it does
aspect double math.sqrt1_2 Width / height, sizing the plate before the pixels arrive. Default 1/√2 (A-series portrait); a landscape sheet says so.
image String? null Any reference AppImage reads — the fallback when pdf is absent or cannot be drawn.
key * String — Identity, stable across re-renders (a page row's key).
label String? null The page's accessible label. Null reads "Page `n`".
pdf ReaderPdfPage? null One page of a PDF, rendered on the device through the kit's PdfPageRenderer.
pending bool false True while the host is still preparing this page: the plate shows "Preparing page" and stays navigable (THE PAGE FACES).

ReaderPlacing support

PLACING, CONTROLLED (`ReaderScreen.d.ts` / `MarkupScreen.d.ts` `ReaderPlacing`, #1639): the place mode's state and BOTH of its channels as one record, so a mode that cannot end (no onChange: Escape swallowed) or cannot place (no onPlace: an opaque layer that places nothing) is not a configuration a…

Field Type Default What it does
active * bool — Whether the place mode is on.
onChange * ValueChanged<bool> — The floorplan ends placing (Escape; on MarkupScreen, a drawing tool): called with false. The host owns active and moves it.
onPlace * ReaderPlace — A tap on a page while active: the spot in that page's UNROTATED space, and the page's index. The floorplan draws nothing there; the host adds the pin it means.

ReaderRegion support

One region outlined on a page (`ReaderScreen.d.ts` `ReaderRegion`, THE REGIONS): where a value was read, in the host's state.

Field Type Default What it does
box * PageBox —
key * String — Identity, stable across rebuilds: what ReaderScreen.focusedRegion names.
label String? null Its name to assistive tech, the state appended ("Number, read"). Null: decorative.
page * int —
state * ReaderRegionState — The host's: ReaderRegionState.read draws dashed, ReaderRegionState.confirmed a solid hairline.

ReaderViewport support

THE VIEWPORT (`ReaderScreen.d.ts`, 2026-10-20): what the stage shows, as a SHARE OF THE PAGE (W3C Media Fragments `#xywh=percent:`) — the zoom over fit and the page point at the stage's center, of the page AS DISPLAYED. A share survives a page of another size.

Field Type Default What it does
x * double — The page point at the stage's center: 0 the page's left edge, 1 its right (physical, never mirrored under RTL).
y * double — The page point at the stage's center: 0 the page's top, 1 its bottom.
zoom * double — The zoom over fit: 1 is fit width, 4 the most. Clamped into [1, 4] where it is applied.

Where it lives

Class File
ReaderScreen packages/app_widgets/lib/src/floorplans/reader_screen.dart
MarkupScreen packages/app_widgets/lib/src/floorplans/markup_screen.dart
FillInScreen packages/app_widgets/lib/src/floorplans/fill_in_screen.dart
PdfPageRendererScope packages/app_widgets/lib/src/pdf/pdf_page_renderer_scope.dart
AppMarkupScope packages/app_widgets/lib/src/kit/data/markup/markup_scope.dart
AppMarkupLayer packages/app_widgets/lib/src/kit/data/markup/markup_layer.dart
AppMarkupToolbar packages/app_widgets/lib/src/kit/data/markup/markup_toolbar.dart
AppMarkupComments packages/app_widgets/lib/src/kit/data/markup/markup_comments.dart
AppPageGrid packages/app_widgets/lib/src/kit/data/page_grid.dart
AppDocumentPane packages/app_widgets/lib/src/kit/data/document_pane.dart