Repository navigation
Conversation
A new example, examples/lims_gallery: laboratory screens from design mockups, written as ui/1 documents to find what the declarative UI cannot express. It has no backend; each screen keeps its records in its own state. The scaffold is a Qt-free screens library whose bundle registers an index and every built design with a green ui-theme/1 theme, recording-backend tests over a helper that mounts a screen by id at a width class, a Qt Quick render smoke that mounts every screen at both widths, and the lims-gallery app in the examples client. Design 01, the e-signature: worksheet WS-2026-0412 signed to approve its results. Expanded, a dialog over the worksheet with the record summary, the refused-password banner, signer, meaning, reason and password; a wrong password costs an attempt, the right one signs. Compact, the same fields as an in-page sheet with Cancel at the top and a full-width sign button. Gaps: G1, G2, G3, G4, G5, G6, G7, G8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 02, sample receipt at the bench. Expanded: a barcode scan field that keeps its focus, a notice for a duplicate or unknown scan, the card of the sample being checked (type, temperature with its 2-8 C range, received at, storage, containers, courier, the four condition checks, a note) with Reject and Receive-and-scan-next, and this session's outcomes beside it. The head carries the session's start and the received and rejected counts. Receiving or rejecting (with a reason, in a dialog) records the outcome and moves on to the next registered sample; a temperature out of range blocks receiving. Compact: one column with every field kept, the session folded into a collapsible, and the two actions as the last row. Adds a cellTag helper for status tags in table cells, and a submit helper to the test harness. Gaps: G1, G2, G5, G8, G9, G10, G11, G12, G13, G14, G15, G16, G17. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 03, the record of HPLC-02 (EQ-0142). The screen head carries the breadcrumb, the service, qualification and calibration-due tags, and the Log maintenance and Book actions; an overflow menu holds Record calibration, Print equipment label, Export history and Take out of service. The record is in tabs: Overview shows the details, recent events, qualification stages and the current booking's elapsed bar; Bookings lists the bookings; the tabs the design does not draw say so. Logging maintenance or a calibration adds a recent event (a calibration clears the one due), taking the instrument out of service asks a reason and changes its status, and booking adds a booking. Compact stacks the details as label-over-value pairs, puts Book and Log maintenance in the page as large buttons, and folds booking, qualification and events into collapsibles. Gaps: G1, G2, G5, G7, G18, G19, G20, G21, G22, G23, G24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 04, the audit trail. Expanded, a facet rail beside a read-only table: record type and action checkboxes with their counts, a user select and a from-to date range narrow the trail, each active filter shows as a chip whose "×" removes it, and Clear all empties them. The table shows when, who, the record, the field, the old value in red, the new in green, and the reason; with no entry left it shows an empty state. The head exports CSV and PDF. Compact stacks each entry as a card, nothing shortened, and moves the facets into a drawer opened by "Filters · n". Gaps: G1, G2, G5, G13, G25, G26, G27, G28. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 05, the sample list. A dense table of open samples with their product and lot, tests, priority, status, due time (an overdue one in red) and specification outcome; a row with a result out of specification is tinted. Search, status and priority filter it, and an empty state offers to clear the filters. A first column of check boxes selects rows, three as the design opens; the bulk bar on a green-tinted surface counts them and assigns them to a worksheet, changes their priority, prints labels or clears them, and "Select all 312" widens the selection. Compact turns each row into a card with the same selection, tinted when out of specification, with the filters in a drawer and the bulk bar after the list. The bundle's theme gains two surfaces, selected and outOfSpec, for those tints. Gaps: G1, G2, G5, G16, G18, G25, G26, G28, G29, G30, G31, G32, G33. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 06, step 2 of the stability study wizard: steps for the four stages, the study start, pull window and containers per pull, the conditions × timepoints matrix of check boxes with each condition's pull count, and a summary of pulls, first and last pull and containers needed against stock. Too few containers in stock is a field error and a banner that keep Continue on this step. Compact folds each condition into a collapsible of timepoints. Missing: the screen is not registered in designs.cpp and neither the screen nor its test is in CMakeLists.txt. Its unit tests passed when registered, but the render smoke fails with a Qt Quick GridLayout polish loop in MorphKeyValue.qml (G34), not isolated yet. Steps 1, 3 and 4 are placeholders. Gaps: G1, G5, G9, G16, G25, G34, G35, G36, G37, G38, G39. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The ten design mockups, the lanes' brief and the gap log lived in a session scratch directory. Kept beside the gallery, the work can resume from the branch alone: the brief opens with what is done, what is WIP and the next steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
This was referenced Oct 24, 2026
Open
Open
Open
Open
Design 06, a four-step wizard for a new stability study of Paracetamol 500 mg tablets. Step 1 picks the lot, its container closure and the study's reason; the lot sets the stock the containers are checked against. Step 2, the one the design draws, holds the study start, pull window and containers per pull, the conditions x timepoints matrix of check boxes with each condition's pull count, and a summary of pulls, first and last pull and containers needed against stock. Too few containers is a field error and a banner that keep Continue on this step. Step 3 chooses the tests run at each pull, each at every pull or at T0 and the last pull, with the test runs they add up to. Step 4 reviews the study and enables signing once the reviewer confirms it. Compact folds each condition into a collapsible of timepoints and each test into a card. The summary and the review are rows of a caption beside a text, not a keyValue: on Qt Quick a keyValue reports a GridLayout polish loop for some caption and value widths, and the summary's reach them. The gap log records the smallest reproduction found. Gaps: G1, G5, G9, G13, G16, G25, G34, G35, G36, G37, G38, G39, G40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 07, the layout of 96-well ELISA plate PL-0193 for worksheet WS-2026-0415. Expanded shows the fourteen samples to place beside the plate, a 13-column grid of row letters and 96 wells: the template's standards, controls and blank on tinted surfaces, and sample wells holding S1 to S67 or empty. A sample is dragged into a well, or the selected wells are filled with the next samples in plate order; Clear wells returns their samples, and Auto-fill fills every empty well. Arrow keys move between wells, Shift with an arrow extends the selection, and Enter opens Assign for the focused well. Compact lists the wells row by row, each row a collapsible titled with its counts, with Assign on every empty well. The theme gains the standard, control and blank surfaces. Gaps: G1, G2, G5, G23, G25, G26, G28, G32, G41, G42, G43, G44. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Nine gap-log entries are defects in the library rather than vocabulary the designs need. Each is now filed, and the entry names its issue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 08: worksheet WS-2026-0412's results for two samples, with the system suitability checks above the grid. Each result is entered to the decimal places of its specification and evaluated against it as it is typed; the assay's mean and RSD and the total impurities are calculated, read-only lines of the grid. Committing a changed result opens a reason-for-change dialog (reason and comment required) that confirms or undoes the change; the screen opens on the design's state, Impurity B changed from 0.12 % to 0.21 % and out of specification. Compact shows one sample at a time with Previous and Next, and asks the reason inline. Gaps: adds G45-G48; extends G1, G2, G5, G6, G8, G9, G10, G16, G35, G40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Lane 4 stopped on request before building designs 09 and 10. This keeps the gaps found while reading 09 (G49-G51, all inferred from reading, not reproduced) and updates the brief's status: 08 done, 09 and 10 not started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 09: the four worksheets awaiting review in a queue beside the one under review, split by a splitter. The worksheet shows its submission, its tags, and tabs for its results (flagged or all, each compared with the previous lot), the previous lots, its audit trail and comments. WS-2026-0409, which the reviewer entered, cannot be approved, and the reason is shown in the queue, in a banner and beside the disabled button. Returning and rejecting open a dialog that needs a reason and take the worksheet out of the queue; "Approve and sign…" navigates to design 01's e-signature with `as: dialog`. Compact shows the worksheet as its own page, the queue behind a back button, actions in one row. A cell tag now keeps its own height in a taller table row. Gaps: G49 and G50 reproduced; G51 gets its workaround; adds G52-G54; extends G1, G2, G5, G14, G16, G25. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 10, the most complex of the ten: - a navigation sidebar, which is a menu on compact; - the system suitability alert and the day's figures; - an approximated Levey-Jennings chart; - the instruments; - the worklist beside a non-modal, resizable sample pane with Previous/Next. The pane says why the held sample cannot be submitted. All ten designs are now built. The gap log gains G55 (no chart, and no way for a document to bring one) and G56 (a screen's own navigation). G49 is filed. The brief's status table marks 09 and 10 done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The 56 gap-log entries become five coherent changes to the declarative UI: - A, defects and responsive layout; - B, tables; - C, text, fields and small controls; - D, layout, overlays and navigation; - E, expressions and charts. Each batch is one design, one review and one implementation on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The cause of each batch A gap, re-checked in the code, with the fix, the spec changes, tests with controls, and risks. G42 is reproduced on all three backends; G34's cause differs from the gap log's inference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Enter inside a dialog follows one key order on every backend, width classes are defined in units, the touch height token is a sibling of control.height, and the table, head-line, close-self and intOf plans cover what the review found missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
A progress bar, a custom component's number prop and every integer property read a decimal through Rational::toDouble, which rounds to the value's own places. div keeps its operands' places, so 29 / 100 reached the bar as 0, and intOf turned 29/10 into 3 instead of truncating it to 2. The interpreter now reads the unrounded quotient (detail::realOf) for progress and component numbers, and intOf truncates in integer arithmetic. toDouble stays a display reading. No computed decimal in the examples or the corpus feeds an intOf key; the gallery's progress(level / 10) now fills half the bar at level 5 instead of all of it. Closes #1051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
lowerRows built a forEach without commonOf, and ui::forEach had no way to take a Common, so testId, visible, sizing and every other common key on a forEach was loaded, type-checked and dropped without a report. ForEach::common and the mount already applied them. Both ui::forEach overloads take a trailing Common, and the interpreter fills it with commonOf as it does for a table. Closes #1052 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The loader accepted a splitter's sizes and the view tree, the mount and both frontends applied them, but the lowering never read the key, so every document splitter shared its space equally. The lowering now binds sizes, reading each item as an integer property is. A value that is not a list of numbers is reported once for its path and lowers to an empty list. The document still has no onResize: a drag stays the splitter's own. The gallery's review test that pinned the dropped sizes now expects them. Closes #1055 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Every close: self went to InterpreterHost::closeSelf, so a dialog's own "close" asked the host to end the screen. The author API and document.md define self as the dialog or drawer the command runs in, while host.md uses it to end a screen opened by navigate. The interpreter now walks the command's scope and its parents to the first one a dialog or drawer owns and closes that overlay as close: <id> does. A walk that reaches the screen's root scope still asks the host, so both meanings hold. A walk that ends at a component's fallback scope, with no overlay of the component's own, is reported and fails instead of closing the screen around the component. Closes #1048 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Enter on a focused button that declared its own Enter chord clicked it on Qt Quick and the terminal, and ran the chord on RecordingBackend; the gallery's plate test passed only because it runs on the latter. Which focused widgets let Enter through to a dialog's default also differed: a terminal text input took Enter whether or not it submitted, and a terminal check box toggled on it. Every backend now routes a key the same way: first a chord the focused widget declares itself, then the widget's own keys (a button takes Enter and Space, a single-line text input takes Enter only when it submits, a check box takes Space, a table takes Enter), then the ancestors' chords, innermost first. - Terminal: dispatch, the field view and the list view try the widget's chords before its own handling; a field that does not submit commits and lets Enter bubble while it is still alive; a check box toggles on Space only. - Qt Quick: MorphWidget gains takeOwnChord, which the button, the table row and every input's control call before their own handling; the text input stops Enter only while its widget has a submit handler (`submits`). - RecordingBackend::press implements the three steps from the rule. - The mount gives setOnSubmit an empty handler for a field without onSubmit, and the interpreter gives a text input one only when it has onSubmit or commits on Enter. Six conformance cases pin the order on all three backends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
A dialog's Popup and a drawer's Drawer set no background, so the Basic style's grey palette.window showed through, and their content took the scheme of the node that declared them: a dialog declared under a sidebar drew the sidebar's light text on that grey. Both now draw as the theme's card surface (color.surface, its border, radius.card on the dialog, elevation.card) with the page padding, and OverlayImpl hands its content the card's scheme whatever surface encloses the node. The drawer sets each side's padding itself, since the style pads the edge it lies against for its own border line. Closes #1047 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
TextInputImpl::setOnSubmit wrote `submits` on every text input, and a multi-line one's MorphTextArea declares no such property, so each mount of a text area with a submit handler reported a refused write. The property is now written only where the field reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
MorphProgress used the Basic ProgressBar's own background and contentItem, which paint the style's blue whatever the theme says. The bar now draws its own: the scheme's accent on its neutral track, 4 px tall with rounded ends. Replacing the contentItem loses the style's indeterminate animation, so an indeterminate bar slides a third-wide segment across the track itself, only while the bar is shown. Closes #1050 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
MorphKeyValue capped each value at the grid's width less a caption column width that the captions wrote back from onImplicitWidthChanged, which runs during the grid's own polish. That is a binding cycle through the layout, and for some text widths Qt reports it as a polish loop, even in a widget far wider than its content. The cap and the written-back width are gone. Captions keep their whole width on one line (no wrap, a minimum of their implicit width) and values fill what is left and wrap in it, so a long value still wraps in a narrow widget. Closes #1054 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
A read-only text input, text area, select, date input or file picker drew the same input fill as an editable one, so nothing told the user the field would refuse typing. MorphInputBackground takes `readOnly`, which every input passes, and a read-only input draws on the page's background colour instead of the fill, keeping its border and focus ring since it still takes focus for selection. The contract now says how a read-only field looks. The terminal needs no change: no terminal field draws an input fill, so a read-only one is already its text in place in the normal colour, distinct from a disabled field's muted text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Three defects in how both frontends lay out a table's cells: - A badge, a check box or a button in a cell stretched across its column on Qt Quick. MorphTableRow.place now gives a cell whose fillsAcross is false its natural width at the column's start, the rule stacks already follow. - Every cell sat at the top of its row, so a text beside a taller select or check box sat above the control's centre line. A cell now keeps its own height and is centred in the row: in place() on Qt Quick, and through a table row stack's centre alignment on the terminal. - A stretch column in an overfull table was given 0 width: its header vanished and a neighbour's text drew over its cells. A content or stretch column is now never narrower than its header (on Qt Quick the floor an empty content column has, the share it gives up taken from the other stretch columns), and a fixed column is never squeezed below its header on the terminal. On Qt Quick the header and the rows sit in a horizontal Flickable that scrolls sideways only while the columns are wider than the table; the virtualized vertical flick keeps the height the document gave the table, less the header. The terminal cuts the columns past its edge, and the contract says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Nothing ever wrote {"env": "widthClass"}: the Qt Quick backend computed
a class and handed it to a callback no shell set, and it did so before
the shell and its session existed. Under morph::client the class read
null on every frontend.
The width class is now the view backend's to report and the shell's to
copy into the session:
- IViewBackend gains widthClass() and setOnWidthClass(listener). The
listener hears each later change on the owner, outside any layout
pass; registering does not call it, so the initial value has one
write path, before the first mount.
- ui::widthClassOf(units) classes a width in the vocabulary's own unit,
the one fixed(n) counts: compact below 80, medium below 160. The
terminal counts its columns (an 80-column terminal is medium), Qt
Quick its window's width over Theme.unitWidth, so a document's
breakpoints sit at the same number of units on both.
- Session::setEnvironmentValue sets one env value and keeps the others,
so the class and a switch's backend do not drop each other.
- The client shell writes the class before the first mount and on each
change. Qt Quick reports the host's width in units from MorphRoots,
which also follows a theme with another font, and the frontend sizes
the window's content item before the backend reads it. The terminal
session re-classes after dispatching a resize.
- RecordingBackend reports expanded until setWidthClass; ScreenHarness
takes the class to start with and can change it.
BackendOptions::onWidthClass, FrontendConfig::onWidthClass and
qt_quick::widthClassOf in pixels are gone.
Closes #1046
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Every control was Theme.controlHeight tall whatever the window, so a compact layout drawn for touch got desktop-sized targets, and no node or class could ask for more. A theme token, control.touchHeight (44 px by default), is the least control height while the window's width class is compact. It is a sibling of control.height, since a token's object is never read for tokens inside it. The backend sets Theme.touchTargets with the class, and Theme.targetHeight is controlHeight, or at least touchHeight while touchTargets holds: the button and input backgrounds, the text area and the table row read it, and a check box and a tab button take it as their least height while touch targets are on. The width class stands in for touch input; it is not a density layer, which stays a choice the theme index reserves. The terminal is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
ShellFrame put the title, the head slot, the principal and the sign-out button in one row. A head row of a subtitle, a few badges and two actions is wider than its share of that row, and a Qt Quick row cannot shrink a child below its minimum, so the title wrapped and the sign-out button was drawn over the head's actions, at 1280 px already. The top bar is now a column of two lines: the title, the principal and sign-out, and below them a line that holds the head slot in a horizontal scroll, so a head wider than the bar scrolls sideways instead of overlapping. The frame cannot see whether the mounted head shows anything, so the line starts hidden and the client says: ShellFrame::setHeadShown, and Workspace::activeHeadShown, a tracked read sharing one rule with the head row's own visibility. The client shell keeps an effect beside the head mount that shows the line while the active tab's head has content. A Qt Quick horizontal scroll's content is now at least its viewport's width, as on the terminal, so a head row narrower than the bar fills it and its actions sit at the trailing edge. Closes #1049 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The framework now does what the gallery's screens worked around, so the
screens use it directly:
- Width class: the screens lay out for {"env": "widthClass"} alone; the
optional width param, kWidthParam, and the param forwarded by the
index, the console and the review's navigate are gone. GalleryScreen
sets the class on its RecordingBackend through ScreenHarness, and the
render smoke resizes the real window to 60 or 180 units before each
mount and checks the class the backend reported as the screen mounted.
- forEach carries its own test id: the wrapper columns around it go.
- Dialogs and drawers close with close(self) instead of naming their
element.
- The booking's progress is the computed quotient 29 / 100, not a
literal decimal at two places.
- Design 06 draws its summary, product and review pairs with keyValue;
the containers line, which takes a tone, stays a row.
- A status tag in a table cell is a bare badge; layout::cellTag goes.
gaps.md marks G1, G5, G7, G12, G15, G19, G21, G22, G25, G34, G40, G42,
G48, G49 and G54 fixed in batch A, and batches.md marks batch A landed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Covers the batch C gaps from the gallery's gap log: spans for inline emphasis, a description and tone on every input and on dialogs, the required mark beside the caption, a checkbox group, a unit on a text input, a prominent scanner field, one application date pattern with a UTC-offset token, a destructive outline button, an outlined badge, a segmented select, stacked and toned key-value pairs, and display-only content in an item button. Plain-text rendering of document and data text on Qt Quick lands first. The monospace role belongs to batch B. Reviewed once; the review's findings and the decisions on its open questions are folded in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Covers the batch B gaps from the gallery's gap log: a table's selection, sort and page held in screen state and bound the same way a select's choice is; one selection model on every backend (click, Ctrl-click, Shift-click, keyboard, Ctrl+A) with Replace/Extend reports; a check-box column with a select-all header; in-table sorting, paging and group heading rows over in-memory rows; column alignment; the monospace text role and font token; a selectable push button for plate wells; and the sort, slice and runs expression forms. It lands in two parts: columns and the selection, then the row pipeline. Reviewed once; the review's findings and the decisions on its open questions are folded in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Sample, lot and worksheet ids are set in a fixed-pitch face in every LIMS design, and a table column's role is a text role, so the role belongs to every text rather than to a column: `mono` joins the role set and serves a `text` node and a column alike. It draws at the body size, in the text colour, in the theme's new `font.mono` family, or in the platform's fixed-pitch font while the theme names none. `font.mono` is a sibling of `font.family`, not `font.family.mono`: a token's object is never read for tokens inside it. On Qt Quick the backend writes the platform's fixed-pitch family into the theme, since QML cannot ask the font database, and a `font.mono` Qt cannot resolve is reported as `font.family`'s is. A terminal draws every cell in one fixed-pitch font, so mono draws as normal text there. A column's literal role was never checked and an unknown name drew as body text silently. The loader now checks a column's role as it checks a text's, and a computed role that names none is reported once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Covers the batch D gaps from the gallery's gap log: tab labels that follow the data and a chosen tab bound to state; surface as an expression; focus that reaches into switch cases; the pinned bottom bar idiom and a stepper helper; grid tracks of different widths; an overflow menu and a head menu; removable badges; search and filter tools in the head; a dialog that becomes a sheet on compact; navigate as a dialog that returns a result; and navigator counts with a compact shell. It proposes landing in four parts. Not reviewed yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
A column part gains `align` (start, center, end) and `valign` (start, center, end), and the table a `valign` for every column that gives none. A number column needs its digits and its header at the end, and results and review each wrapped their numbers in a `justify: end` row to get there: the same hand-rolled guarantee twice is a missing seam. The audit trail draws every cell at the top of its row, where some cells are two lines tall. `align` places the text of a text or text input cell, a label-width cell (badge, check box, push button) and the header label; a container cell fills its column and places its own content. On Qt Quick an end column also draws tabular figures. A terminal input keeps its text at the start, since `InputField` has no alignment. The view tree's `TableColumn` carries both places through `setColumns`, and RecordingBackend records a place that is not the default. A document table's first cell held the row's scope by being wrapped in a column, which its table column would then have placed instead of the cell. The first cell's node now owns the scope instead, for the same lifetime, so every column places its own cell. Layout conformance cases 8 and 9 check a badge at an end column's end and cells at their row's top and bottom, on Qt Quick and the terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
A table listed a `selection` key that the lowering never read, had no event for a user's change, and `set` could not name it, so a screen could neither clear a table's selection nor share it with another view of the same rows. The author API could not even write the key: its `selection` option was the mode. `selection` is now an expression for a list of row keys, controlled as a select's `selected` is: bound, the table shows exactly the bound keys, a user's change runs the new `onSelectionChange` with `event` the new keys, and the mount's re-assertion snaps the widget back unless the document writes the change back. Without the key the table keeps the user's selection as before and still reports it. The element values `selection` and `selectedCount` read the bound value when there is one. The loader refuses `selection` and `onSelectionChange` on a table that selects nothing or is model-backed, and its refusal of `set` on a table says to bind the selection to state instead. The author option that held the mode is renamed `selectionMode`, the key it writes, and `selection` and `onSelectionChange` join it. Closes #1053 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The backends disagreed on what a click on a row does: Qt Quick selected the row alone and toggled it on Ctrl, the terminal toggled it on a plain click, and neither had a range. A Qt Quick table could not be reached from the keyboard at all: no part of it was a Tab stop and no arrow key moved anything, so a table of text cells had no keyboard selection and Enter reached no row. Every backend now follows one model in Multiple mode: a click selects only its row and makes it the anchor, Ctrl+click toggles it, Shift+click selects the range from the anchor; Space toggles the current row, Shift+Up and Shift+Down extend a range, and Ctrl+A selects every row or clears them. A Single table selects one row for each and lets Ctrl+A go on to the ancestors' chords. Only a plain click is half of a double click, so two quick Ctrl-clicks toggle twice. A report now carries a SelectionChange, Replace or Extend, with the keys: a backend that holds one page cannot report the keys it does not hold, so only the kind says whether those stay selected. The handler of TableWidget, Table and TableOptions takes it; setExistingKeys passes the keys in row order, so a range covers rows that are not realized. On Qt Quick the table is a Tab stop that hands the focus to its current row, the TapHandler reports Ctrl and Shift, and the table's keys run while it or a row holds the focus itself, so the arrows in a result field do not move the current row. On the terminal a click reaches the table with its modifiers through clickWith, and a plain click no longer toggles. RecordingBackend gains clickRow and a Multiple table's Ctrl+A. Conformance cases 41 to 43 check the click, the range and the keys on every backend; case 43 runs the keyboard on a Qt Quick table of text cells. The recording probe's NoModifiers fault fails cases 41 and 42. Closes #1059 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Design 05 needs a column of row check boxes under a "select all on this page" box, and the gallery drew both by hand: two controls over one state, where a click beside a row's box did something else. A general control in a column header would turn the header, which both frontends draw as labels, into a container the mount fills, with its own scope and a rule for living beside a sort click; no design asks for anything but select-all there. So the table draws its own selection another way. `selectColumn`, a literal flag on a `multiple` table, gives it a leading column of check boxes: a row's box is checked while the row is selected and toggling it is a Ctrl-click on the row; the header box is checked, partly checked or clear by how many shown rows are selected, is disabled while there are none, and toggling it is Ctrl+A. Neither box changes its own state, so a selection the document refuses leaves both as the selection is. `selectAllLabel` names the header box, and `rowLabel`, read in each row's scope, names a row and its box; without it a row is named by its key. TableWidget gains setSelectColumn, setSelectAllLabel and setRowLabel, pure so no backend silently draws no boxes. Qt Quick reserves a touch-target width before the first column for CheckBoxes whose nextCheckState keeps their state; the terminal widens its gutter to five cells for `[x]`, `[ ]` and the header's `[-]`. The loader refuses `selectColumn` without a literal `multiple` and `selectAllLabel` without `selectColumn`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
The sample list draws its selection with the table's own check-box column over the `selected` state, its header box labelled "Select all on this page", in place of a hand-made column of check boxes and a box in the toolbar; the compact cards keep a check box over the same state. Results and review align their number columns, and the result field, to the end instead of wrapping each number in an end-justified row. The audit trail places every cell at the top of its row. The console's worklist marks the sample in the pane as its single selection rather than with an info tone. Sample and record ids in those tables are mono. The plate's well labels stay proportional: they are button labels, and a button takes no text role. gaps.md marks G2, G29, G30 and G47 fixed in batch B, and G43 fixed for tables, a button's modifiers staying out of ui/1; next.md records that part 1 has landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
Yaraslaut
force-pushed
the
feat/lims-gallery
branch
from
October 25, 2026 18:10
679e7b5 to
7c7f9d7
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, work in progress. This branch holds the whole initiative: the gallery screens first, then the framework batches that come out of its gap log. It ends as one PR.
What this is
examples/lims_galleryis a separate example with no backend: no models, server or database. It implements ten LIMS UI designs as ui/1 declarative screens, to find out what the declarative UI can and cannot express. Sample data lives in screen state, and actions only change that state. Each screen has a compact and an expanded layout, a recording-backend test, and a Qt Quick render smoke test at both widths. Green is the main colour (#17784A), shipped as the bundle'sui-theme/1theme.Run it with
morph_examples_client --app lims-gallery --ui=qt.To continue, start from
examples/lims_gallery/design/next.md.examples/lims_gallery/design/holds the mockups, the gap log (gaps.md, G1–G56) and the batch plan (batches.md). Each framework batch has a design underdocs/superpowers/specs/.Status
Batch A
env.widthClassis set by every view backend, in units (ui::widthClassOf). The gallery reads it directly.progressand component numbers;forEachkeeps its common properties;sizes;close: selfcloses the enclosing dialog.keyValuepolish loop;Closes #1046, #1047, #1048, #1049, #1050, #1051, #1052, #1054, #1055.
Filed during the batch designs, to be fixed here: #1058, #1059, #1060, #1061, #1062.
Verification (local, at
1e4c9cf34)morph_testsmorph_qt_quick_testsmorph_tui_testsmorph_client_testslims_gallery_testslims_gallery_render_smoke🤖 Generated with Claude Code
https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn