@happyvertical/smrt-svelte 0.55.22 → 0.55.24

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 (85) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +2 -2
  3. package/agents/overview-surfaces.md +108 -4
  4. package/agents/screens.md +171 -5
  5. package/dist/components/overview/index.d.ts +4 -1
  6. package/dist/components/overview/index.d.ts.map +1 -1
  7. package/dist/components/overview/index.js +3 -0
  8. package/dist/components/overview/record.d.ts +51 -0
  9. package/dist/components/overview/record.d.ts.map +1 -0
  10. package/dist/components/overview/record.js +146 -0
  11. package/dist/components/overview/server.d.ts +4 -0
  12. package/dist/components/overview/server.d.ts.map +1 -1
  13. package/dist/components/overview/server.js +4 -0
  14. package/dist/components/overview/types.d.ts +12 -0
  15. package/dist/components/overview/types.d.ts.map +1 -1
  16. package/dist/components/overview/widgets/LineItemsWidget.svelte +144 -0
  17. package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts +6 -0
  18. package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts.map +1 -0
  19. package/dist/components/overview/widgets/RecordListWidget.svelte +67 -3
  20. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -1
  21. package/dist/components/overview/widgets/RecordTable.svelte +167 -0
  22. package/dist/components/overview/widgets/RecordTable.svelte.d.ts +21 -0
  23. package/dist/components/overview/widgets/RecordTable.svelte.d.ts.map +1 -0
  24. package/dist/components/overview/widgets/RelatedWidget.svelte +117 -0
  25. package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts +6 -0
  26. package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts.map +1 -0
  27. package/dist/components/overview/widgets/core.d.ts +13 -2
  28. package/dist/components/overview/widgets/core.d.ts.map +1 -1
  29. package/dist/components/overview/widgets/core.js +43 -2
  30. package/dist/components/overview/widgets/data.d.ts +97 -8
  31. package/dist/components/overview/widgets/data.d.ts.map +1 -1
  32. package/dist/components/overview/widgets/line-totals.d.ts +48 -0
  33. package/dist/components/overview/widgets/line-totals.d.ts.map +1 -0
  34. package/dist/components/overview/widgets/line-totals.js +91 -0
  35. package/dist/components/overview/widgets/record-loaders.d.ts +63 -0
  36. package/dist/components/overview/widgets/record-loaders.d.ts.map +1 -0
  37. package/dist/components/overview/widgets/record-loaders.js +150 -0
  38. package/dist/components/overview/widgets/record-table.d.ts +45 -0
  39. package/dist/components/overview/widgets/record-table.d.ts.map +1 -0
  40. package/dist/components/overview/widgets/record-table.js +201 -0
  41. package/dist/components/overview/widgets/record-widgets.d.ts +27 -0
  42. package/dist/components/overview/widgets/record-widgets.d.ts.map +1 -0
  43. package/dist/components/overview/widgets/record-widgets.js +100 -0
  44. package/dist/components/screens/DetailScreen.svelte +105 -6
  45. package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -1
  46. package/dist/components/screens/EditForm.svelte +39 -3
  47. package/dist/components/screens/EditForm.svelte.d.ts.map +1 -1
  48. package/dist/components/screens/RecipeScreens.svelte +18 -18
  49. package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -1
  50. package/dist/components/screens/RecordHeader.svelte +238 -0
  51. package/dist/components/screens/RecordHeader.svelte.d.ts +26 -0
  52. package/dist/components/screens/RecordHeader.svelte.d.ts.map +1 -0
  53. package/dist/components/screens/RecordScreen.svelte +511 -0
  54. package/dist/components/screens/RecordScreen.svelte.d.ts +5 -0
  55. package/dist/components/screens/RecordScreen.svelte.d.ts.map +1 -0
  56. package/dist/components/screens/columns.d.ts +29 -0
  57. package/dist/components/screens/columns.d.ts.map +1 -0
  58. package/dist/components/screens/columns.js +115 -0
  59. package/dist/components/screens/fields.d.ts +12 -2
  60. package/dist/components/screens/fields.d.ts.map +1 -1
  61. package/dist/components/screens/fields.js +37 -5
  62. package/dist/components/screens/index.d.ts +7 -2
  63. package/dist/components/screens/index.d.ts.map +1 -1
  64. package/dist/components/screens/index.js +6 -1
  65. package/dist/components/screens/record-header.d.ts +51 -0
  66. package/dist/components/screens/record-header.d.ts.map +1 -0
  67. package/dist/components/screens/record-header.js +118 -0
  68. package/dist/components/screens/relations.d.ts +49 -0
  69. package/dist/components/screens/relations.d.ts.map +1 -0
  70. package/dist/components/screens/relations.js +98 -0
  71. package/dist/components/screens/roles.d.ts +56 -0
  72. package/dist/components/screens/roles.d.ts.map +1 -0
  73. package/dist/components/screens/roles.js +174 -0
  74. package/dist/components/screens/types.d.ts +182 -1
  75. package/dist/components/screens/types.d.ts.map +1 -1
  76. package/dist/components/screens/values.d.ts +23 -1
  77. package/dist/components/screens/values.d.ts.map +1 -1
  78. package/dist/components/screens/values.js +80 -4
  79. package/dist/i18n/strings.overview.d.ts +25 -0
  80. package/dist/i18n/strings.overview.d.ts.map +1 -1
  81. package/dist/i18n/strings.overview.js +25 -0
  82. package/dist/i18n/strings.screens.d.ts +8 -0
  83. package/dist/i18n/strings.screens.d.ts.map +1 -1
  84. package/dist/i18n/strings.screens.js +8 -0
  85. package/package.json +16 -16
package/AGENTS.md CHANGED
@@ -15,12 +15,12 @@ subpath you are editing. This file keeps what holds in every module.
15
15
  | `src/test-support/` + `__tests__/` | the golden-test harness and pattern for Svelte component tests | [agents/testing.md](agents/testing.md) |
