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
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.
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`". |
| 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 |