@flood-md/agent 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/DESIGN.md +1 -1
  2. package/dist/catalog.json +106 -50
  3. package/dist/materials/component-app-shell.md +2 -0
  4. package/dist/materials/component-collection-toolbar.md +2 -1
  5. package/dist/materials/component-data-table.md +21 -3
  6. package/dist/materials/component-editable.md +4 -0
  7. package/dist/materials/component-heading.md +1 -1
  8. package/dist/materials/component-section.md +1 -1
  9. package/dist/materials/component-sheet.md +49 -0
  10. package/dist/materials/component-sparkline.md +41 -0
  11. package/dist/materials/component-table.md +2 -0
  12. package/dist/materials/component-toolbar.md +1 -0
  13. package/dist/materials/guide-agent-knowledge.md +5 -5
  14. package/dist/materials/guide-foundations.md +2 -2
  15. package/dist/materials/guide-installation.md +6 -6
  16. package/dist/materials/guide-motion.md +7 -10
  17. package/dist/materials/recipe-collection-views.md +69 -47
  18. package/dist/materials/recipe-record-management.md +18 -4
  19. package/lib/inspect.mjs +11 -0
  20. package/manifest.json +1 -1
  21. package/package.json +1 -1
  22. package/references/component-families.md +4 -0
  23. package/references/composition.md +12 -0
  24. package/references/flood-data-display/SKILL.md +29 -0
  25. package/references/flood-feedback/SKILL.md +12 -0
  26. package/references/flood-hierarchy/SKILL.md +4 -0
  27. package/references/flood-interface-content/SKILL.md +7 -0
  28. package/references/flood-motion/SKILL.md +3 -0
  29. package/references/flood-overlays/SKILL.md +6 -0
  30. package/references/flood-records/SKILL.md +23 -0
  31. package/references/flood-spacing/SKILL.md +7 -0
  32. package/rules.json +4 -0
@@ -1,6 +1,6 @@
1
- # Installation · 0.2.11
1
+ # Installation · 0.2.12
2
2
 
3
- The commands below target version 0.2.11. Verify registry availability before installation; for local source evaluation, see the tarball instructions at the end of this guide.
3
+ The commands below target version 0.2.12. Verify registry availability before installation; for local source evaluation, see the tarball instructions at the end of this guide.
4
4
 
5
5
  Use Node.js 22.12 or newer and an existing npm-based React/Next.js project.
6
6
  React 18.3/19 is supported; React 19 and Next.js App Router are integration targets.
@@ -15,8 +15,8 @@ repeat installation when you start using another component.
15
15
  ## Connect with the CLI
16
16
 
17
17
  ```sh
18
- npx @flood-md/cli@0.2.11 init --dry-run
19
- npx @flood-md/cli@0.2.11 init
18
+ npx @flood-md/cli@0.2.12 init --dry-run
19
+ npx @flood-md/cli@0.2.12 init
20
20
  ```
21
21
 
22
22
  The installer adds exact matching tokens, React and agent packages. It creates
@@ -40,7 +40,7 @@ No Tailwind configuration or font CDN is needed. The foundations profile is dark
40
40
  only; legacy theme behavior remains available outside `FloodRoot`.
41
41
 
42
42
  ```sh
43
- npx @flood-md/cli@0.2.11 doctor
43
+ npx @flood-md/cli@0.2.12 doctor
44
44
  ```
45
45
 
46
46
  Doctor checks package versions and guide presence, not stylesheet imports or
@@ -50,7 +50,7 @@ reference `flood/DESIGN.md` from its existing instructions.
50
50
  ## Manual package installation
51
51
 
52
52
  ```sh
53
- npm install --save-exact @flood-md/tokens@0.2.11 @flood-md/react@0.2.11 @flood-md/agent@0.2.11
53
+ npm install --save-exact @flood-md/tokens@0.2.12 @flood-md/react@0.2.12 @flood-md/agent@0.2.12
54
54
  ```
55
55
 
56
56
  Import the stylesheet and use `FloodRoot` as above. Manual installation does not
@@ -1,10 +1,5 @@
1
1
  # Motion
2
2
 
3
- `PageHeader` stays static. Its actions keep their own Flood interaction feedback,
4
- and container reflow does not animate the title or move focus.
5
- `AppShell` and `AppTopBar` do not animate layout or scroll position. Sidebar
6
- and Drawer own their own transitions.
7
-
8
3
  Flood uses short motion to show where an interface changed. Motion follows the
9
4
  component's own state and respects the operating system's reduced-motion preference.
10
5
 
@@ -43,10 +38,11 @@ behavior of their children. State updates and focus take effect immediately.
43
38
  | `FloodRoot` | Applies the motion preference to its scope. Preference changes are immediate. |
44
39
  | `Text`, `Heading`, `Kbd`, `VisuallyHidden`, `Blockquote`, `InlineCode`, `IconSlot`, `Label` | Static text and artwork. The containing control owns feedback. |
45
40
  | `Stack`, `Inline`, `Surface`, `Divider`, `Container`, `Grid`, `AspectRatio`, `Section`, `Split`, `Center`, `Fieldset` | Stable layout. Children own any appearance or interaction feedback. |
41
+ | `PageHeader`, `AppShell`, `AppTopBar` | Titles, layout and scroll position stay stable during reflow. Actions, Sidebar and Drawer own their interaction feedback. |
46
42
  | `ScrollArea`, `ResizablePanels`, `Slider` | Direct pointer tracking. Only scrollbar hover paint eases. Resize, scroll and slider positions stay under the pointer. |
47
43
  | `Button`, `IconButton`, `Toggle`, `CopyButton` | Hover paint and short press compression. Toggle holds selected paint. Copy shows its result glyph without resizing the button. |
48
44
  | `ButtonGroup`, `ToggleGroup`, `Toolbar`, `OverflowActions`, `BulkActionBar`, `Menubar` | Actions inherit Button and Menu feedback. Joined button surfaces keep their geometry. Bulk actions fade in when available. |