16
16
  | `src/components/import-export/` (`./import-export`) | `ImportExport` CSV/TSV import (column mapping, validation preview, error report) and export (column selection) driven by collection definitions + resolved field policy, over a pure Node-tested CSV layer (#3715) | [agents/import-export.md](agents/import-export.md) |
17
17
  | `src/components/settings/` (`./settings`) | `SettingsCatalog`, `paginateSettingsCatalog`, and the summary-vs-detail scalability contract | [agents/settings.md](agents/settings.md) |
18
- | `src/components/screens/` (`./screens`) | `RecipeScreens` / `ListScreen` / `DetailScreen` / `EditForm`: list, view, create and edit derived from a generated web definition plus resolved field policy; pure `deriveScreenFields` selection, minor-unit money handling, transport-neutral source (#3718) | [agents/screens.md](agents/screens.md) |
18
+ | `src/components/screens/` (`./screens`) | `RecipeScreens` / `ListScreen` / `RecordScreen` / `DetailScreen` / `EditForm`: list, view, create and edit derived from a generated web definition plus resolved field policy; pure `deriveScreenFields` selection, `deriveFieldRoles` (title / identifier / status / money / totals roles) and `deriveScreenRelations` (children / items / reference); the record page's header (identifier, status, key facts, overflow Delete), details card and overview, select / derived fields and an items slot in inline (desktop) or sheet (phone) editing; minor-unit money handling, transport-neutral source (#3718) | [agents/screens.md](agents/screens.md) |
19
19
  | `src/components/app/` (`./app`) | `AppShell` (Provider + ThemeProvider + theme CSS + AdminShell + nav/dock slots), `OwnerSetupForm` (props-driven first-run owner form), `ShellSettingsPage`, `RuntimeDiagnosticsWebMcp`. Browser-only; no `$app/*` or server imports | [agents/workspace.md](agents/workspace.md#app-shell-and-owner-setup-app) |
20
20
  | `src/components/workspace/` (`./workspace` + `./web`) | the AdminShell family and its principles, the legacy ToolsDock surface, the `./web` activity-feed and `updateAvailable` adapters, and server-side dock gates | [agents/workspace.md](agents/workspace.md) |
21
21
  | `src/components/command-palette/` (`./command-palette`) | `CommandPalette` trigger + dialog, `PaletteProvider` contract, nav and manifest providers (#3713) | [agents/command-palette.md](agents/command-palette.md) |
22
22
  | `src/components/cookbook/` (`./cookbook` + `./cookbook/server`) | cookbook-driven app shell: pure `buildCookbookShell` (cookbook + recipe catalog to `AppShell` sections, entries, layout, theme, section overviews, recipe surfaces), `CookbookApp` / `CookbookSectionPage`, and the server `loadCookbookApp` (file + installed manifests + validation); ported from smrt-planner (#3749) | [agents/cookbook-shell.md](agents/cookbook-shell.md) |
23
- | `src/components/overview/` (`./overview` + `./overview/server`) | customizable overview surfaces: `OverviewGrid` flow grid (span 1-4, keyboard reorder/resize) joined to the shell's layout edit mode, host-owned `createOverview` controller, widget registry with versioned option schemas validated on save and load, the server `load(options, ctx)` contract (`loadOverview`), the metric/chart/records/shortcuts/note core widgets, and `registerRecipeWidgets` (recipe `widget` surfaces to registry registrations, injected module resolver) (#3727); production persistence is `@happyvertical/smrt-preferences` (kind `overview`) | [agents/overview-surfaces.md](agents/overview-surfaces.md) |
23
+ | `src/components/overview/` (`./overview` + `./overview/server`) | customizable overview surfaces: `OverviewGrid` flow grid (span 1-4, keyboard reorder/resize) joined to the shell's layout edit mode, host-owned `createOverview` controller, widget registry with versioned option schemas validated on save and load, the server `load(options, ctx)` contract (`loadOverview`), the metric/chart/records/shortcuts/note core widgets (records as a role-column table with a `DataTable` summaries footer), the record-page `related` (tabbed) and `line-items` (totals block) widgets, and `registerRecipeWidgets` (recipe `widget` surfaces to registry registrations, injected module resolver) (#3727); production persistence is `@happyvertical/smrt-preferences` (kind `overview`) | [agents/overview-surfaces.md](agents/overview-surfaces.md) |
24
24
  | `src/web/remote-query.svelte.ts` | Svelte 5 binding for query-shaped remote pages: rows, page, totals, loading/refreshing/stale/error, retry, last-updated, and query-scoped live subscriptions (#2445) | — |
25
25
  | `src/web/list-data-surface.svelte.ts` (`mountListDataSurface`) | Registers an existing non-`DataTable` list (mirroring a `DataTableController`) as a mounted `DataSurfaceDescriptor`; a same-behavior port of `registerContentListDataSurface`, not yet unified behind smrt-ui (#2917). The module doc comment is the authoritative list of divergences (#2906) | — |
26
26
  | `src/web/webmcp-provider.ts` | Provider config for generated data/model WebMCP tools: definitions, effect policy, namespace, budget, legacy/canonical filters, and fetcher seams (#2520) | — |
package/README.md CHANGED
@@ -708,13 +708,13 @@ importable, even if it appears in `dist/`.
708
708
  | `@happyvertical/smrt-svelte/forms` | Form inputs (TextInput, Select, MoneyInput, DateTimeInput, Toggle, etc.) |
709
709
  | `@happyvertical/smrt-svelte/import-export` | `ImportExport` (CSV/TSV import with column mapping, validation preview and error report; export with column selection), `createCollectionImportExport`, `fieldsFromCollectionDefinition`, and the pure CSV layer |
710
710
  | `@happyvertical/smrt-svelte/settings` | Server-paged settings search, selection, and list/detail layout (`SettingsCatalog`, `paginateSettingsCatalog`) |
711
- | `@happyvertical/smrt-svelte/screens` | List, view, create and edit screens derived from a generated web definition and resolved field policy (`RecipeScreens`, `ListScreen`, `DetailScreen`, `EditForm`) |
711
+ | `@happyvertical/smrt-svelte/screens` | List, view, create and edit screens derived from a generated web definition and resolved field policy (`RecipeScreens`, `ListScreen`, `RecordScreen`, `DetailScreen`, `EditForm`); `RecordScreen` is the record view with a customizable related-records overview and inline (desktop) or sheet (phone) editing, `deriveScreenRelations` its relation rule |
712
712
  | `@happyvertical/smrt-svelte/workspace` | AdminShell, ShellState, tenant nav, focus tools, settings, activities, and system/app panels |
713
713
  | `@happyvertical/smrt-svelte/app` | `AppShell` (Provider + themes + AdminShell + nav/dock slots, `dockToggles` buttons and host `slots` for the header/footer/sidebar regions), `OwnerSetupForm` (first-run owner setup), `ShellSettingsPage`, `RuntimeDiagnosticsWebMcp` |
714
714
  | `@happyvertical/smrt-svelte/app/runtime-diagnostics` | Svelte-free diagnostics WebMCP registration and its tool name/endpoint constants, importable from server routes |
715
715
  | `@happyvertical/smrt-svelte/command-palette` | Global search and command palette: `CommandPalette` (shell-slot trigger plus dialog), `createCommandPalette`, the provider contract, and the navigation and manifest-driven model providers |
716
716
  | `@happyvertical/smrt-svelte/overview` | Customizable overview surfaces: `OverviewGrid` (flow grid with keyboard reorder and resize), `createOverview` (host-owned controller), the widget registry (`registerWidget`), `registerCoreWidgets` (metric, chart, record list, shortcuts, note) and `shortcutsFromNav` |
717
- | `@happyvertical/smrt-svelte/overview/server` | Svelte-free half of the overview surface: the data model (`resolveOverview`, `checkOverviewOverride`), option-schema validation, `WidgetRegistry`, and `loadOverview` for a page's server load |
717
+ | `@happyvertical/smrt-svelte/overview/server` | Svelte-free half of the overview surface: the data model (`resolveOverview`, `checkOverviewOverride`), option-schema validation, `WidgetRegistry`, and `loadOverview` for a page's server load; `recordOverview` / `recordOverviewId` (the record view's default overview, per model kind) |
718
718
  | `@happyvertical/smrt-svelte/workspace/legacy` | Opt-in ToolsDock compatibility surface for applications migrating to AdminShell |
719
719
  | `@happyvertical/smrt-svelte/workspace/server` | Server-side workspace helpers (Node only) |
720
720
  | `@happyvertical/smrt-svelte/workspace/live` | `systemFeed` — the AdminShell system scope (jobs/schedules/dispatch) polled from an app status endpoint; deliberately carries no `smrt-web` dependency |
@@ -113,10 +113,26 @@ return { overview }; // LoadedOverview: JSON, serializable
113
113
 
114
114
  - A widget's `load(options, ctx)` receives **validated options only** and a
115
115
  `WidgetLoadContext` the host built from the request principal: `overviewId`, an
116
- abort `signal`, `locale`, and any host capabilities (index keys). It must
116
+ abort `signal`, `locale`, the optional core `record` key (below), and any host
117
+ capabilities (index keys). It must
117
118
  authorize through `ctx` (the user's permissions and tenant filters), never
118
119
  through option values. Reports-backed chart loaders call the runtime-report
119
120
  compiler with this context; this package does not depend on it.
121
+ - **`ctx.record = { model, id }`** is a core capability key: the record a
122
+ record overview is rendered on (`RecordScreen` sets it from the screen's
123
+ definition `objectRef` and the record's id; a host's server load sets it the
124
+ same way for a record page). With it comes the **parent-filter vocabulary**
125
+ of the core `records` and `metric` loaders: `filter: 'parent'` plus `field`
126
+ (the FK field on `options.model`) mean "rows of `model` whose `field` equals
127
+ `ctx.record.id`" — a count for `metric`, the newest rows for `records`
128
+ (`sort` / `direction` / `limit` as usual). A `metric` with `parentField`
129
+ uses that as the FK and measures `field` (`measure: 'sum'`, `field:
130
+ 'totalAmount'`, `parentField: 'customerId'`: the record's order total). A
131
+ `records` load may carry `columns` (comma-separated keys; fill each row's
132
+ `cells`). A loader given `filter: 'parent'`
133
+ without `ctx.record` or `field` must return an empty result, never the
134
+ unfiltered model. The shell's own loaders need no data access: hosts
135
+ implement it, the same way they implement every other named filter.
120
136
  - Loads run in parallel with a per-widget timeout (default 8 s; the signal
121
137
  aborts). A thrown error, a timeout or non-serializable / oversized data
122
138
  (default 256 KB of JSON) becomes a per-widget error tile
@@ -197,9 +213,9 @@ shape. Data shapes are in `widgets/data.ts`.
197
213
 
198
214
  | Type | Options | Data |
199
215
  |---|---|---|
200
- | `metric` | `title`, `model`*, `measure`, `field`, `filter` | `{ value, format?, currency?, label?, change?, href? }` (money = integer minor units) |
216
+ | `metric` | `title`, `model`*, `measure`, `field`, `filter`, `parentField` | `{ value, format?, currency?, label?, change?, href? }` (money = integer minor units) |
201
217
  | `chart` | `title`, `model`*, `groupBy`, `measure`, `field`, `period`, `style` (bar/line/table/pie/stat), `filter` | `{ points: [{ label, value }], format?, currency?, href?, delta? }`, one measure of a runtime report |
202
- | `records` | `title`, `model`*, `sort`, `direction`, `limit`, `filter` | `{ rows: [{ id, title, subtitle?, meta?, href? }], total?, href? }`; the loader chooses the title field and the visible records |
218
+ | `records` | `title`, `model`*, `sort`, `field`, `direction`, `limit`, `filter`, `columns`, `summaries` | `{ rows: [{ id, title, subtitle?, meta?, href?, cells? }], total?, href?, columns?, currency?, timeZone?, summaries? }`; the loader chooses the title field and the visible records; `field` is the FK for `filter: 'parent'`; with columns (the option, else `data.columns`) the rows are a table — see "Records tables" |
203
219
  | `shortcuts` | `title`, `section` | `{ items: [{ id, label, href, icon?, description?, count? }] }` (`count` draws as "N records"; widgets also receive the grid's `presentation` and the shortcuts widget drops its entry frame under `plain`); `shortcutsFromNav(sections, sectionId)` maps `resolveShellNavModel` / `useShellLayout().sections` (visible entries, user renames) |
204
220
  | `note` | `title`, `body` (Markdown) | none |
205
221
 
@@ -219,6 +235,64 @@ else derived from the last two points). Motion honours `prefers-reduced-motion`.
219
235
  The chart option schema is **version 2**: v1 (`style` bar or line, or no version)
220
236
  migrates by identity (`migrateChartOptions`), so saved overviews keep validating.
221
237
 
238
+ ## Records tables (`widgets/RecordTable.svelte`, `widgets/record-table.ts`)
239
+
240
+ The `records` widget with columns, each `related` tab and `line-items` render
241
+ one internal `RecordTable`: smrt-ui `DataTable` (dense, `phoneLayout="cards"`)
242
+ with cells by `RecordListColumn.role` — `identifier` monospace, `status` a
243
+ `Badge` (`statusTone`), `date` date-only in `data.timeZone` (default UTC, so
244
+ SSR and the browser agree; a non-ISO string is shown as sent), `money` from
245
+ integer minor units in `data.currency`, `quantity` a number — and the first
246
+ column linking to the row's `href`. The footer is `DataTable`'s `summaries`
247
+ (no per-widget footer code): the count (`total`, else the rows) in the first
248
+ column and the sum of every `money` / `quantity` column unless
249
+ `summarize: false` (a unit price). **A partial page is never summed**: when
250
+ `total` exceeds the rows, a sum cell is empty unless the loader sent
251
+ `summaries` (totals over every matching record, money in minor units). The
252
+ `columns` option is a comma-separated list because options are flat JSON
253
+ primitives (`parseColumnsOption` / `formatColumnsOption`, at most 6 keys);
254
+ labels and roles come from `data.columns`, else a humanized key. Pure,
255
+ Node-tested helpers: `formatRecordCell`, `statusTone` /
256
+ `DEFAULT_STATUS_TONES`, `statusLabel`, `resolveRecordColumns`,
257
+ `readRecordRows`, `isSummedColumn`.
258
+
259
+ ## Record-page widgets (`related`, `line-items`; `widgets/record-loaders.ts`)
260
+
261
+ Built from the record's relations, so they register per record view —
262
+ `registerRecordWidgets(registry, { relations, records, viewAllHref?,
263
+ totals?, currency? })`; `RecordScreen` does it on its own registry when
264
+ `loaders.records` is given. **Loader contract: no new loader.** Each tab /
265
+ the lines are one call of the host's core `records` loader with the
266
+ parent-filter vocabulary (`model`, `field`, `filter: 'parent'`, `sort`,
267
+ `direction`, `limit`, `columns`), so a host that implements parent-filtered
268
+ `records` implements both. Loaders are Svelte-free on both entries
269
+ (`createRelatedLoader`, `createLineItemsLoader`, `relatedRelations`,
270
+ `relatedTabOptions`, `lineItemsRelation`, `lineItemsOptions`).
271
+
272
+ | Type | Options | Data |
273
+ |---|---|---|
274
+ | `related` | `title`, `relations` (relation keys `Order-customerId`, comma-separated; empty = every `children` relation, at most 20), `limit` (5) | `{ tabs: [{ key, label, rows, total?, columns, href?, currency?, timeZone?, summaries? }] }` |
275
+ | `line-items` | `title`, `relation` (an `items` relation key; empty = the first), `limit` (50, the max) | `{ rows, columns, total?, totals?: { subtotal?, discount?, tax?, total? }, currency?, summaries?, href? }` |
276
+
277
+ - `related`: one tab per relation with the count in the tab ("Orders 6"),
278
+ each a records table with a totals footer and that tab's one "View all"
279
+ (`viewAllHref`, else the loaded `href`). Relations with no rows are hidden;
280
+ all empty is one empty state. A failing tab is dropped; all failing fails
281
+ the widget. Never lists `items` relations.
282
+ - `line-items` (the contract's `lineItems`; widget type ids are lowercase
283
+ kebab): the lines in `sort` order, then the totals block
284
+ (`lineItemTotals`, pure). The record's own subtotal / discount / tax /
285
+ total are authoritative and shown as they are; one it lacks is computed
286
+ from the lines (subtotal = sum of the line amounts, total = subtotal -
287
+ discount + tax) and marked "(from the lines)"; discount and tax show when
288
+ present and non-zero. When the record's subtotal (or, without one, its
289
+ total) disagrees with the lines, a visible flag says "Lines add up to $X."
290
+ beside the recorded figure — never a silent override. A partial page of
291
+ lines is not compared.
292
+ - Tests: `__tests__/record-table.test.ts`, `__tests__/record-widgets.test.ts`,
293
+ `__tests__/line-items.test.ts` (axe on both components), the records cases
294
+ in `__tests__/widgets.test.ts`.
295
+
222
296
  ## Recipe widgets (`recipe-widgets.ts`)
223
297
 
224
298
  A package contributes a widget with a `widget` surface on a recipe
@@ -250,7 +324,8 @@ turns the manifest's widget surfaces into `registry.register(...)` calls:
250
324
  - **`enabled(recipeId)`** keeps widgets of recipes that are off out of the add
251
325
  list. Call the helper again (after `dispose()`) when the set changes.
252
326
  - Widget types share one registry namespace with the core widgets
253
- (`metric`, `chart`, `records`, `shortcuts`, `note`). Pages still confine what
327
+ (`metric`, `chart`, `records`, `shortcuts`, `note`) and the record-page
328
+ widgets (`related`, `line-items`). Pages still confine what
254
329
  can appear with `defineOverview({ allowed })`, and a widget with `allowedIn`
255
330
  only validates inside those overview ids.
256
331
  - `src/components/overview/__tests__/recipe-widgets.test.ts` covers lazy
@@ -374,6 +449,35 @@ and the person can undo the change.
374
449
  `createOverviewStore(...).save` as shown there.
375
450
  - **Not built:** the activity widget (Track B, #3710) and agenda.
376
451
 
452
+ ## Record overviews (`record.ts`)
453
+
454
+ `recordOverview(definition, relations, { id?, metrics?, maxMetrics?,
455
+ related? })` (both entries) is the default overview of a record page:
456
+
457
+ 1. **Summary strip** (`metrics`, default on): `metric` tiles of span 1, a
458
+ count per `children` relation (`field` = the FK) and, where its columns
459
+ have a money column, the sum of it (`measure: 'sum'`, `field` = the
460
+ amount, `parentField` = the FK), at most `maxMetrics`
461
+ (`RECORD_OVERVIEW_MAX_METRICS` = 4). Ordinary widgets: removable.
462
+ 2. **`line-items`** (span 4) for the first `items` relation.
463
+ 3. **`related`** (span 4, id `related`) over the `children` relations;
464
+ `related: 'panels'` instead declares one `records` panel per relation
465
+ (span `RECORD_OVERVIEW_PANEL_SPAN` = 2, newest five, `columns` from the
466
+ relation) — the previous default.
467
+
468
+ Default widget ids come from the relation (`count-Order-customerId`,
469
+ `sum-...`, `items-OrderLine-orderId`, `records-...`; a `-2` suffix tells apart
470
+ two that fold alike), not their position, so a host that narrows the defaults
471
+ keeps every id, and added widgets (`w<n>`) never collide with a hidden
472
+ default; `models` confines added widgets to the child models and `allowed` is
473
+ `RECORD_OVERVIEW_ALLOWED` (`related`, `line-items`, `records`, `metric`,
474
+ `chart`, `note`). The id defaults to `recordOverviewId(definition.objectRef)`
475
+ — `detail:<model>` with `@`, `/` and `:` folded to dots
476
+ (`detail:acme.shop.Customer`), because a qualified model id is not a valid
477
+ overview id — so the layout persists **per model kind**, not per record.
478
+ `RecordScreen` renders it with `presentation="plain"` and extends the widget
479
+ context with `record`.
480
+
377
481
  ## Host section presentation
378
482
 
379
483
  `OverviewGrid` defaults to framed cards. Pass `presentation="plain"` for
package/agents/screens.md CHANGED
@@ -8,8 +8,9 @@ invariants, and the traps that apply before editing anything live in
8
8
 
9
9
  `RecipeScreens` is the default screen set for a package that ships models but no
10
10
  consumer UI (tags, facts, secrets, profiles, reports, prompts). It composes
11
- `ListScreen`, `DetailScreen` and `EditForm`, each of which can also be used on
12
- its own. Every screen takes a generated web `definition`
11
+ `ListScreen`, `RecordScreen` (the record view, which itself composes
12
+ `DetailScreen`, `OverviewGrid` and `EditForm`) and `EditForm` (create), each of
13
+ which can also be used on its own. Every screen takes a generated web `definition`
13
14
  (`ScreenCollectionDefinition`, structurally a `SmrtWebCollectionDefinition`) and
14
15
  an optional resolved `policy` (`ScreenPolicy`, structurally smrt-fields'
15
16
  `ResolvedObjectFieldPolicy`). The module imports neither package: smrt-svelte
@@ -34,14 +35,25 @@ stays below the domain layer, and the app passes both in as props.
34
35
  policy default (an explicit `null` included) beats the manifest default.
35
36
  `locked` governs who may change the POLICY, never the record value, so it is
36
37
  not applied here.
37
- - **Values are `values.ts`**: money (`ui.widget: 'currency'` on an INTEGER field
38
- only) is integer minor units end to end. Editing shows major units but converts
38
+ - **Values are `values.ts`**: money (an INTEGER field with the `money` role —
39
+ `ui.format: 'money'`, the legacy `ui.widget: 'currency'`, an explicit role,
40
+ or a money name; see "Field roles") is integer minor units end to end. Editing shows major units but converts
39
41
  with exact string math (`majorStringToMinorUnits`), never `* 100`; integers are
40
42
  rejected unless JavaScript-safe; datetimes go out as ISO strings (an unchanged datetime keeps the
41
43
  loaded record's full precision via `parseDraft`'s `record` context; the control
42
- only shows minutes); JSON as parsed values. Empty optional text becomes `null` when the field is nullable and `''`
44
+ only shows minutes); JSON as parsed values. For display an empty JSON
45
+ object/array (or one whose values are all blank) formats as `''`, the
46
+ "Not set" placeholder, and `DetailScreen` lists a non-empty object as
47
+ key/value rows (`jsonScreenEntries`). Empty optional text becomes `null` when the field is nullable and `''`
43
48
  otherwise (unset text persists as `''`); every other empty optional field is
44
49
  `null`. Errors are codes, mapped to messages by the component via `useI18n`.
50
+ - **Reference fields link (`relations`, `hrefFor`, `labels`).** `DetailScreen`
51
+ renders a `foreignKey` / `crossPackageRef` value as `<a href={hrefFor(field,
52
+ id)}>{labels[model](id)}</a>`; without an href the label alone, without a
53
+ label a shortened id (`shortScreenId`: first eight characters and `…`) in
54
+ `<code>` with the full id as its tooltip, inside the link when there is one. `labels` is keyed by the **target model id**,
55
+ which the screen finds through the `reference` relations (below), so pass
56
+ `relations` whenever you pass `labels`.
45
57
  - **Transport-neutral.** `RecipeScreens` drives a `RecipeScreensSource`
46
58
  (`list` / `get` / `create` / `update` / `delete`); an operation the source lacks
47
59
  (or `can` turns off) is simply not offered, so a read-only source is a
@@ -49,6 +61,13 @@ stays below the domain layer, and the app passes both in as props.
49
61
  fires on every user navigation, so an app can mirror them into its router.
50
62
  `EditForm.onsubmit` throws to report a failed save or returns `{ fieldErrors }`.
51
63
  Remount `EditForm` with `{#key}` when switching records (`RecipeScreens` does).
64
+ - **`view` and `edit` are `RecordScreen`** (below); `create` stays the bare
65
+ `EditForm`. `onnavigate` is unchanged (`edit` is still a view of its own, so
66
+ a router maps it to `.../<id>/edit`). Pass `definitions` (every exposed
67
+ collection, keyed by name) for the related-records panels and `record`
68
+ (loaders, widget context, overview override and its `onoverviewchange`,
69
+ `hrefFor`, `labels`, `viewAllHref`) for the rest of the record view; without
70
+ `definitions` there are no relations and the view is the detail alone.
52
71
  - **Not the policy-editing surface.** The policy gear, field-usage telemetry and
53
72
  the per-field renderer registry live in `ObjectForm`
54
73
  (`@happyvertical/smrt-fields/svelte`); `EditForm` is the dependency-light
@@ -60,3 +79,150 @@ stays below the domain layer, and the app passes both in as props.
60
79
  `collectionDefinitions[<collection>]` from `@smrt/web` and the source is the
61
80
  model's `/api/<collection>` route (see [cookbook-shell.md](cookbook-shell.md)).
62
81
  A model the API does not expose has neither, so the entry page shows its notice.
82
+
83
+ ## Field roles (`roles.ts`)
84
+
85
+ `deriveFieldRoles(definition)` is the pure, Node-tested rule for what a field
86
+ MEANS on a record page: `FieldRole` = `title | identifier | status | money |
87
+ quantity | date | party | subtotal | tax | discount | total`, several per field
88
+ (`totalAmount` -> `['money', 'total']`), one key per declared field. Sources in
89
+ order:
90
+
91
+ 1. Explicit metadata on `ScreenFieldDefinition`: `role` (authoritative — name
92
+ heuristics never add to it), `format` (`money | date | datetime | percent`,
93
+ wins over `ui.format`), `display: true` (the `title`), `enum`.
94
+ 2. Manifest hints: `ui.format`, and `ui.widget: 'currency'` on an INTEGER (on
95
+ a text field it is the currency-code picker, never money;
96
+ `currencyCodeField` finds it, and `RecordScreen` formats amounts in the
97
+ record's own currency).
98
+ 3. Name heuristics: `*Amount|*Total|price|cost` -> money on integers only;
99
+ `subtotal|tax|discount|total` (not `*Rate`); `status|state` WITH an enum;
100
+ `number|reference|code` but not `phoneNumber` / `postalCode` /
101
+ `currencyCode`; `*At|*Date|*On|*Until` datetimes; `quantity|qty`; an FK
102
+ whose name or target collection is party-like (customer, vendor, profile,
103
+ ...). Without a `display` field the first text `name|title|label|
104
+ displayName` is the title.
105
+
106
+ `deriveScreenFields` uses them: a text field with an `enum` is kind `select`
107
+ (`options`), money comes from the roles, and every `ScreenField` carries
108
+ `roles`. `formatScreenValue` shows `format: 'date'` without a time and
109
+ `format: 'percent'` as a percentage. Helpers: `fieldsWithRole`,
110
+ `fieldWithRole`, `isDocumentLike` (identifier plus party or total),
111
+ `hasLineItemRoles` (quantity plus money). Columns by role are
112
+ `screens/columns.ts`: `relatedColumns` (identifier or title, status, lead
113
+ date, party, amount) and `lineItemColumns` (item, quantity, unit price —
114
+ `summarize: false` —, amount).
115
+
116
+ ## Relations (`relations.ts`)
117
+
118
+ `deriveScreenRelations(definitions, model)` is the pure, Node-tested rule for
119
+ what a record view shows around a record. `definitions` is every exposed
120
+ collection definition keyed by collection name (`collectionDefinitions` from
121
+ `@smrt/web`); `model` is a qualified `objectRef` (or a key of the map). It
122
+ reads the generated `relationships` edges (`ScreenRelationshipDefinition`:
123
+ `field`, `kind`, `relatedCollection` — a collection **name**, resolved by map
124
+ key, then by `name`), so a definition without `relationships` has no
125
+ relations. A `ScreenRelation` is `{ model, field, label, kind, columns?, sort? }`
126
+ (`relationKey(relation)` = `Order-customerId`):
127
+
128
+ | `kind` | Meaning | `model` | `field` | `label` |
129
+ |---|---|---|---|---|
130
+ | `children` | another collection with a `foreignKey` / `crossPackageRef` edge targeting this model | the child's `objectRef` | the FK on the child | the child's plural title; two FKs to the same parent disambiguate as `Orders (Approver)` |
131
+ | `items` | a `children` edge marked `relationships[].role: 'items'` (core `@foreignKey(T, { role: 'items' })`), or a child with line-item roles and one FK here | as `children` | as `children` | as `children` |
132
+ | `reference` | one of this model's own FK edges | the target's `objectRef` | the FK on this model | the field label (`ownerId` -> `Owner`) |
133
+
134
+ `items` come first, then `children`, each sorted by label, then `reference`
135
+ in declaration order. `children` carry `relatedColumns`, `items`
136
+ `lineItemColumns` and a line `sort` (`sortOrder`, `position`, `lineNumber`,
137
+ `sequence`, else `createdAt`).
138
+ Edges whose target or source is not in `definitions` are skipped (an unexposed
139
+ model has no route to link to), as are `oneToMany` / `manyToMany` edges and
140
+ edges whose field the definition does not carry.
141
+
142
+ ## RecordScreen (`RecordScreen.svelte`, `./screens`)
143
+
144
+ The record page: a header, a details card, a customizable `OverviewGrid`
145
+ below, and the edit form as a **mode** of the same screen. Props are
146
+ `RecordScreenProps` in `types.ts`.
147
+
148
+ - **Header anatomy (`RecordHeader.svelte`, model `deriveRecordHeader` in
149
+ `record-header.ts`).** A small back link above the title; the heading is
150
+ `title`, else a document's kind and identifier ("Order GRA-3159",
151
+ identifier monospace — never the party), else the title-role field, else
152
+ the identifier, else the class name; a non-document's identifier shows as
153
+ a monospace chip; the status is a `Badge` toned by value (`statusTones`,
154
+ else `DEFAULT_STATUS_TONES`, else neutral); two to four key facts by role
155
+ (party link, lead date shown date-only, total money, then email / phone).
156
+ It reads only the view fields, so a field the policy omits never reaches
157
+ the header. Actions: Edit is the one primary button (hidden while
158
+ editing); a "More actions" popover holds `extraActions` and Delete, which
159
+ still confirms.
160
+ - **Details card.** `DetailScreen` with `variant="card"`, `hideEmpty` (empty
161
+ fields behind "Show empty fields (n)") and `exclude`: what the header
162
+ shows, the currency-code field, and — when the model has an `items`
163
+ relation — the subtotal / discount / tax / total role fields, which the
164
+ line-items totals block shows instead. `children` render inside the card.
165
+ - **Body = `recordOverview(definition, relations)`** unless an `overview` is
166
+ passed: the summary strip, `line-items`, then `related` (see
167
+ [overview-surfaces.md](overview-surfaces.md#record-overviews-recordts)),
168
+ persisted **per model kind** under `recordOverviewId(objectRef)`
169
+ (`detail:acme.shop.Customer`). The grid renders `presentation="plain"`,
170
+ follows the shell's layout editor unless `layoutEditing` is passed, and
171
+ offers the child models in its add dialog. `override` / `onoverviewchange`
172
+ are the host-owned persistence pair of `createOverview`
173
+ (`@happyvertical/smrt-preferences`, kind `overview`, in production); omit
174
+ `override` to keep edits in memory. With `loaders.records` the screen also
175
+ registers `related` and `line-items` (`registerRecordWidgets`) with the
176
+ record's totals (`recordTotals`) and currency.
177
+ - **Loaders run in the browser with the record context.** `loaders`
178
+ (`CoreWidgetLoaders`) are registered on a registry of the screen's own
179
+ (or pass `registry` with recipe widgets on it); each `load` receives
180
+ `widgetContext` extended with `overviewId`, the `signal`, `locale` and
181
+ `record: { model: definition.objectRef, id }`, and honours `filter:
182
+ 'parent'` + `field` as overview-surfaces.md describes. A widget without a
183
+ loader is not registered, so a default needing it is dropped (reported,
184
+ never an error); without `loaders` the grid is empty. A record change
185
+ reloads every panel; the registry and overview are fixed at mount, so
186
+ remount (`{#key}`) to change the model kind — `RecipeScreens` keys on
187
+ `definition.objectRef`.
188
+ - **One heading per related panel.** A `records` widget whose `model` +
189
+ `field` + `filter: 'parent'` match a `children` relation (user-added ones
190
+ included) is headed `Orders · 3` (the loader's `total`, else its row count)
191
+ with the panel's only "View all": `viewAllHref(relation)`, else the loaded
192
+ `href`. The screen strips `total` / `href` from that widget's data, so the
193
+ body never repeats them. A `metric` widget keeps the plain title and no
194
+ link (its number links when its data has an `href`).
195
+ - **Edit form.** `EditForm` edits a `select` field with a smrt-ui `Select`
196
+ (humanized choices, "Not set" unless required, an out-of-list stored value
197
+ kept). `derivedFields` render read-only and formatted with "Worked out
198
+ from the line items." and are never in the payload; `RecordScreen` passes
199
+ its `derivedFields` prop, else the totals role fields when an `items`
200
+ relation exists. `items?: Snippet` (the host's line-items editor) renders
201
+ under the form, in the edit column on desktop and inside the edit sheet on
202
+ phones.
203
+ - **`mode: 'edit'`** keeps the detail rendered and adds `EditForm`
204
+ (`showHeader={false}`, keyed by record id, `fieldErrors` / `error` /
205
+ `submitting` / `onsave` as `EditFormProps`): on desktop (`min-width: 48rem`,
206
+ a `matchMedia` the screen watches; the server renders desktop) a sticky
207
+ `<aside>` in a `minmax(0, 1fr) minmax(20rem, 28rem)` grid beside the header,
208
+ sliding in with `--smrt-duration-medium1` / `--smrt-easing-standard` and
209
+ still under `prefers-reduced-motion`; on phones a `PhoneSheet
210
+ variant="page"` in a fixed full-screen `<section>` the screen owns (an
211
+ `AdminShell`'s `overlays` snippet is a shell prop a screen cannot reach, so
212
+ the screen stacks its own at `--smrt-z-index-modal`). The sheet stays
213
+ mounted while closed so it animates open; the form inside mounts only in
214
+ edit mode, so Cancel never keeps a stale draft. Cancel, the sheet's close
215
+ button and Escape all call `oncancel`; the detail's Edit action hides while
216
+ editing. `RecipeScreens` maps `onedit` to `navigate('edit', id)` and
217
+ `oncancel` / a saved `onsave` back to `view`.
218
+ - Strings: `ui.screens.view_all`, `ui.screens.related`, `more_actions`,
219
+ `details`, `show_empty`, `derived_hint`, `select_none`, `items`, and the
220
+ existing `ui.screens.edit_title` for the aside and sheet heading.
221
+ - Tests: `__tests__/roles.test.ts`, `__tests__/record-header.test.ts`,
222
+ `__tests__/relations.test.ts`, `__tests__/RecordScreen.test.ts` (header
223
+ anatomy, overflow Delete, details exclusions, line items and the mismatch
224
+ flag, derived fields and the items slot on both layouts, axe
225
+ in view and both edit layouts, the record context reaching every loader,
226
+ "View all" hrefs, host-owned override edits, record change reload) and the
227
+ link cases in `__tests__/DetailScreen.test.ts`; `overview/__tests__/record.test.ts`
228
+ and the parent-context case in `overview/__tests__/load.test.ts`.
@@ -18,9 +18,12 @@ export * from './server.js';
18
18
  export { default as WidgetOptionsForm } from './WidgetOptionsForm.svelte';
19
19
  export { default as ChartWidget } from './widgets/ChartWidget.svelte';
20
20
  export { type CoreWidgetLoaders, createChartWidget, createMetricWidget, createNoteWidget, createRecordListWidget, createShortcutsWidget, registerCoreWidgets, } from './widgets/core.js';
21
- export { type ChartWidgetData, type MetricWidgetData, type RecordListWidgetData, type ShortcutEntry, type ShortcutNavSection, type ShortcutsWidgetData, shortcutsFromNav, } from './widgets/data.js';
21
+ export { type ChartWidgetData, type LineItemsWidgetData, type MetricWidgetData, type RecordListColumn, type RecordListRow, type RecordListWidgetData, type RecordStatusTone, type RelatedWidgetData, type RelatedWidgetTab, type ShortcutEntry, type ShortcutNavSection, type ShortcutsWidgetData, shortcutsFromNav, } from './widgets/data.js';
22
+ export { default as LineItemsWidget } from './widgets/LineItemsWidget.svelte';
22
23
  export { default as MetricWidget } from './widgets/MetricWidget.svelte';
23
24
  export { default as NoteWidget } from './widgets/NoteWidget.svelte';
24
25
  export { default as RecordListWidget } from './widgets/RecordListWidget.svelte';
26
+ export { default as RelatedWidget } from './widgets/RelatedWidget.svelte';
27
+ export { createLineItemsWidget, createRelatedWidget, registerRecordWidgets, } from './widgets/record-widgets.js';
25
28
  export { default as ShortcutsWidget } from './widgets/ShortcutsWidget.svelte';
26
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/overview/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,aAAa,EACb,QAAQ,GACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EACL,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/overview/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,aAAa,EACb,QAAQ,GACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EACL,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,kCAAkC,CAAC"}
@@ -18,7 +18,10 @@ export { default as WidgetOptionsForm } from './WidgetOptionsForm.svelte';
18
18
  export { default as ChartWidget } from './widgets/ChartWidget.svelte';
19
19
  export { createChartWidget, createMetricWidget, createNoteWidget, createRecordListWidget, createShortcutsWidget, registerCoreWidgets, } from './widgets/core.js';
20
20
  export { shortcutsFromNav, } from './widgets/data.js';
21
+ export { default as LineItemsWidget } from './widgets/LineItemsWidget.svelte';
21
22
  export { default as MetricWidget } from './widgets/MetricWidget.svelte';
22
23
  export { default as NoteWidget } from './widgets/NoteWidget.svelte';
23
24
  export { default as RecordListWidget } from './widgets/RecordListWidget.svelte';
25
+ export { default as RelatedWidget } from './widgets/RelatedWidget.svelte';
26
+ export { createLineItemsWidget, createRelatedWidget, registerRecordWidgets, } from './widgets/record-widgets.js';
24
27
  export { default as ShortcutsWidget } from './widgets/ShortcutsWidget.svelte';
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The default overview of a record view (#3718 record page), persisted per
3
+ * model kind under `detail:<model>`: a summary strip of `metric` tiles
4
+ * (a count per related relation, and the sum of its money column where it
5
+ * has one), then the `line-items` table and totals when the model has an
6
+ * `items` relation, then one tabbed `related` widget over the `children`
7
+ * relations. Every piece is an ordinary widget, so a person can remove,
8
+ * move or replace it from the layout editor; `related: 'panels'` keeps the
9
+ * earlier one `records` panel per relation.
10
+ *
11
+ * Svelte-free. The widgets use the parent-filter vocabulary: `filter:
12
+ * 'parent'` plus the FK on the child (`field` for a count or a records
13
+ * panel; `parentField` when `field` is the measured one) confine the
14
+ * host's query to rows that point at `ctx.record.id`. `related` and
15
+ * `line-items` load through the host's `records` loader
16
+ * (`registerRecordWidgets`), so the shell's own loaders need no data access.
17
+ */
18
+ import type { ScreenRelation } from '../screens/relations.js';
19
+ import type { ScreenCollectionDefinition } from '../screens/types.js';
20
+ import type { OverviewDefinition } from './types.js';
21
+ export interface RecordOverviewOptions {
22
+ /** Overview id; defaults to `recordOverviewId(definition.objectRef)`. */
23
+ id?: string;
24
+ /** The summary strip of `metric` tiles (default on). */
25
+ metrics?: boolean;
26
+ /** Most tiles in the strip (default {@link RECORD_OVERVIEW_MAX_METRICS}). */
27
+ maxMetrics?: number;
28
+ /**
29
+ * `tabs` (default): one `related` widget with a tab per relation.
30
+ * `panels`: one `records` panel per relation, with role-chosen columns.
31
+ */
32
+ related?: 'tabs' | 'panels';
33
+ }
34
+ /** Widget types a record overview offers by default. */
35
+ export declare const RECORD_OVERVIEW_ALLOWED: readonly string[];
36
+ /** Tiles in the default summary strip. */
37
+ export declare const RECORD_OVERVIEW_MAX_METRICS = 4;
38
+ /** Rows a default `records` panel shows. */
39
+ export declare const RECORD_OVERVIEW_LIMIT = 5;
40
+ /**
41
+ * `detail:<model>` as an overview id. A qualified model id
42
+ * (`@acme/shop:Customer`) carries characters an overview id cannot
43
+ * (`OVERVIEW_PAGE_ID_PATTERN`), so `@`, `/` and `:` fold to dots:
44
+ * `detail:acme.shop.Customer`. Stable per model kind.
45
+ */
46
+ export declare function recordOverviewId(model: string): string;
47
+ /** Span of a default `records` panel: two side by side on a wide page. */
48
+ export declare const RECORD_OVERVIEW_PANEL_SPAN = 2;
49
+ /** The default record overview of `definition` for its relations. */
50
+ export declare function recordOverview(definition: ScreenCollectionDefinition, relations: readonly ScreenRelation[], options?: RecordOverviewOptions): OverviewDefinition;
51
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../src/components/overview/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,YAAY,CAAC;AAGrE,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED,wDAAwD;AACxD,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAOpD,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,4CAA4C;AAC5C,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD;AAwBD,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,qEAAqE;AACrE,wBAAgB,cAAc,CAC5B,UAAU,EAAE,0BAA0B,EACtC,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,OAAO,GAAE,qBAA0B,GAClC,kBAAkB,CAgGpB"}