@ai-matrx/records-ui 0.108.0 → 0.109.1
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.
- package/CHANGELOG.md +19 -0
- package/dist/first-page.cjs +54 -106
- package/dist/first-page.cjs.map +1 -1
- package/dist/first-page.d.cts +291 -6
- package/dist/first-page.d.ts +291 -6
- package/dist/first-page.js +44 -89
- package/dist/first-page.js.map +1 -1
- package/dist/index.cjs +842 -797
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +601 -99
- package/dist/index.d.ts +601 -99
- package/dist/index.js +629 -586
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +1 -0
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +2 -3
- package/dist/pickers.js.map +1 -1
- package/package.json +17 -2
- package/dist/views-VPwb3QQk.d.cts +0 -505
- package/dist/views-VPwb3QQk.d.ts +0 -505
package/dist/first-page.d.cts
CHANGED
|
@@ -1,11 +1,269 @@
|
|
|
1
|
-
import { Uuid, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
2
|
-
import { RecordsClient } from '@ai-matrx/records/core';
|
|
3
|
-
import {
|
|
4
|
-
import '@ai-matrx/design-system/
|
|
5
|
-
|
|
1
|
+
import { Uuid, RecordFilter, RuleExpression, RecordPageSort, RecordsConfig, Table, Field } from '@ai-matrx/records';
|
|
2
|
+
import { RecordsSeed, RecordsClient } from '@ai-matrx/records/core';
|
|
3
|
+
import { TableStyle, GroupAggregateSpec, GroupOrder } from '@ai-matrx/design-system/data-table';
|
|
4
|
+
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* HOW A TABLE LOOKS — colors, formats and the frozen column.
|
|
8
|
+
*
|
|
9
|
+
* THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a
|
|
10
|
+
* currency, a percent, a date, a person. A VIEW declares HOW that reads and
|
|
11
|
+
* what it is worth looking at: two decimal places or none, tint the row when
|
|
12
|
+
* Status is Blocked, keep Name in place while the rest scrolls. Airtable draws
|
|
13
|
+
* the line in exactly this place, and it has to be here: two people read one
|
|
14
|
+
* table two different ways, and neither reading is a fact about the records.
|
|
15
|
+
*
|
|
16
|
+
* So nothing in this module is a new column on a Field and nothing here needed
|
|
17
|
+
* a migration. A presentation lives on the saved-view record (`views.ts`), which
|
|
18
|
+
* is an ordinary record of an ordinary Table — which means an agent sets it
|
|
19
|
+
* through the same `records` tool it uses for everything else, and it travels
|
|
20
|
+
* with the table the way every other record does.
|
|
21
|
+
*
|
|
22
|
+
* A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`
|
|
23
|
+
* reads the Field's OWN declaration — a `currency` parity type with unit `$`, a
|
|
24
|
+
* `percent`, a `datetime` with `config.kind` — and returns the format that
|
|
25
|
+
* follows from it. The view's `formats` map only OVERRIDES that. A table nobody
|
|
26
|
+
* has ever styled still shows `$1,250.50` and `45%`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SECTIONS BY A FIELD — the view's own grouping.
|
|
31
|
+
*
|
|
32
|
+
* `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,
|
|
33
|
+
* because a view is written against the Table's declaration and never against
|
|
34
|
+
* whatever id a screen happened to build its columns with.
|
|
35
|
+
*
|
|
36
|
+
* `aggregates` is keyed by Field key too, and `collapsed` holds the data
|
|
37
|
+
* table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the
|
|
38
|
+
* primitive emits and the only thing that survives a value rendering the same
|
|
39
|
+
* text as another.
|
|
40
|
+
*
|
|
41
|
+
* NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every
|
|
42
|
+
* object in JavaScript already has all three, so an optional field by one of
|
|
43
|
+
* those names is NEVER absent and a `config.valueOf ? … : …` check silently
|
|
44
|
+
* reads `Object.prototype`. It cost the primitive a whole grouping once.
|
|
45
|
+
*/
|
|
46
|
+
type GridGrouping = {
|
|
47
|
+
/** The Field key whose value makes the sections. */
|
|
48
|
+
field: string;
|
|
49
|
+
/** Per-Field-key subtotal on every section header. */
|
|
50
|
+
aggregates?: Record<string, GroupAggregateSpec>;
|
|
51
|
+
/** Section order. The primitive's default is `value-asc`; blanks sort last. */
|
|
52
|
+
order?: GroupOrder;
|
|
53
|
+
/** The sections this view keeps shut, by the primitive's own group key. */
|
|
54
|
+
collapsed?: string[];
|
|
55
|
+
/** The grouped column drawn as a column too. Absent = it steps aside (the headers name its value). */
|
|
56
|
+
showColumn?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS "sub-grouping"): up to two more levels
|
|
59
|
+
* inside each section, by Field key. At most three levels in all — the store refuses a fourth
|
|
60
|
+
* (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,
|
|
61
|
+
* because this list is parsed and written back whole with the rest of the look.
|
|
62
|
+
*/
|
|
63
|
+
then?: GridSubGroup[];
|
|
64
|
+
/**
|
|
65
|
+
* DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or
|
|
66
|
+
* `<key>:<grain>` for a date cut into periods.
|
|
67
|
+
*/
|
|
68
|
+
across?: string;
|
|
69
|
+
/** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */
|
|
70
|
+
grain?: string;
|
|
71
|
+
/** The Measures shown ("what to display"), by the store's own keys (`count`, `sum_quantity`). */
|
|
72
|
+
show?: string[];
|
|
73
|
+
/** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */
|
|
74
|
+
trail?: GridDrillStep[];
|
|
75
|
+
};
|
|
76
|
+
/** One crumb of a saved drill trail. `value: null` is "no value". */
|
|
77
|
+
type GridDrillStep = {
|
|
78
|
+
dim: string;
|
|
79
|
+
value: string | null;
|
|
80
|
+
};
|
|
81
|
+
/** One sub-group level: a Field key and its section order. */
|
|
82
|
+
type GridSubGroup = {
|
|
83
|
+
field: string;
|
|
84
|
+
order?: GroupOrder;
|
|
85
|
+
grain?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Everything a view says about how its table LOOKS. */
|
|
88
|
+
type GridPresentation = {
|
|
89
|
+
/** Color-by-a-column, rules and manual highlights. */
|
|
90
|
+
style?: TableStyle;
|
|
91
|
+
/** Per-column format overrides, by the Field's key. */
|
|
92
|
+
formats?: Record<string, FieldFormatConfig>;
|
|
93
|
+
/**
|
|
94
|
+
* The columns that stay in place while the table scrolls sideways, by Field
|
|
95
|
+
* key. Only the LEADING run of the view's column order can actually freeze —
|
|
96
|
+
* the data table decides that, and declines rather than pinning a column at a
|
|
97
|
+
* guessed offset.
|
|
98
|
+
*/
|
|
99
|
+
frozen?: string[];
|
|
100
|
+
/** Collapsible sections by one Field, with subtotals on each header. */
|
|
101
|
+
grouping?: GridGrouping;
|
|
102
|
+
/**
|
|
103
|
+
* The width a person dragged a column to, by Field key. SPARSE on purpose —
|
|
104
|
+
* a view saved before widths existed carries none and still parses, and a
|
|
105
|
+
* column nobody resized is left to the table's own measurement.
|
|
106
|
+
*/
|
|
107
|
+
widths?: Record<string, number>;
|
|
108
|
+
/** The body row height this view remembers, in px. */
|
|
109
|
+
rowHeight?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Field keys hidden from this view — the SAME hidden-column list the grid's
|
|
112
|
+
* own column picker holds, now given a place on the view so it survives a
|
|
113
|
+
* reload and a layout switch reads the identical set gallery and grid agree
|
|
114
|
+
* on, rather than a second "fields shown" list nobody kept in sync.
|
|
115
|
+
*/
|
|
116
|
+
hiddenFields?: string[];
|
|
117
|
+
/**
|
|
118
|
+
* THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the
|
|
119
|
+
* card's own choice, kept apart from `hiddenFields` so a board's "Card fields" never hides the
|
|
120
|
+
* grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty
|
|
121
|
+
* list is a choice (every field shown); absent means the card never chose.
|
|
122
|
+
*/
|
|
123
|
+
cardHidden?: string[];
|
|
124
|
+
/**
|
|
125
|
+
* THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like
|
|
126
|
+
* `widths`: a key it does not name keeps its Field's own place after the named ones, and a key
|
|
127
|
+
* whose Field is gone is skipped — a column order can never hide or invent a column.
|
|
128
|
+
*/
|
|
129
|
+
columnOrder?: string[];
|
|
130
|
+
/**
|
|
131
|
+
* The gallery's card size, remembered the same way the grid remembers its
|
|
132
|
+
* row height — a per-view look, not a global default.
|
|
133
|
+
*/
|
|
134
|
+
gallerySize?: "small" | "medium" | "large";
|
|
135
|
+
/**
|
|
136
|
+
* THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane
|
|
137
|
+
* DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means "whatever the organization
|
|
138
|
+
* says"; present is the view's word. `freezeFirst: false` is an explicit NO over an
|
|
139
|
+
* organization that freezes the first column — an empty `frozen` cannot say that.
|
|
140
|
+
*/
|
|
141
|
+
wrap?: boolean;
|
|
142
|
+
/** NOTION'S "WRAP COLUMN" (N5): the columns that wrap their text when the view as a whole does not. */
|
|
143
|
+
wrapColumns?: string[];
|
|
144
|
+
fit?: "auto" | "fit" | "scroll";
|
|
145
|
+
freezeFirst?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column
|
|
148
|
+
* (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out
|
|
149
|
+
* by `custom.record_aggregate` over the whole table.
|
|
150
|
+
*/
|
|
151
|
+
summaries?: Record<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the
|
|
154
|
+
* pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's
|
|
155
|
+
* way, and the default when a view says nothing), or `inline`, right after the last row with
|
|
156
|
+
* the page scrolling. Read by the Grid and a host's Sheet.
|
|
157
|
+
*/
|
|
158
|
+
footer?: FooterPlacement;
|
|
159
|
+
};
|
|
160
|
+
/** Where a grid's footer sits. */
|
|
161
|
+
type FooterPlacement = "sticky" | "inline";
|
|
162
|
+
|
|
163
|
+
/** The ways one saved view can be looked at (SCR-6; the timeline, lane VIEWS-AND-FIELDS TL). */
|
|
164
|
+
declare const VIEW_LAYOUTS: readonly ["grid", "kanban", "calendar", "gallery", "timeline", "list"];
|
|
165
|
+
type ViewLayout = (typeof VIEW_LAYOUTS)[number];
|
|
166
|
+
/** One sort clause. The Field's key and a direction; nothing else is a sort. */
|
|
167
|
+
interface ViewSort {
|
|
168
|
+
field: string;
|
|
169
|
+
direction: "asc" | "desc";
|
|
170
|
+
}
|
|
171
|
+
/** How a saved view is ordered: by hand, by its sort, or it never said (the same as sorted). */
|
|
172
|
+
type ViewOrder = "manual" | "sorted" | null;
|
|
173
|
+
/**
|
|
174
|
+
* THE DECLARATIVE SPEC. An agent writes this whole object; a person tweaks it.
|
|
175
|
+
* Every builder component in this package takes one of these as a prop.
|
|
176
|
+
*/
|
|
177
|
+
interface SavedViewSpec {
|
|
178
|
+
name: string;
|
|
179
|
+
/** The Table this view looks at. */
|
|
180
|
+
subject: Uuid;
|
|
181
|
+
layout: ViewLayout;
|
|
182
|
+
/** Kanban: the Field whose value becomes a column. */
|
|
183
|
+
groupField?: string | null;
|
|
184
|
+
/**
|
|
185
|
+
* Kanban: a number Field to TOTAL under each column heading. It lives on the VIEW for
|
|
186
|
+
* the same reason the colours do — two people read one board two ways and neither
|
|
187
|
+
* reading is a fact about the records.
|
|
188
|
+
*/
|
|
189
|
+
measure?: string | null;
|
|
190
|
+
/**
|
|
191
|
+
* Kanban: the Field whose values cut the board into SWIMLANES across the columns — a
|
|
192
|
+
* dispatcher's board by Status, one lane per Technician (S1-PRIME VIEW-KEYS). Never the
|
|
193
|
+
* column Field itself; the store refuses that.
|
|
194
|
+
*/
|
|
195
|
+
swimlaneField?: string | null;
|
|
196
|
+
/** Kanban: the columns this view keeps shut to a strip, by the column's stored value. */
|
|
197
|
+
collapsedColumns?: string[];
|
|
198
|
+
/** Calendar: the Field that places a record in the month. */
|
|
199
|
+
dateField?: string | null;
|
|
200
|
+
/** Timeline: the Field a record's bar starts on (`start_field`). */
|
|
201
|
+
startField?: string | null;
|
|
202
|
+
/** Calendar and timeline: the Field a record ends on (`end_field`). */
|
|
203
|
+
endField?: string | null;
|
|
204
|
+
/** Gallery: the Field shown large on the card. */
|
|
205
|
+
imageField?: string | null;
|
|
206
|
+
sorts?: ViewSort[];
|
|
207
|
+
/**
|
|
208
|
+
* HOW THE VIEW IS ORDERED (ORDER-FIX): `"manual"` — the positions a person set by hand ARE the
|
|
209
|
+
* view's sort, and it carries no `sorts`; `"sorted"` or null — by `sorts`. READ ONLY: the store
|
|
210
|
+
* writes "manual" only when the rows are placed (`custom.view_record_order_set`) and turns it
|
|
211
|
+
* off itself when a sort is saved, so no patch ever sends it (`viewSettingsPatch` does not).
|
|
212
|
+
*/
|
|
213
|
+
order?: ViewOrder;
|
|
214
|
+
/**
|
|
215
|
+
* The membership Rule. The STORE decides who is in this view; this is a
|
|
216
|
+
* reference to its Rule record, never a predicate this package evaluates.
|
|
217
|
+
*/
|
|
218
|
+
ruleId?: Uuid | null;
|
|
219
|
+
/**
|
|
220
|
+
* THE FLAT FILTER a digest or a notify rule wrote the view with — the one shape
|
|
221
|
+
* `custom.record_filter_sql` compiles, by Field KEY. A view saved in the bar has
|
|
222
|
+
* none; a view saved by `DigestScheduler` ("where Status is Open") does, and the
|
|
223
|
+
* grid and the board show exactly the records it names.
|
|
224
|
+
*/
|
|
225
|
+
filters?: RecordFilter;
|
|
226
|
+
/**
|
|
227
|
+
* S2-PRIME — THE VIEW'S OWN QUESTION, as the condition builder writes it: the store's Rule
|
|
228
|
+
* shape `{op, args}`, ALL / ANY / NOT groups nested to any depth, Fields by id. Kept in the
|
|
229
|
+
* definition as `where` and asked of the store unchanged — the grid's list door, the board's
|
|
230
|
+
* headings and the dashboard's numbers compile it into the one WHERE clause
|
|
231
|
+
* (`custom.record_filter_sql`). Never stored under `filters`: that key is the flat map the
|
|
232
|
+
* digests and the notifier read, and they would refuse it by name.
|
|
233
|
+
*/
|
|
234
|
+
where?: RuleExpression | null;
|
|
235
|
+
isDefault?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* HOW THIS VIEW LOOKS — its colors, its per-column formats and its frozen
|
|
238
|
+
* column (`presentation.ts`).
|
|
239
|
+
*
|
|
240
|
+
* It lives on the VIEW and not on the Fields because two people read one
|
|
241
|
+
* table two ways and neither reading is a fact about the records; it lives on
|
|
242
|
+
* a RECORD, not a new column, because that is what makes it something an
|
|
243
|
+
* agent writes with the same `records` tool as everything else.
|
|
244
|
+
*/
|
|
245
|
+
presentation?: GridPresentation;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A saved view as it comes back out of the store, with its row's own id.
|
|
249
|
+
*
|
|
250
|
+
* It carries no version: `custom.view_declare` MERGES one key at a time, so two
|
|
251
|
+
* tabs pressing two different controls on one view both stand, and one control
|
|
252
|
+
* pressed in both keeps the latest — how Airtable and Notion keep a view change.
|
|
253
|
+
*/
|
|
254
|
+
interface SavedView extends SavedViewSpec {
|
|
255
|
+
id: Uuid;
|
|
256
|
+
/** Where the view came from when it was moved in (the mover, the S0 repair), else null. */
|
|
257
|
+
movedFrom: Record<string, unknown> | null;
|
|
258
|
+
}
|
|
6
259
|
|
|
7
260
|
/** The table page's own page size (`TablePage`: 100 unless embedded). */
|
|
8
261
|
declare const TABLE_PAGE_SIZE = 100;
|
|
262
|
+
/**
|
|
263
|
+
* HOW MANY ROWS AN EMBEDDED TABLE READS AT A TIME (CHAIR-EMBEDDED-FIDELITY 16): its first page, and
|
|
264
|
+
* each "Show more" press brings about this many more. `embeddedLook.tsx` re-exports it.
|
|
265
|
+
*/
|
|
266
|
+
declare const EMBEDDED_PAGE_SIZE = 50;
|
|
9
267
|
interface FirstGridPageArgs {
|
|
10
268
|
p_table_id: Uuid;
|
|
11
269
|
p_filter: Record<string, never>;
|
|
@@ -31,5 +289,32 @@ declare function firstGridPageArgs(input: {
|
|
|
31
289
|
declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
|
|
32
290
|
pageSize?: number;
|
|
33
291
|
}): Promise<FirstGridPageArgs | null>;
|
|
292
|
+
/**
|
|
293
|
+
* A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
|
|
294
|
+
*
|
|
295
|
+
* Asks what the table page opens on — the table's bundle (`custom.table_page_bundle`, through the
|
|
296
|
+
* records client exactly as the browser would), the grid's first page when `firstGridPageRequest`
|
|
297
|
+
* can predict it, and on a record page the record's bundle — through a recording data source, and
|
|
298
|
+
* answers every door answer it got as a `RecordsSeed`. The host hands that to
|
|
299
|
+
* `<RecordsSeedProvider>` (`@ai-matrx/records/react`) above the table page: the page is drawn with
|
|
300
|
+
* its rows in the server's HTML and hydrates onto them without asking again.
|
|
301
|
+
*
|
|
302
|
+
* `dataSource` is the person's own (a server supabase client carrying her session): every door
|
|
303
|
+
* decides exactly as it does from the browser. Never throws; a door that refuses is simply not in
|
|
304
|
+
* the seed, and the page asks it for itself.
|
|
305
|
+
*/
|
|
306
|
+
declare function askTablePageSeed(input: {
|
|
307
|
+
dataSource: unknown;
|
|
308
|
+
organizationId: Uuid;
|
|
309
|
+
tableId: Uuid;
|
|
310
|
+
actor: RecordsConfig["actor"];
|
|
311
|
+
/** A record page: its record's bundle (`custom.record_page_bundle`) is asked beside the table's. */
|
|
312
|
+
recordId?: Uuid | null;
|
|
313
|
+
/** Ask the grid's first page (default true). Off when the address opens something else. */
|
|
314
|
+
rows?: boolean;
|
|
315
|
+
/** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
|
|
316
|
+
embedded?: boolean;
|
|
317
|
+
pageSize?: number;
|
|
318
|
+
}): Promise<RecordsSeed>;
|
|
34
319
|
|
|
35
|
-
export { type FirstGridPageArgs, TABLE_PAGE_SIZE, firstGridPageArgs, firstGridPageRequest };
|
|
320
|
+
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, askTablePageSeed, firstGridPageArgs, firstGridPageRequest };
|
package/dist/first-page.d.ts
CHANGED
|
@@ -1,11 +1,269 @@
|
|
|
1
|
-
import { Uuid, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
2
|
-
import { RecordsClient } from '@ai-matrx/records/core';
|
|
3
|
-
import {
|
|
4
|
-
import '@ai-matrx/design-system/
|
|
5
|
-
|
|
1
|
+
import { Uuid, RecordFilter, RuleExpression, RecordPageSort, RecordsConfig, Table, Field } from '@ai-matrx/records';
|
|
2
|
+
import { RecordsSeed, RecordsClient } from '@ai-matrx/records/core';
|
|
3
|
+
import { TableStyle, GroupAggregateSpec, GroupOrder } from '@ai-matrx/design-system/data-table';
|
|
4
|
+
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* HOW A TABLE LOOKS — colors, formats and the frozen column.
|
|
8
|
+
*
|
|
9
|
+
* THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a
|
|
10
|
+
* currency, a percent, a date, a person. A VIEW declares HOW that reads and
|
|
11
|
+
* what it is worth looking at: two decimal places or none, tint the row when
|
|
12
|
+
* Status is Blocked, keep Name in place while the rest scrolls. Airtable draws
|
|
13
|
+
* the line in exactly this place, and it has to be here: two people read one
|
|
14
|
+
* table two different ways, and neither reading is a fact about the records.
|
|
15
|
+
*
|
|
16
|
+
* So nothing in this module is a new column on a Field and nothing here needed
|
|
17
|
+
* a migration. A presentation lives on the saved-view record (`views.ts`), which
|
|
18
|
+
* is an ordinary record of an ordinary Table — which means an agent sets it
|
|
19
|
+
* through the same `records` tool it uses for everything else, and it travels
|
|
20
|
+
* with the table the way every other record does.
|
|
21
|
+
*
|
|
22
|
+
* A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`
|
|
23
|
+
* reads the Field's OWN declaration — a `currency` parity type with unit `$`, a
|
|
24
|
+
* `percent`, a `datetime` with `config.kind` — and returns the format that
|
|
25
|
+
* follows from it. The view's `formats` map only OVERRIDES that. A table nobody
|
|
26
|
+
* has ever styled still shows `$1,250.50` and `45%`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SECTIONS BY A FIELD — the view's own grouping.
|
|
31
|
+
*
|
|
32
|
+
* `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,
|
|
33
|
+
* because a view is written against the Table's declaration and never against
|
|
34
|
+
* whatever id a screen happened to build its columns with.
|
|
35
|
+
*
|
|
36
|
+
* `aggregates` is keyed by Field key too, and `collapsed` holds the data
|
|
37
|
+
* table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the
|
|
38
|
+
* primitive emits and the only thing that survives a value rendering the same
|
|
39
|
+
* text as another.
|
|
40
|
+
*
|
|
41
|
+
* NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every
|
|
42
|
+
* object in JavaScript already has all three, so an optional field by one of
|
|
43
|
+
* those names is NEVER absent and a `config.valueOf ? … : …` check silently
|
|
44
|
+
* reads `Object.prototype`. It cost the primitive a whole grouping once.
|
|
45
|
+
*/
|
|
46
|
+
type GridGrouping = {
|
|
47
|
+
/** The Field key whose value makes the sections. */
|
|
48
|
+
field: string;
|
|
49
|
+
/** Per-Field-key subtotal on every section header. */
|
|
50
|
+
aggregates?: Record<string, GroupAggregateSpec>;
|
|
51
|
+
/** Section order. The primitive's default is `value-asc`; blanks sort last. */
|
|
52
|
+
order?: GroupOrder;
|
|
53
|
+
/** The sections this view keeps shut, by the primitive's own group key. */
|
|
54
|
+
collapsed?: string[];
|
|
55
|
+
/** The grouped column drawn as a column too. Absent = it steps aside (the headers name its value). */
|
|
56
|
+
showColumn?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS "sub-grouping"): up to two more levels
|
|
59
|
+
* inside each section, by Field key. At most three levels in all — the store refuses a fourth
|
|
60
|
+
* (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,
|
|
61
|
+
* because this list is parsed and written back whole with the rest of the look.
|
|
62
|
+
*/
|
|
63
|
+
then?: GridSubGroup[];
|
|
64
|
+
/**
|
|
65
|
+
* DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or
|
|
66
|
+
* `<key>:<grain>` for a date cut into periods.
|
|
67
|
+
*/
|
|
68
|
+
across?: string;
|
|
69
|
+
/** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */
|
|
70
|
+
grain?: string;
|
|
71
|
+
/** The Measures shown ("what to display"), by the store's own keys (`count`, `sum_quantity`). */
|
|
72
|
+
show?: string[];
|
|
73
|
+
/** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */
|
|
74
|
+
trail?: GridDrillStep[];
|
|
75
|
+
};
|
|
76
|
+
/** One crumb of a saved drill trail. `value: null` is "no value". */
|
|
77
|
+
type GridDrillStep = {
|
|
78
|
+
dim: string;
|
|
79
|
+
value: string | null;
|
|
80
|
+
};
|
|
81
|
+
/** One sub-group level: a Field key and its section order. */
|
|
82
|
+
type GridSubGroup = {
|
|
83
|
+
field: string;
|
|
84
|
+
order?: GroupOrder;
|
|
85
|
+
grain?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Everything a view says about how its table LOOKS. */
|
|
88
|
+
type GridPresentation = {
|
|
89
|
+
/** Color-by-a-column, rules and manual highlights. */
|
|
90
|
+
style?: TableStyle;
|
|
91
|
+
/** Per-column format overrides, by the Field's key. */
|
|
92
|
+
formats?: Record<string, FieldFormatConfig>;
|
|
93
|
+
/**
|
|
94
|
+
* The columns that stay in place while the table scrolls sideways, by Field
|
|
95
|
+
* key. Only the LEADING run of the view's column order can actually freeze —
|
|
96
|
+
* the data table decides that, and declines rather than pinning a column at a
|
|
97
|
+
* guessed offset.
|
|
98
|
+
*/
|
|
99
|
+
frozen?: string[];
|
|
100
|
+
/** Collapsible sections by one Field, with subtotals on each header. */
|
|
101
|
+
grouping?: GridGrouping;
|
|
102
|
+
/**
|
|
103
|
+
* The width a person dragged a column to, by Field key. SPARSE on purpose —
|
|
104
|
+
* a view saved before widths existed carries none and still parses, and a
|
|
105
|
+
* column nobody resized is left to the table's own measurement.
|
|
106
|
+
*/
|
|
107
|
+
widths?: Record<string, number>;
|
|
108
|
+
/** The body row height this view remembers, in px. */
|
|
109
|
+
rowHeight?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Field keys hidden from this view — the SAME hidden-column list the grid's
|
|
112
|
+
* own column picker holds, now given a place on the view so it survives a
|
|
113
|
+
* reload and a layout switch reads the identical set gallery and grid agree
|
|
114
|
+
* on, rather than a second "fields shown" list nobody kept in sync.
|
|
115
|
+
*/
|
|
116
|
+
hiddenFields?: string[];
|
|
117
|
+
/**
|
|
118
|
+
* THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the
|
|
119
|
+
* card's own choice, kept apart from `hiddenFields` so a board's "Card fields" never hides the
|
|
120
|
+
* grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty
|
|
121
|
+
* list is a choice (every field shown); absent means the card never chose.
|
|
122
|
+
*/
|
|
123
|
+
cardHidden?: string[];
|
|
124
|
+
/**
|
|
125
|
+
* THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like
|
|
126
|
+
* `widths`: a key it does not name keeps its Field's own place after the named ones, and a key
|
|
127
|
+
* whose Field is gone is skipped — a column order can never hide or invent a column.
|
|
128
|
+
*/
|
|
129
|
+
columnOrder?: string[];
|
|
130
|
+
/**
|
|
131
|
+
* The gallery's card size, remembered the same way the grid remembers its
|
|
132
|
+
* row height — a per-view look, not a global default.
|
|
133
|
+
*/
|
|
134
|
+
gallerySize?: "small" | "medium" | "large";
|
|
135
|
+
/**
|
|
136
|
+
* THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane
|
|
137
|
+
* DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means "whatever the organization
|
|
138
|
+
* says"; present is the view's word. `freezeFirst: false` is an explicit NO over an
|
|
139
|
+
* organization that freezes the first column — an empty `frozen` cannot say that.
|
|
140
|
+
*/
|
|
141
|
+
wrap?: boolean;
|
|
142
|
+
/** NOTION'S "WRAP COLUMN" (N5): the columns that wrap their text when the view as a whole does not. */
|
|
143
|
+
wrapColumns?: string[];
|
|
144
|
+
fit?: "auto" | "fit" | "scroll";
|
|
145
|
+
freezeFirst?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column
|
|
148
|
+
* (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out
|
|
149
|
+
* by `custom.record_aggregate` over the whole table.
|
|
150
|
+
*/
|
|
151
|
+
summaries?: Record<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the
|
|
154
|
+
* pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's
|
|
155
|
+
* way, and the default when a view says nothing), or `inline`, right after the last row with
|
|
156
|
+
* the page scrolling. Read by the Grid and a host's Sheet.
|
|
157
|
+
*/
|
|
158
|
+
footer?: FooterPlacement;
|
|
159
|
+
};
|
|
160
|
+
/** Where a grid's footer sits. */
|
|
161
|
+
type FooterPlacement = "sticky" | "inline";
|
|
162
|
+
|
|
163
|
+
/** The ways one saved view can be looked at (SCR-6; the timeline, lane VIEWS-AND-FIELDS TL). */
|
|
164
|
+
declare const VIEW_LAYOUTS: readonly ["grid", "kanban", "calendar", "gallery", "timeline", "list"];
|
|
165
|
+
type ViewLayout = (typeof VIEW_LAYOUTS)[number];
|
|
166
|
+
/** One sort clause. The Field's key and a direction; nothing else is a sort. */
|
|
167
|
+
interface ViewSort {
|
|
168
|
+
field: string;
|
|
169
|
+
direction: "asc" | "desc";
|
|
170
|
+
}
|
|
171
|
+
/** How a saved view is ordered: by hand, by its sort, or it never said (the same as sorted). */
|
|
172
|
+
type ViewOrder = "manual" | "sorted" | null;
|
|
173
|
+
/**
|
|
174
|
+
* THE DECLARATIVE SPEC. An agent writes this whole object; a person tweaks it.
|
|
175
|
+
* Every builder component in this package takes one of these as a prop.
|
|
176
|
+
*/
|
|
177
|
+
interface SavedViewSpec {
|
|
178
|
+
name: string;
|
|
179
|
+
/** The Table this view looks at. */
|
|
180
|
+
subject: Uuid;
|
|
181
|
+
layout: ViewLayout;
|
|
182
|
+
/** Kanban: the Field whose value becomes a column. */
|
|
183
|
+
groupField?: string | null;
|
|
184
|
+
/**
|
|
185
|
+
* Kanban: a number Field to TOTAL under each column heading. It lives on the VIEW for
|
|
186
|
+
* the same reason the colours do — two people read one board two ways and neither
|
|
187
|
+
* reading is a fact about the records.
|
|
188
|
+
*/
|
|
189
|
+
measure?: string | null;
|
|
190
|
+
/**
|
|
191
|
+
* Kanban: the Field whose values cut the board into SWIMLANES across the columns — a
|
|
192
|
+
* dispatcher's board by Status, one lane per Technician (S1-PRIME VIEW-KEYS). Never the
|
|
193
|
+
* column Field itself; the store refuses that.
|
|
194
|
+
*/
|
|
195
|
+
swimlaneField?: string | null;
|
|
196
|
+
/** Kanban: the columns this view keeps shut to a strip, by the column's stored value. */
|
|
197
|
+
collapsedColumns?: string[];
|
|
198
|
+
/** Calendar: the Field that places a record in the month. */
|
|
199
|
+
dateField?: string | null;
|
|
200
|
+
/** Timeline: the Field a record's bar starts on (`start_field`). */
|
|
201
|
+
startField?: string | null;
|
|
202
|
+
/** Calendar and timeline: the Field a record ends on (`end_field`). */
|
|
203
|
+
endField?: string | null;
|
|
204
|
+
/** Gallery: the Field shown large on the card. */
|
|
205
|
+
imageField?: string | null;
|
|
206
|
+
sorts?: ViewSort[];
|
|
207
|
+
/**
|
|
208
|
+
* HOW THE VIEW IS ORDERED (ORDER-FIX): `"manual"` — the positions a person set by hand ARE the
|
|
209
|
+
* view's sort, and it carries no `sorts`; `"sorted"` or null — by `sorts`. READ ONLY: the store
|
|
210
|
+
* writes "manual" only when the rows are placed (`custom.view_record_order_set`) and turns it
|
|
211
|
+
* off itself when a sort is saved, so no patch ever sends it (`viewSettingsPatch` does not).
|
|
212
|
+
*/
|
|
213
|
+
order?: ViewOrder;
|
|
214
|
+
/**
|
|
215
|
+
* The membership Rule. The STORE decides who is in this view; this is a
|
|
216
|
+
* reference to its Rule record, never a predicate this package evaluates.
|
|
217
|
+
*/
|
|
218
|
+
ruleId?: Uuid | null;
|
|
219
|
+
/**
|
|
220
|
+
* THE FLAT FILTER a digest or a notify rule wrote the view with — the one shape
|
|
221
|
+
* `custom.record_filter_sql` compiles, by Field KEY. A view saved in the bar has
|
|
222
|
+
* none; a view saved by `DigestScheduler` ("where Status is Open") does, and the
|
|
223
|
+
* grid and the board show exactly the records it names.
|
|
224
|
+
*/
|
|
225
|
+
filters?: RecordFilter;
|
|
226
|
+
/**
|
|
227
|
+
* S2-PRIME — THE VIEW'S OWN QUESTION, as the condition builder writes it: the store's Rule
|
|
228
|
+
* shape `{op, args}`, ALL / ANY / NOT groups nested to any depth, Fields by id. Kept in the
|
|
229
|
+
* definition as `where` and asked of the store unchanged — the grid's list door, the board's
|
|
230
|
+
* headings and the dashboard's numbers compile it into the one WHERE clause
|
|
231
|
+
* (`custom.record_filter_sql`). Never stored under `filters`: that key is the flat map the
|
|
232
|
+
* digests and the notifier read, and they would refuse it by name.
|
|
233
|
+
*/
|
|
234
|
+
where?: RuleExpression | null;
|
|
235
|
+
isDefault?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* HOW THIS VIEW LOOKS — its colors, its per-column formats and its frozen
|
|
238
|
+
* column (`presentation.ts`).
|
|
239
|
+
*
|
|
240
|
+
* It lives on the VIEW and not on the Fields because two people read one
|
|
241
|
+
* table two ways and neither reading is a fact about the records; it lives on
|
|
242
|
+
* a RECORD, not a new column, because that is what makes it something an
|
|
243
|
+
* agent writes with the same `records` tool as everything else.
|
|
244
|
+
*/
|
|
245
|
+
presentation?: GridPresentation;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A saved view as it comes back out of the store, with its row's own id.
|
|
249
|
+
*
|
|
250
|
+
* It carries no version: `custom.view_declare` MERGES one key at a time, so two
|
|
251
|
+
* tabs pressing two different controls on one view both stand, and one control
|
|
252
|
+
* pressed in both keeps the latest — how Airtable and Notion keep a view change.
|
|
253
|
+
*/
|
|
254
|
+
interface SavedView extends SavedViewSpec {
|
|
255
|
+
id: Uuid;
|
|
256
|
+
/** Where the view came from when it was moved in (the mover, the S0 repair), else null. */
|
|
257
|
+
movedFrom: Record<string, unknown> | null;
|
|
258
|
+
}
|
|
6
259
|
|
|
7
260
|
/** The table page's own page size (`TablePage`: 100 unless embedded). */
|
|
8
261
|
declare const TABLE_PAGE_SIZE = 100;
|
|
262
|
+
/**
|
|
263
|
+
* HOW MANY ROWS AN EMBEDDED TABLE READS AT A TIME (CHAIR-EMBEDDED-FIDELITY 16): its first page, and
|
|
264
|
+
* each "Show more" press brings about this many more. `embeddedLook.tsx` re-exports it.
|
|
265
|
+
*/
|
|
266
|
+
declare const EMBEDDED_PAGE_SIZE = 50;
|
|
9
267
|
interface FirstGridPageArgs {
|
|
10
268
|
p_table_id: Uuid;
|
|
11
269
|
p_filter: Record<string, never>;
|
|
@@ -31,5 +289,32 @@ declare function firstGridPageArgs(input: {
|
|
|
31
289
|
declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
|
|
32
290
|
pageSize?: number;
|
|
33
291
|
}): Promise<FirstGridPageArgs | null>;
|
|
292
|
+
/**
|
|
293
|
+
* A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
|
|
294
|
+
*
|
|
295
|
+
* Asks what the table page opens on — the table's bundle (`custom.table_page_bundle`, through the
|
|
296
|
+
* records client exactly as the browser would), the grid's first page when `firstGridPageRequest`
|
|
297
|
+
* can predict it, and on a record page the record's bundle — through a recording data source, and
|
|
298
|
+
* answers every door answer it got as a `RecordsSeed`. The host hands that to
|
|
299
|
+
* `<RecordsSeedProvider>` (`@ai-matrx/records/react`) above the table page: the page is drawn with
|
|
300
|
+
* its rows in the server's HTML and hydrates onto them without asking again.
|
|
301
|
+
*
|
|
302
|
+
* `dataSource` is the person's own (a server supabase client carrying her session): every door
|
|
303
|
+
* decides exactly as it does from the browser. Never throws; a door that refuses is simply not in
|
|
304
|
+
* the seed, and the page asks it for itself.
|
|
305
|
+
*/
|
|
306
|
+
declare function askTablePageSeed(input: {
|
|
307
|
+
dataSource: unknown;
|
|
308
|
+
organizationId: Uuid;
|
|
309
|
+
tableId: Uuid;
|
|
310
|
+
actor: RecordsConfig["actor"];
|
|
311
|
+
/** A record page: its record's bundle (`custom.record_page_bundle`) is asked beside the table's. */
|
|
312
|
+
recordId?: Uuid | null;
|
|
313
|
+
/** Ask the grid's first page (default true). Off when the address opens something else. */
|
|
314
|
+
rows?: boolean;
|
|
315
|
+
/** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
|
|
316
|
+
embedded?: boolean;
|
|
317
|
+
pageSize?: number;
|
|
318
|
+
}): Promise<RecordsSeed>;
|
|
34
319
|
|
|
35
|
-
export { type FirstGridPageArgs, TABLE_PAGE_SIZE, firstGridPageArgs, firstGridPageRequest };
|
|
320
|
+
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, askTablePageSeed, firstGridPageArgs, firstGridPageRequest };
|