49
- | `CommandList`, `ListBox`, `Tree`, `NavigationItem`, `NavigationList`, `NavigationTree`, `NavigationGroup`, `Sidebar` | Row paint, disclosure glyphs and optional flyouts. NavigationTree fades in branch content in place and removes it immediately on collapse. Sidebar eases between expanded and rail widths. Focus, route and tree reflow update immediately. |
45
+ | `CommandList`, `ListBox`, `Tree`, `NavigationItem`, `NavigationList`, `NavigationTree`, `NavigationGroup`, `Sidebar` | Row paint, disclosure glyphs and optional flyouts. NavigationTree fades in branch content in place and removes it immediately on collapse. Sidebar eases between expanded and rail widths and truncates its row text to keep it on one line. Its toggle shows a filled keyboard focus state, with the system outline in forced colors. Focus, route and tree reflow update immediately. |
50
46
  | `Link`, `Breadcrumb`, `Pagination`, `SkipLink`, `Steps` | Navigation is immediate. Button-style actions and step rows can ease their paint. Skip links appear immediately on focus. |
51
47
  | `Tabs`, `SegmentedControl` | Selected paint and, for line tabs, an arriving indicator. Tabs reveal the visible panel with a short fade while selection and focus update immediately. |
52
48
  | `Field`, `Input`, `Textarea`, `InputGroup`, `SearchInput`, `PasswordInput`, `NumberInput`, `PinInput`, `DateField`, `TimeField`, `DateRangeField`, `Editable`, `CharacterCount` | Field hover paint and inherited action feedback. Typing, caret, counts, validation and edit-mode focus are immediate. |
@@ -57,13 +53,14 @@ behavior of their children. State updates and focus take effect immediately.
57
53
  | `Collapsible`, `Accordion` | Keep the header flush on press, rotate the centered disclosure mark and reveal panel content. Closing immediately hides the panel and restores focus where needed. |
58
54
  | `CodeSnippet`, `Card`, `DataList`, `DataListItem`, `FileItem`, `FileList`, `AvatarGroup` | Embedded actions and avatars own motion. Reading order and record layout remain stable. |
59
55
  | `Badge`, `DescriptionList`, `Table`, `Image`, `List`, `Figure`, `Metric`, `Timeline`, `ColorSwatch`, `FileMeta` | Static content. Changes to data, labels and supporting media render immediately. |
56
+ | `Sparkline` | Hover briefly strengthens the latest point and area. Data changes render immediately. |
60
57
  | `Avatar` | Loaded image eases in within a fixed frame. Fallback text is immediately available. |
61
- | `DataTable` | Selected row paint and sort chevron rotation. Sorting, pagination and table geometry update immediately. |
62
- | `Tooltip`, `Popover`, `Menu`, `ContextMenu`, `HoverCard`, `CommandPalette`, `Dialog`, `AlertDialog`, `Drawer`, `ImagePreview` | Anchored entrances follow placement. Modal and edge panels have their own timing. Controls, focus, close and positioning retain their native behavior. |
58
+ | `DataTable` | Selected row paint, chevron rotation and a short height transition for row details. Sorting, pagination and column resize update geometry immediately. |
59
+ | `Tooltip`, `Popover`, `Menu`, `ContextMenu`, `HoverCard`, `CommandPalette`, `Dialog`, `AlertDialog`, `Drawer`, `Sheet`, `ImagePreview` | Anchored entrances follow placement. Modal and edge panels have their own timing. Sheet uses the bottom Drawer entrance. Controls, focus, close and positioning retain their native behavior. |
60
+ | `Spinner`, `Skeleton`, `Progress`, `Meter`, `ToastRegion`, `UploadItem`, `UploadList` | Busy indicators and value fill. A floating toast enters as a card from its placement edge; an inline toast fades in place. Upload compositions inherit the progress and action recipes. Announcements and final values are immediate. |
61
+ | `Alert`, `EmptyState`, `StatusIndicator`, `ErrorSummary` | State and message changes are immediate. Embedded actions keep Button feedback. |
63
62
 
64
63
  Menu, ContextMenu and title-only navigation flyouts fade in and out where
65
64
  discrete top-layer transitions are supported. Dismissal releases focus and
66
65
  pointer interaction immediately. When a sibling navigation flyout takes over
67
66
  the same rail position, the outgoing panel yields immediately.
68
- | `Spinner`, `Skeleton`, `Progress`, `Meter`, `ToastRegion`, `UploadItem`, `UploadList` | Busy indicators, value fill and floating toast arrivals. Upload compositions inherit the progress and action recipes. Announcements and final values are immediate. |
69
- | `Alert`, `EmptyState`, `StatusIndicator`, `ErrorSummary` | State and message changes are immediate. Embedded actions keep Button feedback. |
@@ -61,7 +61,9 @@ checkboxes with no resulting action.
61
61
 
62
62
  Selecting projects reveals one shared action bar floating at the viewport bottom. It shows the
63
63
  selected count, changes status, confirms archiving and clears the
64
- selection. Selection spans the filtered client collection across its pages.
64
+ selection. DataTable uses `showSelection={false}` to keep its result range and
65
+ pagination while announcing the selected count without displaying it again.
66
+ Selection spans the filtered client collection across its pages.
65
67
  Changing the query resets it. Archiving captures IDs and names before opening
66
68
  the confirmation. Cancel keeps the selection and records intact.
67
69
 
@@ -70,6 +72,10 @@ records after the optional onUpdateStatus callback accepts the whole operation.
70
72
  Reload returns to the declared fixtures. A rejection keeps accepted records and
71
73
  selection intact. Retry uses the failed operation's captured IDs and status.
72
74
  Dismiss or a deliberate query/selection change abandons its feedback.
75
+ Report results in a floating ToastRegion so the table stays in place.
76
+ Use the default bottom-end placement. Success expires automatically. A failed
77
+ update offers a persistent Retry action. While the selection rail is visible,
78
+ raise the toast above it without changing the table's scroll clearance.
73
79
 
74
80
  Only one update can run at a time. Pending actions are disabled. If the user
75
81
  changes the query or selection while an update is pending, its result still
@@ -113,7 +119,7 @@ export function ProjectSelectionActions({ projects, pending, onUpdate, onClear }
113
119
  <VisuallyHidden role="status">{pending ? 'Updating…' : ''}</VisuallyHidden>
114
120
  {archive && <AlertDialog
115
121
  title={`Archive ${archive.length} ${archive.length === 1 ? 'project' : 'projects'}?`}
116
- description={`${archive.map(project => project.name).join(', ')} will move to Archived.`}
122
+ description={`${archive.map(project => project.name).join(', ')}\n${archive.length === 1 ? 'It' : 'They'} will move to Archived.`}
117
123
  confirmLabel="Archive projects"
118
124
  cancelLabel="Keep projects"
119
125
  open
@@ -129,29 +135,52 @@ export function ProjectSelectionActions({ projects, pending, onUpdate, onClear }
129
135
 
130
136
  The panel uses the existing floating-surface tokens and a viewport-safe inset.
131
137
  It leaves normal document flow and stays centered when the document scrolls.
138
+ Check the table header position and scroll dimensions with zero, one and
139
+ several selected records. A collection that fits should keep fitting on selection.
132
140
  BulkActionBar keeps a single pill-shaped row with at most two visible commands
133
141
  by default. Lower-priority commands move into More as space shrinks; the host
134
- does not add a second row of commands. ResizeObserver measures its actual height, and the
135
- collection reserves enough bottom space for the final rows and pagination to
136
- scroll above the panel. Keep this measured clearance with the host styles.
142
+ keeps the commands on one row. Budget bottom clearance in the base layout
143
+ using the control height, the panel chrome and the viewport inset. At the end
144
+ of a scroll, the final rows and pagination should be reachable above the panel.
145
+ Keep the scroll area inside the bordered Surface and clip the frame at its
146
+ rounded corners so the table footer cannot cover the outline.
137
147
 
138
148
  ```css
139
149
  .collection-examples { padding-block:var(--flood-sys-space-8); min-inline-size:0; }
140
150
  .collection-view-controls { min-inline-size:0; }
141
151
  .flood-root .collection-filter-editor { inline-size:min(42rem,calc(100vw - 2rem)); }
142
152
 
153
+ .collection-projects {
154
+ --collection-selection-inset: max(var(--flood-sys-space-4), env(safe-area-inset-bottom));
155
+ --collection-selection-control-height: var(--flood-sys-control-sm-height);
156
+ --collection-selection-height: calc(var(--collection-selection-control-height) + 2 * (var(--flood-sys-space-1) + var(--flood-sys-stroke-width)));
157
+ padding-block-end: calc(var(--collection-selection-inset) + var(--collection-selection-height) + var(--flood-sys-space-2));
158
+ }
159
+
160
+ @media (pointer: coarse) {
161
+ .collection-projects {
162
+ --collection-selection-control-height: max(var(--flood-sys-control-sm-height), var(--flood-sys-size-touch));
163
+ }
164
+ }
165
+
166
+ .collection-table-frame {
167
+ border: var(--flood-sys-stroke-width) solid var(--flood-sys-color-border-decorative);
168
+ overflow: clip;
169
+ }
170
+
143
171
  .collection-selection-panel {
144
172
  position: fixed;
145
173
  inset-inline: 0;
146
- inset-block-end: max(var(--flood-sys-space-4), env(safe-area-inset-bottom));
174
+ inset-block-end: var(--collection-selection-inset);
147
175
  margin-inline: auto;
148
176
  inline-size: min(26rem, calc(100% - 2 * var(--flood-sys-space-4)));
149
177
  max-inline-size: calc(100% - 2 * var(--flood-sys-space-4));
150
178
  z-index: var(--flood-sys-layer-sticky);
151
179
  }
152
180
 
153
- .collection-projects[data-selected="true"] {
154
- padding-block-end: calc(var(--collection-selection-height, 0px) + 2 * var(--flood-sys-space-4) + env(safe-area-inset-bottom));
181
+ .flood-root .collection-selection-panel + .flood-toast-region {
182
+ inset-block-end: calc(var(--collection-selection-inset) + var(--collection-selection-height) + var(--flood-sys-space-2));
183
+ max-block-size: calc(100dvh - var(--collection-selection-inset) - var(--collection-selection-height) - var(--flood-sys-space-6));
155
184
  }
156
185
  ```
157
186
 
@@ -210,6 +239,9 @@ defines reset scope and supplies canClear for any state held by quickFilters.
210
239
  At most two task-important quick properties remain inline; additional properties
211
240
  belong inside Filters. Summaries outside the panel name the property and preserve
212
241
  any-match meaning. Container width, rather than viewport size, governs reflow.
242
+ The Projects table places its Columns control in CollectionToolbar's `actions`
243
+ slot via DataTable's `toolbar` render function. It shares the tool row with
244
+ search and filters, and the commands wrap as a group in a narrow container.
213
245
 
214
246
  CollectionViewSwitcher owns the separate accepted-view choice and adaptive
215
247
  commands. It reuses Field/Select and Button/OverflowActions with the same size.
@@ -222,7 +254,7 @@ SearchInput, Select, Menu, MultiSelect and SegmentedControl own input/choice beh
222
254
  Popover contains the condition draft and Tag removes applied conditions. Button
223
255
  and IconButton perform actions. Several view commands use OverflowActions; a
224
256
  single command uses Button directly. Field/Fieldset, Grid/Stack/Inline, Dialog,
225
- AlertDialog and Alert retain their own roles. DataTable/StatusIndicator and
257
+ AlertDialog and ToastRegion retain their own roles. DataTable/StatusIndicator and
226
258
  FileList/FileItem/FileMeta demonstrate two result structures. No owner is inserted
227
259
  merely to increase its usage count, and no new package atom is needed for layout.
228
260
 
@@ -293,7 +325,7 @@ export function readCollectionViews(key:string,schema:CollectionSchema,storage:V
293
325
 
294
326
  ```tsx
295
327
  "use client";
296
- import {useEffect,useRef,useState,type FormEvent} from 'react';
328
+ import {useEffect,useRef,useState,type FormEvent,type ReactNode} from 'react';
297
329
  import {Alert,AlertDialog,Button,CollectionToolbar,CollectionViewSwitcher,Dialog,Field,FilterGroup,IconSlot,Inline,Input,Menu,Select,Stack,Tag,type OverflowAction} from '@flood-md/react';
298
330
  import {collectionQueryKey,copyCollectionQuery,parseCollectionQuery,readCollectionViews,type CollectionQuery,type CollectionSchema,type CollectionView,type FilterProperty,type ViewRead,type ViewStorage} from './collection-views-model';
299
331
 
@@ -340,13 +372,13 @@ export function AppliedFilterControl({ property, value, onChange }: {
340
372
 
341
373
  interface CollectionViewControlsProps {
342
374
  label:string;schema:CollectionSchema;presets:readonly CollectionView[];
343
- value:CollectionQuery;onValueChange:(query:CollectionQuery)=>void;storageKey:string;storage?:ViewStorage;
375
+ value:CollectionQuery;onValueChange:(query:CollectionQuery)=>void;storageKey:string;storage?:ViewStorage;toolbarActions?:ReactNode;
344
376
  }
345
377
  type NameDraft={kind:'create'|'rename';id?:string;name:string;snapshot:CollectionQuery;error?:string;invalid?:string};
346
378
  const inlineFilterLimit = 2;
347
379
 
348
380
  /** Host recipe: shared behaviour, not a second Flood component implementation. */
349
- export function CollectionViewControls({label,schema,presets,value,onValueChange,storageKey,storage}:CollectionViewControlsProps){
381
+ export function CollectionViewControls({label,schema,presets,value,onValueChange,storageKey,storage,toolbarActions}:CollectionViewControlsProps){
350
382
  const [saved,setSaved]=useState<ViewRead|{state:'loading';views:[]}>({state:'loading',views:[]}),[selectedId,setSelectedId]=useState(presets[0].id);
351
383
  const [editing,setEditing]=useState(false),[draft,setDraft]=useState(()=>copyCollectionQuery(value)),[draftError,setDraftError]=useState('');
352
384
  const [advancedEditor, setAdvancedEditor] = useState(false);
@@ -435,6 +467,7 @@ export function CollectionViewControls({label,schema,presets,value,onValueChange
435
467
  {saved.state==='error'&&<Stack gap="3"><Alert tone="error">Saved views could not be read.</Alert><Inline gap="3"><Button size="sm" variant="subtle" onClick={()=>setReadAttempt(attempt=>attempt+1)}>Retry saved views</Button>{saved.raw!==null&&<Button size="sm" variant="subtle" onClick={exportSnapshot}>Export snapshot</Button>}<Button size="sm" variant="subtle" tone="danger" onClick={()=>{viewInput.current?.focus({preventScroll:true});setConfirmation({kind:'replace'});}}>Replace saved views</Button></Inline></Stack>}
436
468
  {operationError&&<Alert tone="error" announcement="polite"><Inline gap="3">{operationError.message}<Button size="sm" variant="ghost" onClick={()=>setOperationError(null)}>Dismiss</Button></Inline></Alert>}
437
469
  <CollectionToolbar label={label+' collection toolbar'} searchValue={value.search}
470
+ actions={toolbarActions}
438
471
  onSearchValueChange={search=>onValueChange({...value,search})}
439
472
  searchLabel={'Search '+label.toLowerCase()} searchPlaceholder={'Search '+label.toLowerCase()+'…'} clearSearchLabel={'Clear '+label.toLowerCase()+' search'}
440
473
  appliedFiltersLabel={label+' applied filters'}
@@ -496,8 +529,8 @@ chosen consumer with actual data and their own chrome.
496
529
 
497
530
  ```tsx
498
531
  "use client";
499
- import {useEffect,useRef,useState} from 'react';
500
- import {Alert,Button,DataTable,EmptyState,Field,FileItem,FileList,FloodRoot,Heading,Inline,Select,Stack,StatusIndicator,Tabs} from '@flood-md/react';
532
+ import {useRef,useState} from 'react';
533
+ import {Button,DataTable,EmptyState,Field,FileItem,FileList,FloodRoot,Heading,Select,Stack,StatusIndicator,Surface,Tabs,ToastRegion,type ToastItem} from '@flood-md/react';
501
534
  import {SiteHeader} from './site-header';
502
535
  import {SiteFooter} from './site-footer';
503
536
  import {CollectionViewControls} from './recipes/collection-views';
@@ -532,10 +565,8 @@ export function ProjectCollection({storage,onUpdateStatus}: {
532
565
  const [page, setPage] = useState(1);
533
566
  const [selection, setSelection] = useState<readonly string[]>([]);
534
567
  const [pending, setPending] = useState(false);
535
- const [feedback, setFeedback] = useState<
536
- { kind: 'success'; message: string } |
537
- { kind: 'error'; update: ProjectStatusUpdate; message: string } | null
538
- >(null);
568
+ const [feedback, setFeedback] = useState<ToastItem | null>(null);
569
+ const feedbackSequence = useRef(0);
539
570
  const inFlight = useRef(false);
540
571
  const collection = useRef<HTMLElement>(null);
541
572
  const selectionPanel = useRef<HTMLDivElement>(null);
@@ -549,20 +580,6 @@ export function ProjectCollection({storage,onUpdateStatus}: {
549
580
  const selectedProjects=rows.filter(project=>selection.includes(project.id));
550
581
  const hasSelection = selectedProjects.length > 0;
551
582
 
552
- useEffect(() => {
553
- const panel = selectionPanel.current;
554
- const host = collection.current;
555
- if (!hasSelection || !panel || !host) return;
556
- const measure = () => host.style.setProperty('--collection-selection-height', `${panel.getBoundingClientRect().height}px`);
557
- measure();
558
- const observer = new ResizeObserver(measure);
559
- observer.observe(panel);
560
- return () => {
561
- observer.disconnect();
562
- host.style.removeProperty('--collection-selection-height');
563
- };
564
- }, [hasSelection]);
565
-
566
583
  async function updateStatus(proposal: ProjectStatusUpdate) {
567
584
  if (inFlight.current) return;
568
585
  const update = { ids: [...new Set(proposal.ids)], status: proposal.status };
@@ -580,33 +597,38 @@ export function ProjectCollection({storage,onUpdateStatus}: {
580
597
  setSelection(current => current.filter(id => !ids.has(id)));
581
598
  setPage(1);
582
599
  const result = update.status === 'active' ? 'set active' : update.status;
583
- setFeedback({ kind: 'success', message: `${ids.size} ${ids.size === 1 ? 'project' : 'projects'} ${result}.` });
600
+ setFeedback({ id: String(++feedbackSequence.current), status: 'success', title: `${ids.size} ${ids.size === 1 ? 'project' : 'projects'} ${result}.` });
584
601
  } catch {
585
- setFeedback({ kind: 'error', update, message: `Couldn’t update ${update.ids.length} ${update.ids.length === 1 ? 'project' : 'projects'}.` });
602
+ setFeedback({
603
+ id: String(++feedbackSequence.current),
604
+ status: 'error',
605
+ title: `Couldn’t update ${update.ids.length} ${update.ids.length === 1 ? 'project' : 'projects'}.`,
606
+ action: { label: 'Retry update', onClick: () => {
607
+ collection.current?.focus({ preventScroll: true });
608
+ void updateStatus(update);
609
+ }},
610
+ });
586
611
  } finally {
587
612
  inFlight.current = false;
588
613
  setPending(false);
589
614
  }
590
615
  }
591
616
 
592
- return <section ref={collection} tabIndex={-1} className="collection-projects" data-selected={hasSelection || undefined} aria-label="Projects collection"><Stack gap="6">
593
- <CollectionViewControls label="Projects" schema={projectSchema} presets={projectPresets} value={query} onValueChange={change} storageKey="flood-project-views-v1" storage={storage}/>
617
+ return <section ref={collection} tabIndex={-1} className="collection-projects" aria-label="Projects collection"><Stack gap="6">
594
618
  {hasSelection&&<div ref={selectionPanel} className="collection-selection-panel"><ProjectSelectionActions projects={selectedProjects} pending={pending} onUpdate={update=>{void updateStatus(update);}} onClear={()=>{
595
619
  collection.current?.focus({preventScroll:true});setSelection([]);setFeedback(null);
596
620
  }}/></div>}
597
- {feedback&&<Alert tone={feedback.kind==='error'?'error':'success'} announcement="polite"><Inline gap="3">
598
- {feedback.message}
599
- {feedback.kind==='error'&&<Button size="sm" variant="subtle" disabled={pending} onClick={()=>{
600
- collection.current?.focus({preventScroll:true});void updateStatus(feedback.update);
601
- }}>Retry update</Button>}
602
- <Button size="sm" variant="ghost" disabled={pending} onClick={()=>{
603
- collection.current?.focus({preventScroll:true});setFeedback(null);
604
- }}>Dismiss</Button>
605
- </Inline></Alert>}
606
- <DataTable caption="Projects" hideCaption minWidth="36rem" selectable pageSize={5} page={page} onPageChange={setPage} selectedIds={selection} onSelectionChange={ids=>{setSelection(ids);setFeedback(null);}} sort={query.sort} onSortChange={sort=>change({...query,sort})}
621
+ <ToastRegion label="Project updates" items={feedback ? [feedback] : []}
622
+ onDismiss={id=>setFeedback(current=>current?.id===id?null:current)}/>
623
+ <Surface padding="0" className="collection-table-frame">
624
+ <DataTable caption="Projects" hideCaption minWidth="36rem" selectable showSelection={false} pageSize={5} page={page} onPageChange={setPage} selectedIds={selection} onSelectionChange={ids=>{setSelection(ids);setFeedback(null);}} sort={query.sort} onSortChange={sort=>change({...query,sort})}
625
+ inset="roomy" showColumnVisibility
626
+ toolbar={({columns}) => <CollectionViewControls label="Projects" schema={projectSchema} presets={projectPresets}
627
+ value={query} onValueChange={change} storageKey="flood-project-views-v1" storage={storage} toolbarActions={columns}/>}
607
628
  emptyContent={<EmptyState title="No matching projects" actions={<Button variant="subtle" onClick={()=>{change({...query,search:'',conditions:[]});collection.current?.querySelector<HTMLInputElement>('.flood-collection-toolbar input')?.focus({preventScroll:true});}}>Reset filters</Button>}/>}
608
- columns={[{id:'name',label:'Project',sortable:true},{id:'owner',label:'Owner',sortable:true},{id:'status',label:'Status',sortable:true},{id:'priority',label:'Priority',sortable:true}]}
629
+ columns={[{id:'name',label:'Project',sortable:true,hideable:false},{id:'owner',label:'Owner',sortable:true},{id:'status',label:'Status',sortable:true},{id:'priority',label:'Priority',sortable:true}]}
609
630
  rows={rows.map(project=>({id:project.id,label:project.name,cells:{name:project.name,owner:owners.find(owner=>owner.value===project.owner)!.label,status:<StatusIndicator label={projectSchema.properties[0].options.find(option=>option.value===project.status)!.label} tone={project.status==='active'?'success':project.status==='paused'?'warning':'neutral'}/>,priority:project.priority==='urgent'?'Urgent':'Normal'},sortValues:{name:project.name,owner:project.owner,status:project.status,priority:project.priority==='urgent'?1:0}}))}/>
631
+ </Surface>
610
632
  </Stack></section>;
611
633
  }
612
634
 
@@ -36,11 +36,11 @@ and filters that share the table surface.
36
36
  },
37
37
  }]} />
38
38
  )}>
39
- <Stack gap="6">
40
- <Section title="Customer message">
39
+ <Stack gap="8">
40
+ <Section title="Customer message" headingLevel={3} headingSize="group">
41
41
  <Text as="p">Confirm who should manage member permissions.</Text>
42
42
  </Section>
43
- <Section title="Assignment">
43
+ <Section title="Assignment" headingLevel={3} headingSize="group">
44
44
  <Field label="Owner">{field => <Input {...field} />}</Field>
45
45
  </Section>
46
46
  </Stack>
@@ -51,6 +51,20 @@ Use `size="lg"` when long messages and several field groups need the wider
51
51
  reading area. Compose the body from Section for separately named content,
52
52
  DescriptionList for facts and Fieldset for related controls. Check wrapping and
53
53
  scrolling with the longest title and a real footer action before selecting a size.
54
+ Use the group heading size inside a detail Drawer to keep the record name prominent.
55
+
56
+ When the detail contains line items and a total, use the Table footer to mark
57
+ the summary and keep its amount aligned with the line-item amounts. A row header
58
+ labels the total, and the Table footer supplies the separator and emphasis.
59
+
60
+ ```tsx
61
+ <tfoot>
62
+ <tr>
63
+ <th scope="row" colSpan={2}>Total</th>
64
+ <td data-numeric>{formattedTotal}</td>
65
+ </tr>
66
+ </tfoot>
67
+ ```
54
68
 
55
69
  ## Keep browsing state and edit state separate
56
70
 
@@ -235,7 +249,7 @@ export function RecordManagement() {
235
249
  <Surface elevation="raised" padding="4">
236
250
  <ScrollArea ref={list} role="region" aria-label="Request results" style={{maxBlockSize: "22rem"}}>
237
251
  {results.length > 0
238
- ? <DataList aria-label="Requests">
252
+ ? <DataList aria-label="Requests" className="request-results">
239
253
  {results.map(record => <DataListItem
240
254
  key={record.id}
241
255
  title={record.title}
package/lib/inspect.mjs CHANGED
@@ -51,6 +51,17 @@ export function inspectRenderedUI(options = {}) {
51
51
  return matches.slice(0, limit);
52
52
  };
53
53
  if(root===document&&document.documentElement.scrollWidth>innerWidth+1)addIssue({id:'layout.page-overflow',target:{tag:'html',id:document.documentElement.id||null,className:document.documentElement.getAttribute('class')||'',label:'Document'},offset:document.documentElement.scrollWidth-innerWidth,message:'The document extends beyond the viewport. Review the actual overflow owner at this text size; dense content can scroll locally. Hiding page overflow does not repair the layout.'});
54
+ const documentGutter = innerWidth - document.documentElement.clientWidth;
55
+ const scrollOwner = document.scrollingElement;
56
+ if (documentGutter > 1 && scrollOwner && scrollOwner.scrollHeight > scrollOwner.clientHeight + 1) {
57
+ const table = select('.flood-data-table').find(visible);
58
+ if (table) addIssue({
59
+ id: 'layout.record-scroll-owner',
60
+ target: target(table),
61
+ offset: documentGutter,
62
+ message: 'A document scrollbar consumes width on this record view. Compare full and filtered views with classic scrollbars. For a full-window workspace, use AppShell content scrolling so changes in row count keep the table edges in place.',
63
+ });
64
+ }
54
65
  for (const button of select('button.flood-button, button.flood-icon-button')) {
55
66
  // Text is inspected outside real icon slots, SVGs and visually hidden names.
56
67
  const walker = document.createTreeWalker(button, NodeFilter.SHOW_TEXT);
package/manifest.json CHANGED
@@ -158,7 +158,7 @@
158
158
  "kind": "reference"
159
159
  }
160
160
  ],
161
- "version": "0.2.11",
161
+ "version": "0.2.12",
162
162
  "installedEntry": ".agents/skills/flood-ui/SKILL.md",
163
163
  "pathBase": "source is package-relative; target is relative to the host project root"
164
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flood-md/agent",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "description": "Versioned Flood design instructions for coding agents.",
6
6
  "license": "MIT",
@@ -89,6 +89,10 @@ override leaves fields and composed controls inconsistent. Keep absolute
89
89
  material colors separate from contextual control roles and verify the nearest
90
90
  nested owner resets those roles. Preserve accent focus and destructive meaning.
91
91
 
92
+ Vertical pill Toolbar keeps one column on a shared center axis. Use named
93
+ icon-only actions and a consistent control size for a compact rail. Use a
94
+ rounded vertical Toolbar with consistently labeled controls when text is needed.
95
+
92
96
  BulkActionBar is a one-row capsule for a small set of selection commands. Keep
93
97
  one to three visible commands, prefer two, and disclose the remainder through
94
98
  its overflow owner. Do not wrap this floating rail into a second row or use it
@@ -43,6 +43,18 @@ adapter such as @/ui/button may re-export Flood, but does not change React APIs.
43
43
  Before handing off, inspect a real composed screen at narrow and wide widths,
44
44
  not only isolated controls. See [verification](verification.md).
45
45
 
46
+ Choose the scroll owner when laying out a screen. In an application workspace
47
+ with fixed chrome or floating collection actions, use AppShell with
48
+ `scroll="content"`. For a padded workspace, set `scrollContentInset="none"`
49
+ when that prop is available and the existing edge padding clears the rails.
50
+ Use the document as scroll owner for pages designed to flow as one document.
51
+ Apply the workspace recipe to filterable or pageable record queues with floating
52
+ actions even when they have no persistent top bar. Keep viewport-fixed actions
53
+ outside the scrolling content. Table's horizontal ScrollArea handles wide
54
+ columns independently.
55
+ When testing a floating selection bar, select the first row and check that the
56
+ viewport and the table keep the same inline size as the overlay rail appears.
57
+
46
58
  For constrained overflow, use installed ScrollArea when available. Set height or
47
59
  maxHeight on its viewport, choose the needed axis, and provide a useful accessible
48
60
  name. Rails overlay content with insets; keep essential content out of the rail
@@ -60,6 +60,35 @@ the interface-content workflow for contextual placement.
60
60
  Keep identifiers, such as postal codes and account numbers, aligned as text
61
61
  even when they contain only digits. Preserve formatted units and use raw sort
62
62
  values when localized text or rich cells would sort differently.
63
+ - Offer column visibility when users need different subsets of fields for real
64
+ tasks in the same table. Leave it off for compact tables with a fixed task.
65
+ Use one layout control per table. If the screen already has View options or
66
+ Display settings, put column visibility there and pass `hiddenColumnIds`.
67
+ Otherwise enable `showColumnVisibility`. If the table already has search and
68
+ filters, use the `toolbar` render function and put its Columns control in
69
+ `CollectionToolbar.actions` to share the existing tool row. Keep the record
70
+ identity visible with `hideable: false` and save preferences by column ID in
71
+ the host. Keep layout preferences independent of the data query. Verify that
72
+ visibility changes preserve page, selection and sort.
73
+ - Enable resizing for columns with variable-length content when readers need
74
+ control over width. Use logical start pinning for record identity and logical
75
+ end for recurring actions when horizontal scrolling would hide them. Persist
76
+ widths by column ID with visibility, and verify keyboard resize, reset, RTL
77
+ and access to the unpinned middle at the smallest intended width.
78
+ - Choose short one-line column labels and use DataTable's `unit` badge for
79
+ a shared short unit. Preserve the complete label when a narrow column
80
+ truncates it. Compare the label, sort icon and resize grip on the same row.
81
+ - Use DataTable's row disclosure for short details the reader needs while
82
+ scanning parent records. Compose a named nested Table for related line items.
83
+ Keep the disclosure and any record-opening action independent. Use a Drawer
84
+ or separate page for longer detail or editing tasks.
85
+ - Use Sparkline when the shape of a series helps a comparison. Supply a label
86
+ that describes the trend, use a shared scale for comparing magnitudes, and
87
+ show decision-critical numbers as text. If a rich trend cell is sortable,
88
+ define the metric it sorts by and supply its raw value.
89
+ Keep the same tone across rows of one metric. A soft area fill supports the
90
+ stroke and follows the same gaps. Reserve semantic tones for meaning and
91
+ keep numbers and labels readable independently of color.
63
92
  - DescriptionList expresses named values; DataList/DataListItem express records.
64
93
  Card/Section/Surface organize groups without inventing a second typography or
65
94
  spacing system. A heading's semantic level does not prescribe its visual size.
@@ -35,6 +35,14 @@ visible even when reducing decorative emphasis.
35
35
 
36
36
  ## Scope feedback to its operation
37
37
 
38
+ Use a floating ToastRegion for the result of a discrete action in a collection
39
+ that is still usable. Keep a failed action's toast available while its retry
40
+ is offered, and preserve the operation's captured targets. Start at bottom-end
41
+ and keep the toast clear of selection actions. Use an inline Alert when the message belongs to
42
+ the affected section, for example when a read failure blocks the table itself.
43
+ Keep its action on the same visual axis as the message in a short titleless
44
+ row. Use Flood Button variants so hover and press inherit the Alert tone.
45
+
38
46
  Associate a message with the operation and record that produced it. Preserve
39
47
  entered data after refusal and keep a useful error while that operation remains
40
48
  unresolved. A successful retry clears/replaces its failure. Explicitly discarding
@@ -48,6 +56,10 @@ that distinction; the feedback primitive only presents the supplied state.
48
56
 
49
57
  ## Evidence
50
58
 
59
+ After a collection action, check that its result is visible at the current
60
+ scroll position and that success, refusal, retry and dismissal leave the
61
+ table header and scroll height in place.
62
+
51
63
  Trigger the operation and observe each applicable transition, including failure
52
64
  and retry. Also follow failure → explicit discard → list → reopen a saved record;
53
65
  the abandoned draft's failure must be gone and the saved data unchanged. Distinguish
@@ -21,6 +21,10 @@ this workflow makes that meaning visible.
21
21
 
22
22
  - Dialog owns its task heading and the body inset. Its dark title uses the
23
23
  dialog-title role (1.25rem / 600); it is distinct from compact popup captions.
24
+ - For independent blocks inside a record Drawer, use Section with
25
+ `headingSize="group"` when installed. Keep the Drawer title one level stronger
26
+ visually. Set the semantic heading level separately. Start with a tighter gap
27
+ from each heading to its content and space-8 between independent sections.
24
28
  - Field owns the label-to-control relationship: space-1 (.25rem) in the dark
25
29
  profile. DateField/TimeField already use that relationship. Leave room between
26
30
  independent fields instead of increasing the gap inside every label/control pair.
@@ -38,6 +38,13 @@ Do not hide essential instructions or risks behind a tooltip merely
38
38
  to make a screen quieter. Preserve accessible names and associations when reducing
39
39
  visible copy; use the component's documented hideLabel API where appropriate.
40
40
 
41
+ For a bulk confirmation, put the action and its scope first. Use the title for
42
+ the count when it reads naturally there. If the count belongs in the body, give
43
+ it the first sentence. Put a meaningful consequence on a separate line or in a
44
+ separate paragraph. Let each thought wrap naturally on narrow screens. Preserve
45
+ the dialog's accessible description and check singular and plural copy.
46
+ AlertDialog preserves a newline in its string description.
47
+
41
48
  ## Separate prototype provenance from product state
42
49
 
43
50
  An explicitly requested local prototype or fixture-backed test app does not need
@@ -22,6 +22,9 @@ opacity and transform on bounded visual parts. Keep an anchored overlay's
22
22
  positioning root stable while a child moves. Animate color only where the old and
23
23
  new colors keep required information legible. Keep focus outlines and error
24
24
  messages immediate. Apply hover treatment only to devices that can hover.
25
+ For ToastRegion, animate each arriving card as a unit from its placement edge.
26
+ Keep the region anchored and its announcement available as soon as it appears.
27
+ Inline toasts can fade without moving surrounding content.
25
28
  Keep compression off disabled controls, text editors and joined control
26
29
  surfaces. Verify release and cancellation without a layout shift.
27
30
  Treat disclosure headers as part of their enclosing surface. Keep their
@@ -56,6 +56,12 @@ DescriptionList or Fieldset as appropriate. Put the next action in the footer
56
56
  when it must stay reachable during scrolling. Avoid repeating the heading
57
57
  as a section name or showing two equivalent dismiss actions in a clean state.
58
58
 
59
+ Use Sheet when a short touch task, navigation list or option list needs the
60
+ width of the phone. Keep the current choice visible on its trigger and compose
61
+ single-choice options with ListBox. Commit on selection, keep the previous value
62
+ on dismissal, and return focus to the trigger. Use one scrolling body and keep
63
+ long content, the close action and safe-area padding reachable.
64
+
59
65
  Overlay material establishes the nested neutral control context automatically.
60
66
  Compare its fields, secondary actions, selection and supporting chips with their
61
67
  canvas counterparts. Avoid opaque dark action islands or hiding the entire
@@ -69,6 +69,29 @@ button provides the keyboard entry. Keep checkbox, links and menus independent.
69
69
  For a real destination, preserve a Link. Use the table toolbar and matching
70
70
  edge inset when the heading, filters, cells and footer share one surface.
71
71
 
72
+ For transient selection commands, look up `recipe:collection-views` in the
73
+ installed catalog. For a full-window record workspace with a changing row count,
74
+ use `AppShell scroll="content" scrollOrientation="vertical"` as the vertical
75
+ scroll owner, including when the page has no app chrome. Keep fixed actions
76
+ outside that ScrollArea. Test a populated view and a filtered or empty view in a
77
+ browser with space-reserving scrollbars. Compare the table's inline edges
78
+ and the document's client width across those views.
79
+
80
+ Place BulkActionBar in a fixed host centered near the bottom
81
+ of the viewport. Keep the host outside the table toolbar. Budget any needed
82
+ bottom clearance in the base layout so selecting rows preserves both the table
83
+ position and the scroll height. Use `showSelection={false}` when BulkActionBar displays the count.
84
+ The table still announces selection and can show its result range and pagination.
85
+ When a bordered Surface wraps a flush table, apply `overflow: clip` to that
86
+ frame. Keep horizontal scrolling on the table's own ScrollArea. Verify the
87
+ outline at both lower corners with a visible footer and check keyboard focus at
88
+ the frame's edges.
89
+
90
+ If a detail table's total summarizes its rows, put it in Table's `tfoot`. Use a
91
+ row header for the label and align the amount with the corresponding numeric
92
+ column. The existing footer border separates it from the last item. Use an
93
+ external summary when its scope includes data beyond the table.
94
+
72
95
  Give a queue's summaries only the prominence they earn in the task. If a
73
96
  count changes which work to pick up, make the relevant scope clear. If the
74
97
  table already exposes the decision, compare a quiet inline count with a
@@ -74,6 +74,13 @@ and compact states. Center an icon-only rail against the full rail, not the
74
74
  remainder after a one-sided gutter. A scrollbar appearing must not shift body
75
75
  tracks relative to the fixed header/footer. Full-viewport workspaces without
76
76
  document overflow must not reserve a document scrollbar strip.
77
+ For a floating action bar, size its host to the actual count, commands and close
78
+ control. Keep the count slot stable across digit changes and bound the panel to
79
+ the viewport. Measure the clearance below the last row with the panel open.
80
+ Compare scroll height, available content width and table position before and
81
+ after selection. Check a viewport where all rows fit and one where the last
82
+ row needs scrolling. Put required clearance in the base layout so selection
83
+ keeps the same scroll geometry.
77
84
  Test expanded/compact, missing icons, badges/disclosures, long labels, RTL and
78
85
  enlarged text. Measure rendered glyph centers, text starts and action centers
79
86
  across distant rows; include the header and footer. Correct the owning primitive
package/rules.json CHANGED
@@ -131,6 +131,7 @@
131
131
  "verify": [
132
132
  "320px width",
133
133
  "Root 24px and increased text spacing",
134
+ "Record workspace keeps table edges and document width stable across full, filtered and empty views with classic scrollbars",
134
135
  "No glyph shrink or unintended page overflow"
135
136
  ]
136
137
  },
@@ -418,6 +419,9 @@
418
419
  "verify": [
419
420
  "Caption, headers and named-value relationships preserved",
420
421
  "Row actions remain independent named controls",
422
+ "When users need column visibility, the table has one layout control in its tool row or existing view settings",
423
+ "If enabled, column resizing works by pointer and keyboard, pinned edges leave the middle reachable, and resetting layout preserves the query",
424
+ "Expanded detail remains associated with its parent record and preserves native table semantics",
421
425
  "File metadata/status/progress reuse their existing primitives",
422
426
  "Test one supplied server page without local slicing/sorting; declare and validate page/dataset selection scope."
423
427
  ]