@ai-matrx/records-ui 0.115.70 → 0.115.71
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 +5 -0
- package/dist/first-page.cjs +0 -1
- package/dist/first-page.cjs.map +1 -1
- package/dist/first-page.js +0 -1
- package/dist/first-page.js.map +1 -1
- package/dist/helpers.cjs +618 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.d.cts +1258 -0
- package/dist/helpers.d.ts +1258 -0
- package/dist/helpers.js +559 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.cjs +309 -285
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +182 -164
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +94 -72
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +69 -53
- package/dist/pickers.js.map +1 -1
- package/package.json +15 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.115.71
|
|
4
|
+
### Unreleased
|
|
5
|
+
|
|
6
|
+
- **`@ai-matrx/records-ui/helpers` — the light door (page-weight campaign, 2026-10-13).** The root entry is one "use client" bundle of every screen (~2.4 MB, the grid and recharts with it), and a host service that imported one helper from it put the whole package on every page. `/helpers` (~25 KB, no React, no "use client", shaken) serves `personActor`, `recordsDataSource`, `setPersonTimeZone` / `getPersonTimeZone` / `personWallNow` / `subscribePersonTimeZone`, `tableRightsAt`, `NO_RIGHTS`, the names (`tableName`, `fieldName`, `recordName`, …), `whenWords` and the other who-changed words, `withheldNoticeOf` / `withheldSentence` / `WITHHELD_WORD`, `fieldTypeLabel` and the parity helpers, `tablePickerEntries` / `isValueSet` / `isKeptTable`, `declareTable` and the table builders, `resolveTableStyle` / `styleFromDecorations`, and the host contract's types (`RecordsUiHost`, `OpenRecordsAsk`, `AgentBuildAsk`, `TableRights`, `PickerTable`, `NewFieldSpec`, …). The root re-exports every name (new: `subscribePersonTimeZone`). The person's zone and the one-data-source-per-client cache moved into realm-wide `globalSlot`s, so `/helpers` and the root share ONE instance though each bundles its own copy of the code. The pure halves were split out of their screens: `dataSource.ts` (from `RecordsMount.tsx`), `personZone.ts` (from `personTime.ts`), `tableRights.ts` (from `host.tsx`), `withheldNotice.ts` (from `withheld.tsx`), `tablePickerEntries.ts` (from `tablePicking.tsx`); `choiceLooks.ts` / `presentation.ts` read their colour values from design-system's pure `data-table/table-style` instead of the data-table root. Guard: `src/helpers-entry-is-light.test.tsx` (the helper graph reaches no React, screen or heavy entry; every helper is also a root export; the zone and the cache are shared by a second copy of the code — red with a module-level `let`). Consumer action: a module that imports only these helpers from the root imports them from `/helpers`.
|
|
7
|
+
|
|
3
8
|
## 0.115.70
|
|
4
9
|
|
|
5
10
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
package/dist/first-page.cjs
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
var firstPage = require('@ai-matrx/records/first-page');
|
|
4
4
|
var base = require('@ai-matrx/records/views');
|
|
5
5
|
var tableStyle = require('@ai-matrx/design-system/data-table/table-style');
|
|
6
|
-
require('@ai-matrx/design-system/data-table');
|
|
7
6
|
require('@ai-matrx/design-system/field-formats');
|
|
8
7
|
require('@ai-matrx/records');
|
|
9
8
|
|
package/dist/first-page.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/presentation.ts","../src/viewKeys.ts","../src/firstPage.ts"],"names":["parseTableStyle","tableStyleIsEmpty","raw","base","bindFirstPageCodec"],"mappings":"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","file":"first-page.cjs","sourcesContent":["/**\n * The Gantt mode of a timeline view, by Field key. `mode` turns the left task list on; `critical` paints the longest\n * chain; `progress` is a percent/number column; `milestone` a yes/no column; `baselineStart` / `baselineEnd` the two\n * date columns \"Save baseline\" writes; `dependency` the link column that dependencies are written to.\n */\nexport interface GanttLook {\n mode?: boolean;\n critical?: boolean;\n progress?: string;\n milestone?: string;\n baselineStart?: string;\n baselineEnd?: string;\n dependency?: string;\n}\n\n/** Read a stored Gantt look; unknown keys and wrong shapes are dropped. */\nexport function parseGanttLook(raw: unknown): GanttLook | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const v = raw as Record<string, unknown>;\n const out: GanttLook = {};\n if (v[\"mode\"] === true) out.mode = true;\n if (v[\"critical\"] === true) out.critical = true;\n for (const k of [\"progress\", \"milestone\", \"baselineStart\", \"baselineEnd\", \"dependency\"] as const) {\n const x = v[k];\n if (typeof x === \"string\" && x !== \"\") out[k] = x;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * HOW A TABLE LOOKS — colors, formats and the frozen column.\n *\n * THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a\n * currency, a percent, a date, a person. A VIEW declares HOW that reads and\n * what it is worth looking at: two decimal places or none, tint the row when\n * Status is Blocked, keep Name in place while the rest scrolls. Airtable draws\n * the line in exactly this place, and it has to be here: two people read one\n * table two different ways, and neither reading is a fact about the records.\n *\n * So nothing in this module is a new column on a Field and nothing here needed\n * a migration. A presentation lives on the saved-view record (`views.ts`), which\n * is an ordinary record of an ordinary Table — which means an agent sets it\n * through the same `records` tool it uses for everything else, and it travels\n * with the table the way every other record does.\n *\n * A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`\n * reads the Field's OWN declaration — a `currency` parity type with unit `$`, a\n * `percent`, a `datetime` with `config.kind` — and returns the format that\n * follows from it. The view's `formats` map only OVERRIDES that. A table nobody\n * has ever styled still shows `$1,250.50` and `45%`.\n */\n\n// The style parser from its own client-free subpath (lane SSR-ROWS): `@ai-matrx/records-ui/first-page`\n// runs in a host's SERVER component, and every export of the \"use client\" `data-table` entry is a\n// client reference there — calling `parseTableStyle` through it threw on the server.\nimport { parseTableStyle, tableStyleIsEmpty } from \"@ai-matrx/design-system/data-table/table-style\";\nimport {\n COLOR_ROTATION,\n type ChoiceColorLookup,\n type GroupAggregateKind,\n type GroupAggregateSpec,\n type GroupOrder,\n type StyleColor,\n type TableStyle,\n} from \"@ai-matrx/design-system/data-table\";\nimport { getFieldFormat, type FieldFormatConfig } from \"@ai-matrx/design-system/field-formats\";\nimport type { ColorRule } from \"@ai-matrx/design-system/data-table\";\nimport { newerKindOf, ratingMaxOf, type Field, type TableDecorations } from \"@ai-matrx/records\";\n\nimport { editorKindFor } from \"./parity\";\nimport { dateFormatFor } from \"./dates\";\n\n/**\n * SECTIONS BY A FIELD — the view's own grouping.\n *\n * `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,\n * because a view is written against the Table's declaration and never against\n * whatever id a screen happened to build its columns with.\n *\n * `aggregates` is keyed by Field key too, and `collapsed` holds the data\n * table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the\n * primitive emits and the only thing that survives a value rendering the same\n * text as another.\n *\n * NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every\n * object in JavaScript already has all three, so an optional field by one of\n * those names is NEVER absent and a `config.valueOf ? … : …` check silently\n * reads `Object.prototype`. It cost the primitive a whole grouping once.\n */\nexport type GridGrouping = {\n /** The Field key whose value makes the sections. */\n field: string;\n /** Per-Field-key subtotal on every section header. */\n aggregates?: Record<string, GroupAggregateSpec>;\n /** Section order. The primitive's default is `value-asc`; blanks sort last. */\n order?: GroupOrder;\n /** The sections this view keeps shut, by the primitive's own group key. */\n collapsed?: string[];\n /** The grouped column stays drawn as a column (Airtable). Absent = shown; `false` = the person stepped it aside. */\n showColumn?: boolean;\n /**\n * SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS \"sub-grouping\"): up to two more levels\n * inside each section, by Field key. At most three levels in all — the store refuses a fourth\n * (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,\n * because this list is parsed and written back whole with the rest of the look.\n */\n then?: GridSubGroup[];\n /**\n * DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or\n * `<key>:<grain>` for a date cut into periods.\n */\n across?: string;\n /** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */\n grain?: string;\n /** The Measures shown (\"what to display\"), by the store's own keys (`count`, `sum_quantity`). */\n show?: string[];\n /** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */\n trail?: GridDrillStep[];\n};\n\n/** One crumb of a saved drill trail. `value: null` is \"no value\". */\nexport type GridDrillStep = { dim: string; value: string | null };\n\n/** One sub-group level: a Field key and its section order. */\nexport type GridSubGroup = { field: string; order?: GroupOrder; grain?: string };\n\n/** Everything a view says about how its table LOOKS. */\nexport type GridPresentation = {\n /** Color-by-a-column, rules and manual highlights. */\n style?: TableStyle;\n /** Per-column format overrides, by the Field's key. */\n formats?: Record<string, FieldFormatConfig>;\n /**\n * The columns that stay in place while the table scrolls sideways, by Field\n * key. Only the LEADING run of the view's column order can actually freeze —\n * the data table decides that, and declines rather than pinning a column at a\n * guessed offset.\n */\n frozen?: string[];\n /** Collapsible sections by one Field, with subtotals on each header. */\n grouping?: GridGrouping;\n /**\n * The width a person dragged a column to, by Field key. SPARSE on purpose —\n * a view saved before widths existed carries none and still parses, and a\n * column nobody resized is left to the table's own measurement.\n */\n widths?: Record<string, number>;\n /** The body row height this view remembers, in px. */\n rowHeight?: number;\n /**\n * Field keys hidden from this view — the SAME hidden-column list the grid's\n * own column picker holds, now given a place on the view so it survives a\n * reload and a layout switch reads the identical set gallery and grid agree\n * on, rather than a second \"fields shown\" list nobody kept in sync.\n */\n hiddenFields?: string[];\n /**\n * THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the\n * card's own choice, kept apart from `hiddenFields` so a board's \"Card fields\" never hides the\n * grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty\n * list is a choice (every field shown); absent means the card never chose.\n */\n cardHidden?: string[];\n /**\n * THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like\n * `widths`: a key it does not name keeps its Field's own place after the named ones, and a key\n * whose Field is gone is skipped — a column order can never hide or invent a column.\n */\n columnOrder?: string[];\n /**\n * The gallery's card size, remembered the same way the grid remembers its\n * row height — a per-view look, not a global default.\n */\n gallerySize?: \"small\" | \"medium\" | \"large\";\n /**\n * THE BOARD'S OWN LOOK (lane BOARD-SWIMLANES, Notion's board options). All kept on the view, all\n * read only by the kanban layout. Absent means the board's default: columns wear their choice's\n * colour, each heading shows its count, empty columns and lanes are drawn, cards are medium with\n * no preview.\n * boardSize card size: sets the column width, the preview's height and, small, drops the card's extras\n * boardPreview the FILE field drawn as a cover at the top of each card (absent = no preview)\n * boardHideEmpty leave out a column or a lane that holds no card\n * boardColorColumns false = neutral headings, no accent bar on the cards\n * boardCounts false = no count beside the heading\n * boardLanesShut the swimlanes kept shut to a strip, by the lane's name\n */\n boardSize?: \"small\" | \"medium\" | \"large\";\n boardPreview?: string;\n boardHideEmpty?: boolean;\n boardColorColumns?: boolean;\n boardCounts?: boolean;\n boardLanesShut?: string[];\n /**\n * A LOCKED VIEW (lane NOTION-SMALL-2): its layout, filters, sorts and settings are not saved over until\n * somebody unlocks it. An editing convenience against an accidental change, never a permission.\n */\n locked?: boolean;\n /**\n * THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane\n * DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means \"whatever the organization\n * says\"; present is the view's word. `freezeFirst: false` is an explicit NO over an\n * organization that freezes the first column — an empty `frozen` cannot say that.\n */\n wrap?: boolean;\n /** NOTION'S \"WRAP COLUMN\" (N5): the columns that wrap their text when the view as a whole does not. */\n wrapColumns?: string[];\n fit?: \"auto\" | \"fit\" | \"scroll\";\n freezeFirst?: boolean;\n /**\n * THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column\n * (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out\n * by `custom.record_aggregate` over the whole table.\n */\n summaries?: Record<string, string>;\n /** THE TIMELINE'S GANTT MODE (kept as `presentation.gantt`): see `GanttLook`. */\n gantt?: GanttLook;\n /**\n * WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the\n * pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's\n * way, and the default when a view says nothing), or `inline`, right after the last row with\n * the page scrolling. Read by the Grid and a host's Sheet.\n */\n footer?: FooterPlacement;\n};\n\n/** Where a grid's footer sits. */\nexport type FooterPlacement = \"sticky\" | \"inline\";\n\n/** What a view that says nothing gets — the older grid's pinned footer. */\nexport const DEFAULT_FOOTER: FooterPlacement = \"sticky\";\n\n/** Where THIS view's footer sits, said or defaulted. */\nexport function footerOf(p: GridPresentation | null | undefined): FooterPlacement {\n return p?.footer ?? DEFAULT_FOOTER;\n}\n\nexport const EMPTY_PRESENTATION: GridPresentation = {};\n\n/** A frozen column needs a known width, or the table declines to freeze at all. */\nexport const FROZEN_COLUMN_WIDTH = 200;\n\n/**\n * WHAT A ROW HEIGHT IS ALLOWED TO BE. Below the floor the text is clipped and\n * no editor fits; above the ceiling one record fills the screen. A view asking\n * for anything outside it is DROPPED rather than obeyed, because a saved look\n * must not be able to make a table unreadable — and the Fields' own heights\n * still draw, which is the tolerant half.\n */\nexport const MIN_ROW_HEIGHT = 24;\nexport const MAX_ROW_HEIGHT = 240;\n\n/**\n * The aggregate kinds and orders the primitive actually implements. Listed here\n * because they are the tolerant parser's allow-list and a view is hostile input:\n * `satisfies` ties them to the primitive's own union, so a kind it renames stops\n * this file compiling instead of quietly parsing into a subtotal nobody draws.\n */\nconst AGGREGATE_KINDS = [\n \"count\",\n \"sum\",\n \"average\",\n \"min\",\n \"max\",\n \"empty\",\n \"filled\",\n \"unique\",\n \"checked\",\n \"unchecked\",\n \"percentFilled\",\n] as const satisfies readonly GroupAggregateKind[];\n\nconst GROUP_ORDERS = [\n \"value-asc\",\n \"value-desc\",\n \"count-desc\",\n \"first-seen\",\n] as const satisfies readonly GroupOrder[];\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Read a presentation out of whatever the view record holds — an object, or the\n * JSON string a text field stores.\n *\n * Tolerant in exactly the way `parseTableStyle` is: an unreadable half is\n * dropped and the rest still applies, because a table must keep drawing when\n * one piece of its styling goes out of date.\n */\nexport function parseGridPresentation(raw: unknown): GridPresentation {\n let value = raw;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return {};\n try {\n value = JSON.parse(trimmed);\n } catch {\n return {};\n }\n }\n if (!isRecord(value)) return {};\n\n const out: GridPresentation = {};\n\n const style = parseTableStyle(value[\"style\"]);\n if (!tableStyleIsEmpty(style)) out.style = style;\n\n if (isRecord(value[\"formats\"])) {\n const formats: Record<string, FieldFormatConfig> = {};\n for (const [key, cfg] of Object.entries(value[\"formats\"])) {\n if (!isRecord(cfg)) continue;\n const id = cfg[\"id\"];\n if (typeof id !== \"string\") continue;\n const options = cfg[\"options\"];\n formats[key] = isRecord(options)\n ? {\n id: id as FieldFormatConfig[\"id\"],\n options: options as NonNullable<FieldFormatConfig[\"options\"]>,\n }\n : { id: id as FieldFormatConfig[\"id\"] };\n }\n if (Object.keys(formats).length > 0) out.formats = formats;\n }\n\n if (Array.isArray(value[\"frozen\"])) {\n const frozen = value[\"frozen\"].filter((k): k is string => typeof k === \"string\");\n if (frozen.length > 0) out.frozen = frozen;\n }\n\n const grouping = parseGrouping(value[\"grouping\"]);\n if (grouping) out.grouping = grouping;\n\n if (isRecord(value[\"widths\"])) {\n const widths: Record<string, number> = {};\n for (const [key, raw] of Object.entries(value[\"widths\"])) {\n // A width that is not a usable number is one column's loss, never the\n // view's: the rest of the widths still apply and that column is measured.\n if (typeof raw !== \"number\" || !Number.isFinite(raw) || raw <= 0) continue;\n widths[key] = Math.round(raw);\n }\n if (Object.keys(widths).length > 0) out.widths = widths;\n }\n\n const rowHeight = value[\"rowHeight\"];\n if (\n typeof rowHeight === \"number\" &&\n Number.isFinite(rowHeight) &&\n rowHeight >= MIN_ROW_HEIGHT &&\n rowHeight <= MAX_ROW_HEIGHT\n ) {\n out.rowHeight = Math.round(rowHeight);\n }\n\n if (Array.isArray(value[\"hiddenFields\"])) {\n const hiddenFields = value[\"hiddenFields\"].filter((k): k is string => typeof k === \"string\");\n if (hiddenFields.length > 0) out.hiddenFields = hiddenFields;\n }\n if (Array.isArray(value[\"cardHidden\"])) {\n out.cardHidden = value[\"cardHidden\"].filter((k): k is string => typeof k === \"string\");\n }\n\n if (Array.isArray(value[\"columnOrder\"])) {\n const columnOrder = value[\"columnOrder\"].filter((k): k is string => typeof k === \"string\");\n if (columnOrder.length > 0) out.columnOrder = columnOrder;\n }\n\n const gallerySize = value[\"gallerySize\"];\n if (gallerySize === \"small\" || gallerySize === \"medium\" || gallerySize === \"large\") {\n out.gallerySize = gallerySize;\n }\n\n const boardSize = value[\"boardSize\"];\n if (boardSize === \"small\" || boardSize === \"medium\" || boardSize === \"large\") out.boardSize = boardSize;\n if (typeof value[\"boardPreview\"] === \"string\" && value[\"boardPreview\"] !== \"\") out.boardPreview = value[\"boardPreview\"];\n if (typeof value[\"boardHideEmpty\"] === \"boolean\") out.boardHideEmpty = value[\"boardHideEmpty\"];\n if (typeof value[\"boardColorColumns\"] === \"boolean\") out.boardColorColumns = value[\"boardColorColumns\"];\n if (typeof value[\"boardCounts\"] === \"boolean\") out.boardCounts = value[\"boardCounts\"];\n if (value[\"locked\"] === true) out.locked = true;\n if (Array.isArray(value[\"boardLanesShut\"])) {\n const shut = value[\"boardLanesShut\"].filter((k): k is string => typeof k === \"string\");\n if (shut.length > 0) out.boardLanesShut = shut;\n }\n\n if (typeof value[\"wrap\"] === \"boolean\") out.wrap = value[\"wrap\"];\n if (Array.isArray(value[\"wrapColumns\"])) {\n const wrapColumns = value[\"wrapColumns\"].filter((k): k is string => typeof k === \"string\");\n if (wrapColumns.length > 0) out.wrapColumns = wrapColumns;\n }\n const fit = value[\"fit\"];\n if (fit === \"auto\" || fit === \"fit\" || fit === \"scroll\") out.fit = fit;\n if (typeof value[\"freezeFirst\"] === \"boolean\") out.freezeFirst = value[\"freezeFirst\"];\n const footer = value[\"footer\"];\n if (footer === \"sticky\" || footer === \"inline\") out.footer = footer;\n {\n const gantt = parseGanttLook(value[\"gantt\"]);\n if (gantt) out.gantt = gantt;\n }\n if (isRecord(value[\"summaries\"])) {\n const summaries: Record<string, string> = {};\n for (const [key, op] of Object.entries(value[\"summaries\"])) {\n // A measure name is judged by the store when it is asked; a non-string is nobody's.\n if (typeof op === \"string\" && op.trim() !== \"\") summaries[key] = op;\n }\n if (Object.keys(summaries).length > 0) out.summaries = summaries;\n }\n\n return out;\n}\n\n/**\n * A grouping, or nothing. A grouping with no Field to group BY is not a partial\n * grouping — it is no grouping at all, so it is dropped whole; an unreadable\n * subtotal or a section order this primitive does not have costs only itself.\n */\nfunction parseGrouping(raw: unknown): GridGrouping | undefined {\n if (!isRecord(raw)) return undefined;\n const field = raw[\"field\"];\n if (typeof field !== \"string\" || field.trim() === \"\") return undefined;\n\n const out: GridGrouping = { field };\n\n if (isRecord(raw[\"aggregates\"])) {\n const aggregates: Record<string, GroupAggregateSpec> = {};\n for (const [key, spec] of Object.entries(raw[\"aggregates\"])) {\n if (!isRecord(spec)) continue;\n const kind = spec[\"kind\"];\n if (typeof kind !== \"string\") continue;\n if (!(AGGREGATE_KINDS as readonly string[]).includes(kind)) continue;\n const label = spec[\"label\"];\n aggregates[key] =\n typeof label === \"string\"\n ? { kind: kind as GroupAggregateKind, label }\n : { kind: kind as GroupAggregateKind };\n }\n if (Object.keys(aggregates).length > 0) out.aggregates = aggregates;\n }\n\n const order = raw[\"order\"];\n if (typeof order === \"string\" && (GROUP_ORDERS as readonly string[]).includes(order)) {\n out.order = order as GroupOrder;\n }\n\n if (Array.isArray(raw[\"collapsed\"])) {\n const collapsed = raw[\"collapsed\"].filter((k): k is string => typeof k === \"string\");\n if (collapsed.length > 0) out.collapsed = collapsed;\n }\n\n if (typeof raw[\"showColumn\"] === \"boolean\") out.showColumn = raw[\"showColumn\"];\n\n if (Array.isArray(raw[\"then\"])) {\n const then: GridSubGroup[] = [];\n for (const level of raw[\"then\"]) {\n if (!isRecord(level)) continue;\n const sub = level[\"field\"];\n if (typeof sub !== \"string\" || sub.trim() === \"\") continue;\n const subOrder = level[\"order\"];\n const subGrain = level[\"grain\"];\n then.push({\n field: sub,\n ...(typeof subOrder === \"string\" && (GROUP_ORDERS as readonly string[]).includes(subOrder)\n ? { order: subOrder as GroupOrder }\n : {}),\n ...(typeof subGrain === \"string\" && subGrain !== \"\" ? { grain: subGrain } : {}),\n });\n }\n if (then.length > 0) out.then = then.slice(0, 2);\n }\n\n const grain = raw[\"grain\"];\n if (typeof grain === \"string\" && grain !== \"\") out.grain = grain;\n const across = raw[\"across\"];\n if (typeof across === \"string\" && across.trim() !== \"\") out.across = across;\n if (Array.isArray(raw[\"show\"])) {\n const show = raw[\"show\"].filter((k): k is string => typeof k === \"string\" && k !== \"\");\n if (show.length > 0) out.show = show;\n }\n if (Array.isArray(raw[\"trail\"])) {\n const trail: GridDrillStep[] = [];\n for (const step of raw[\"trail\"]) {\n if (!isRecord(step) || typeof step[\"dim\"] !== \"string\" || step[\"dim\"] === \"\") continue;\n const value = step[\"value\"];\n if (value !== null && typeof value !== \"string\") continue;\n trail.push({ dim: step[\"dim\"], value });\n }\n if (trail.length > 0) out.trail = trail;\n }\n\n return out;\n}\n\n/** True when nothing here would change how anything looks. */\nexport function presentationIsEmpty(p: GridPresentation): boolean {\n return (\n (p.style === undefined || tableStyleIsEmpty(p.style)) &&\n Object.keys(p.formats ?? {}).length === 0 &&\n (p.frozen?.length ?? 0) === 0 &&\n p.grouping === undefined &&\n Object.keys(p.widths ?? {}).length === 0 &&\n p.rowHeight === undefined &&\n (p.hiddenFields?.length ?? 0) === 0 &&\n p.cardHidden === undefined &&\n (p.columnOrder?.length ?? 0) === 0 &&\n p.gallerySize === undefined &&\n p.boardSize === undefined &&\n p.boardPreview === undefined &&\n p.boardHideEmpty === undefined &&\n p.boardColorColumns === undefined &&\n p.boardCounts === undefined &&\n (p.boardLanesShut?.length ?? 0) === 0 &&\n p.locked === undefined &&\n p.wrap === undefined &&\n p.fit === undefined &&\n p.freezeFirst === undefined &&\n p.footer === undefined &&\n Object.keys(p.summaries ?? {}).length === 0 &&\n p.gantt === undefined\n );\n}\n\n/** The string a view record stores. Empty string for \"this view styles nothing\". */\nexport function presentationDocument(p: GridPresentation): string {\n return presentationIsEmpty(p) ? \"\" : JSON.stringify(p);\n}\n\n/** Kinds whose value is a control the record renderer draws, never a formatter's string. */\nconst DRAWN_KINDS = new Set([\"select\", \"multi_select\", \"relation\", \"member\", \"person\", \"attachment\", \"file\", \"entity_reference\", \"checkbox\", \"boolean\", \"signature\", \"json\"]);\n\n/**\n * The display format a column's settings chose for it, when it is one the platform registry\n * knows and the column is not one the record renderer draws itself. `undefined` otherwise.\n */\nexport function shownAsOf(field: Field): FieldFormatConfig | undefined {\n if (DRAWN_KINDS.has(String(editorKindFor(field)))) return undefined;\n const raw = (field as unknown as { display_format?: unknown }).display_format;\n if (!raw || typeof raw !== \"object\") return undefined;\n const id = (raw as { id?: unknown }).id;\n // `formula` is the column's KIND, never a look: a worked-out column shows as its result format.\n if (typeof id !== \"string\" || id === \"formula\" || !getFieldFormat(id)) return undefined;\n const options = (raw as { options?: unknown }).options;\n return options && typeof options === \"object\" && Object.keys(options).length > 0\n ? ({ id, options } as FieldFormatConfig)\n : ({ id } as FieldFormatConfig);\n}\n\n/**\n * The format a Field's OWN declaration implies, before any view says anything.\n *\n * Returns `undefined` for a Field whose value the record renderer already draws\n * better than a formatter could — a relation chip, a person, an attachment, a\n * choice — because handing those to a text formatter would REPLACE a working\n * control with a string.\n */\nexport function formatForField(field: Field): FieldFormatConfig | undefined {\n // \"SHOWS AS\" — THE FORMAT THE COLUMN'S OWN SETTINGS CHOSE (merge tranche 6a/6e). The column\n // keeps it on the Field (`display_format`, written by `custom.field_update`), so the grid, the\n // record panel and the export read one answer. A column the record renderer draws better than\n // a formatter (a choice, a link, a person, a file) keeps its own drawing.\n const shownAs = shownAsOf(field);\n // A RATING'S TOP STAR IS ITS MAX RULE (lane 10 P4), so changing the rule changes the stars.\n if (newerKindOf(field) === \"rating\" && (!shownAs || shownAs.id === \"rating\")) {\n return { id: \"rating\", options: { ...(shownAs?.options ?? {}), ratingMax: ratingMaxOf(field) } };\n }\n if (shownAs) return shownAs;\n // THE STORE'S OWN STAMPS (Created time, Last changed time) are instants and print like\n // every other date-and-time cell — `dates.ts` answers, the same helper every card reads.\n const stamp = dateFormatFor(field);\n if (stamp && editorKindFor(field) !== \"datetime\") return stamp;\n switch (editorKindFor(field)) {\n case \"currency\":\n return {\n id: \"currency\",\n options: { currency: currencyCodeFor(field.unit) },\n };\n case \"percent\":\n return { id: \"percent\" };\n case \"datetime\":\n // The COLUMN's declaration: a `kind: \"date\"` column is a date column, anything else can\n // hold a moment. Each VALUE is still printed in its own shape (`RecordValue` →\n // `dates.dateFormatFor(field, value)`), so a bare date in a datetime column reads as a date.\n return String(field.config?.[\"kind\"] ?? \"\") === \"date\" ? { id: \"date\" } : { id: \"datetime\" };\n case \"email\":\n return { id: \"email\" };\n case \"url\":\n return { id: \"url\" };\n case \"phone\":\n return { id: \"phone\" };\n case \"number\":\n return { id: \"number\" };\n default:\n return undefined;\n }\n}\n\n/**\n * THE FORMAT ONE CELL OF A COLUMN IS DRAWN IN — the grid's cell and the gallery's card alike\n * (guard G4 \"one format\", lane VIEWS-AND-FIELDS W1-B): the view's own format for the column when\n * it set one, else the Field's own (`formatForField`). One function, so no layout formats money\n * (or a date, or a percent) its own way.\n */\nexport function cellFormatFor(field: Field, viewFormat?: FieldFormatConfig | undefined): FieldFormatConfig | undefined {\n return viewFormat ?? formatForField(field);\n}\n\n/**\n * The store lets a person type the SYMBOL they think in (`$`, `€`), and\n * `Intl.NumberFormat` wants the ISO code. The symbols people actually enter are\n * mapped; anything else is passed straight through, because a real ISO code is\n * the other thing that lands in this field, and an unknown code still renders\n * (`1,234.00 XYZ`) rather than blanking — THE FALLBACK LAW.\n */\nconst SYMBOL_TO_CODE: Record<string, string> = {\n $: \"USD\",\n \"£\": \"GBP\",\n \"€\": \"EUR\",\n \"¥\": \"JPY\",\n \"₹\": \"INR\",\n \"₽\": \"RUB\",\n \"₩\": \"KRW\",\n \"R$\": \"BRL\",\n \"C$\": \"CAD\",\n \"A$\": \"AUD\",\n};\n\nexport function currencyCodeFor(unit: string | null | undefined): string {\n const raw = (unit ?? \"\").trim();\n if (raw === \"\") return \"USD\";\n return SYMBOL_TO_CODE[raw] ?? raw.toUpperCase();\n}\n\n/**\n * The color a value paints with when a view colors BY a column.\n *\n * NOTHING FAILS SILENTLY, so this never returns \"no color\" for a value that\n * exists. The store keeps a list column's choices as records of another Table,\n * and this grid has not read that Table's option colors, so the color is\n * derived from the value's own text: the same word always gets the same color,\n * different words get different ones, and `true` is green the way every\n * checklist reads. When those option colors do land, this is the ONE function\n * that changes and every screen follows.\n */\nexport const colorFromTheValue: ChoiceColorLookup = (_fieldKey, value) => {\n if (value === true) return \"green\";\n if (value === false || value === null || value === undefined) return undefined;\n const text = String(value).trim().toLowerCase();\n if (text === \"\") return undefined;\n let hash = 0;\n for (let i = 0; i < text.length; i += 1) {\n hash = (hash * 31 + text.charCodeAt(i)) >>> 0;\n }\n return COLOR_ROTATION[hash % COLOR_ROTATION.length] as StyleColor;\n};\n\n/**\n * G1's TABLE-LEVEL COLORS (`custom.table_decorations` / `client.tableDecorations`),\n * TRANSLATED INTO A `TableStyle` — the same document a saved view's own `presentation.style`\n * is, so one resolver (`resolveTableStyle`, below) can treat \"the table's colors\" and \"this\n * view's colors\" as two layers of the SAME thing instead of two unrelated systems.\n *\n * THE TRANSLATION IS NECESSARY AND IT IS THE ONLY ONE. G1 addresses a Field by its ID\n * (REC-17 — a column renamed keeps its color), because it is the STORE's own door; a\n * `TableStyle` addresses a Field by its KEY, because it reads straight off `row.document`,\n * which is keyed by Field key. A rule or a color-by whose Field has been deleted (its id no\n * longer resolves to a key) is dropped rather than crashing the whole style — a defect in\n * one rule costs that rule, never the read.\n */\nexport function styleFromDecorations(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n): TableStyle {\n if (!decorations) return { version: 1 };\n const keyOf = (id: string) => fields.find((f) => f.id === id)?.key;\n const style: TableStyle = { version: 1 };\n\n if (decorations.color_by?.field) {\n const field = keyOf(decorations.color_by.field);\n if (field) style.colorBy = { field, target: decorations.color_by.target };\n }\n\n if (Array.isArray(decorations.rules) && decorations.rules.length > 0) {\n const rules: ColorRule[] = [];\n decorations.rules.forEach((rule, index) => {\n const field = keyOf(rule.field);\n if (!field) return;\n rules.push({\n id: rule.id ?? `decoration:${index}`,\n field,\n op: rule.op,\n color: rule.color,\n target: rule.target,\n ...(rule.value === undefined || rule.value === null ? {} : { value: String(rule.value) }),\n });\n });\n if (rules.length > 0) style.rules = rules;\n }\n\n if (decorations.rows && Object.keys(decorations.rows).length > 0) {\n style.rows = { ...decorations.rows };\n }\n\n const columns: Record<string, StyleColor> = {};\n for (const [id, color] of Object.entries(decorations.columns ?? {})) {\n const key = keyOf(id);\n if (key) columns[key] = color;\n }\n if (Object.keys(columns).length > 0) style.columns = columns;\n\n const cells: Record<string, Record<string, StyleColor>> = {};\n for (const [rowId, byField] of Object.entries(decorations.cells ?? {})) {\n for (const [id, color] of Object.entries(byField ?? {})) {\n const key = keyOf(id);\n if (!key) continue;\n (cells[rowId] ??= {})[key] = color;\n }\n }\n if (Object.keys(cells).length > 0) style.cells = cells;\n\n return style;\n}\n\n/**\n * THE ONE PRESENTATION RESOLVER every colored surface in this package reads —\n * `Grid`, and (via `Card`) the kanban board, the calendar and the gallery.\n *\n * THE CLASS THIS CLOSES (lane UI-QUICK-1, TABLE-PARITY review, 2026-09-24). Threading\n * `view.presentation.style` into `Card` closed the instance the CALENDAR-GALLERY bench\n * found — a VIEW that had its own color-by looked right everywhere — but it left the\n * class open: a table whose colors are the ORGANIZATION's decorations (G1's\n * `custom.table_decorations` — \"color rows by Account Type\", \"MAXED and NEAR rules\" on\n * Arman's own Coding Accounts table) and whose views never set a `presentation.style` of\n * their own drew NOTHING, on the grid exactly as much as on every other layout, because\n * nothing in this package had ever read G1's door at all.\n *\n * TWO LAYERS, ONE DOCUMENT. The table's decorations are the base coat — what every view\n * of this table shows until a view says otherwise. A view's OWN `presentation.style` is\n * an override, piece by piece: a view that only ever set manual row highlights still\n * inherits the table's color-by and rules for everything it never touched, and a view\n * that fully redecorated (its own `colorBy` AND its own `rules`) is not haunted by a\n * table rule it meant to replace. Per-piece, never all-or-nothing, because a view is a\n * LOOK on top of the table, not a wholesale substitute for it.\n */\nexport function resolveTableStyle(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n viewStyle: TableStyle | undefined,\n): TableStyle {\n const base = styleFromDecorations(decorations, fields);\n if (!viewStyle) return base;\n const colorBy = viewStyle.colorBy ?? base.colorBy;\n const rules = viewStyle.rules ?? base.rules;\n const rows = viewStyle.rows ?? base.rows;\n const columns = viewStyle.columns ?? base.columns;\n const cells = viewStyle.cells ?? base.cells;\n return {\n version: 1,\n ...(colorBy !== undefined ? { colorBy } : {}),\n ...(rules !== undefined ? { rules } : {}),\n ...(rows !== undefined ? { rows } : {}),\n ...(columns !== undefined ? { columns } : {}),\n ...(cells !== undefined ? { cells } : {}),\n };\n}\n","\"use client\";\n\n// src/viewKeys.ts — @ai-matrx/records-ui · lane S1-PRIME VIEW-KEYS\n//\n// THE VIEW-KEY REGISTRY MOVED TO `@ai-matrx/records/views` (step 10, 2026-10-08): the declared keys, the one\n// reader and the one writer are headless there. What stays here is this package's LOOK: the grid\n// presentation (colors, formats, frozen columns — `presentation.ts`, built on design-system) bound as the\n// view's presentation codec. Every name this file exported is unchanged.\n\nimport * as base from \"@ai-matrx/records/views\";\nimport { parseGridPresentation, presentationDocument, presentationIsEmpty, type GridPresentation } from \"./presentation\";\n\nexport {\n STORE_KEEPS_TIMELINE,\n STORE_KEPT_LAYOUTS,\n VIEW_KEYS,\n storeKeepsLayout,\n viewKeysFor,\n viewSettingsFor,\n type ViewKeyDeclaration,\n type ViewKeyLayout,\n type ViewKeyWriter,\n type ViewSortKey,\n} from \"@ai-matrx/records/views\";\n\n/** This package's look, as the view's presentation codec. */\nexport const gridPresentationCodec: base.ViewPresentationCodec<GridPresentation> = {\n parse: parseGridPresentation,\n isEmpty: presentationIsEmpty,\n toDocument: (presentation) => JSON.parse(presentationDocument(presentation)) as Record<string, unknown>,\n};\n\n/** EVERY DECLARED KEY OF A VIEW, TYPED, with this package's grid look. */\nexport type ViewSettings = base.ViewSettings<GridPresentation>;\nexport type ViewSettingsPatch = base.ViewSettingsPatch<GridPresentation>;\n\n/** Every view and look door with this package's grid look bound — bindings of records' one definition. */\nexport const boundViews = base.bindViewCodec<GridPresentation>(gridPresentationCodec);\n\n/** THE READER (a stored `definition` and the row's flat `filters` in, every declared key out) and THE WRITER'S SHAPE. */\nexport const { viewSettings, viewSettingsPatch } = boundViews;\n","// src/firstPage.ts — @ai-matrx/records-ui/first-page · lane PAGE-BUNDLE-2\n//\n// THE GRID'S FIRST PAGE, ASKED BEFORE THE GRID EXISTS. The logic lives in `@ai-matrx/records/first-page` (headless,\n// step 10, 2026-10-08); this entry binds this package's grid look (`gridPresentationCodec`) so a view is read exactly\n// as the screen reads it. No React here (server-safe entry).\n\nimport { bindFirstPageCodec } from \"@ai-matrx/records/first-page\";\nimport { gridPresentationCodec } from \"./viewKeys\";\n\nexport {\n EMBEDDED_PAGE_SIZE,\n TABLE_PAGE_SIZE,\n firstGridPageArgs,\n serverRowsOf,\n viewFilterOf,\n type FirstGridPageArgs,\n} from \"@ai-matrx/records/first-page\";\n\nexport const { firstGridPageRequest, askTablePageSeed } = bindFirstPageCodec(gridPresentationCodec);\n\n// Entity blocks and chart tiles (lane SEED-ENTITY).\nexport * from \"./firstPageEntity\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/presentation.ts","../src/viewKeys.ts","../src/firstPage.ts"],"names":["parseTableStyle","tableStyleIsEmpty","raw","base","bindFirstPageCodec"],"mappings":"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","file":"first-page.cjs","sourcesContent":["/**\n * The Gantt mode of a timeline view, by Field key. `mode` turns the left task list on; `critical` paints the longest\n * chain; `progress` is a percent/number column; `milestone` a yes/no column; `baselineStart` / `baselineEnd` the two\n * date columns \"Save baseline\" writes; `dependency` the link column that dependencies are written to.\n */\nexport interface GanttLook {\n mode?: boolean;\n critical?: boolean;\n progress?: string;\n milestone?: string;\n baselineStart?: string;\n baselineEnd?: string;\n dependency?: string;\n}\n\n/** Read a stored Gantt look; unknown keys and wrong shapes are dropped. */\nexport function parseGanttLook(raw: unknown): GanttLook | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const v = raw as Record<string, unknown>;\n const out: GanttLook = {};\n if (v[\"mode\"] === true) out.mode = true;\n if (v[\"critical\"] === true) out.critical = true;\n for (const k of [\"progress\", \"milestone\", \"baselineStart\", \"baselineEnd\", \"dependency\"] as const) {\n const x = v[k];\n if (typeof x === \"string\" && x !== \"\") out[k] = x;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * HOW A TABLE LOOKS — colors, formats and the frozen column.\n *\n * THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a\n * currency, a percent, a date, a person. A VIEW declares HOW that reads and\n * what it is worth looking at: two decimal places or none, tint the row when\n * Status is Blocked, keep Name in place while the rest scrolls. Airtable draws\n * the line in exactly this place, and it has to be here: two people read one\n * table two different ways, and neither reading is a fact about the records.\n *\n * So nothing in this module is a new column on a Field and nothing here needed\n * a migration. A presentation lives on the saved-view record (`views.ts`), which\n * is an ordinary record of an ordinary Table — which means an agent sets it\n * through the same `records` tool it uses for everything else, and it travels\n * with the table the way every other record does.\n *\n * A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`\n * reads the Field's OWN declaration — a `currency` parity type with unit `$`, a\n * `percent`, a `datetime` with `config.kind` — and returns the format that\n * follows from it. The view's `formats` map only OVERRIDES that. A table nobody\n * has ever styled still shows `$1,250.50` and `45%`.\n */\n\n// The style parser from its own client-free subpath (lane SSR-ROWS): `@ai-matrx/records-ui/first-page`\n// runs in a host's SERVER component, and every export of the \"use client\" `data-table` entry is a\n// client reference there — calling `parseTableStyle` through it threw on the server.\nimport { COLOR_ROTATION, parseTableStyle, tableStyleIsEmpty } from \"@ai-matrx/design-system/data-table/table-style\";\nimport type {\n ChoiceColorLookup,\n GroupAggregateKind,\n GroupAggregateSpec,\n GroupOrder,\n StyleColor,\n TableStyle,\n} from \"@ai-matrx/design-system/data-table\";\nimport { getFieldFormat, type FieldFormatConfig } from \"@ai-matrx/design-system/field-formats\";\nimport type { ColorRule } from \"@ai-matrx/design-system/data-table\";\nimport { newerKindOf, ratingMaxOf, type Field, type TableDecorations } from \"@ai-matrx/records\";\n\nimport { editorKindFor } from \"./parity\";\nimport { dateFormatFor } from \"./dates\";\n\n/**\n * SECTIONS BY A FIELD — the view's own grouping.\n *\n * `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,\n * because a view is written against the Table's declaration and never against\n * whatever id a screen happened to build its columns with.\n *\n * `aggregates` is keyed by Field key too, and `collapsed` holds the data\n * table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the\n * primitive emits and the only thing that survives a value rendering the same\n * text as another.\n *\n * NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every\n * object in JavaScript already has all three, so an optional field by one of\n * those names is NEVER absent and a `config.valueOf ? … : …` check silently\n * reads `Object.prototype`. It cost the primitive a whole grouping once.\n */\nexport type GridGrouping = {\n /** The Field key whose value makes the sections. */\n field: string;\n /** Per-Field-key subtotal on every section header. */\n aggregates?: Record<string, GroupAggregateSpec>;\n /** Section order. The primitive's default is `value-asc`; blanks sort last. */\n order?: GroupOrder;\n /** The sections this view keeps shut, by the primitive's own group key. */\n collapsed?: string[];\n /** The grouped column stays drawn as a column (Airtable). Absent = shown; `false` = the person stepped it aside. */\n showColumn?: boolean;\n /**\n * SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS \"sub-grouping\"): up to two more levels\n * inside each section, by Field key. At most three levels in all — the store refuses a fourth\n * (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,\n * because this list is parsed and written back whole with the rest of the look.\n */\n then?: GridSubGroup[];\n /**\n * DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or\n * `<key>:<grain>` for a date cut into periods.\n */\n across?: string;\n /** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */\n grain?: string;\n /** The Measures shown (\"what to display\"), by the store's own keys (`count`, `sum_quantity`). */\n show?: string[];\n /** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */\n trail?: GridDrillStep[];\n};\n\n/** One crumb of a saved drill trail. `value: null` is \"no value\". */\nexport type GridDrillStep = { dim: string; value: string | null };\n\n/** One sub-group level: a Field key and its section order. */\nexport type GridSubGroup = { field: string; order?: GroupOrder; grain?: string };\n\n/** Everything a view says about how its table LOOKS. */\nexport type GridPresentation = {\n /** Color-by-a-column, rules and manual highlights. */\n style?: TableStyle;\n /** Per-column format overrides, by the Field's key. */\n formats?: Record<string, FieldFormatConfig>;\n /**\n * The columns that stay in place while the table scrolls sideways, by Field\n * key. Only the LEADING run of the view's column order can actually freeze —\n * the data table decides that, and declines rather than pinning a column at a\n * guessed offset.\n */\n frozen?: string[];\n /** Collapsible sections by one Field, with subtotals on each header. */\n grouping?: GridGrouping;\n /**\n * The width a person dragged a column to, by Field key. SPARSE on purpose —\n * a view saved before widths existed carries none and still parses, and a\n * column nobody resized is left to the table's own measurement.\n */\n widths?: Record<string, number>;\n /** The body row height this view remembers, in px. */\n rowHeight?: number;\n /**\n * Field keys hidden from this view — the SAME hidden-column list the grid's\n * own column picker holds, now given a place on the view so it survives a\n * reload and a layout switch reads the identical set gallery and grid agree\n * on, rather than a second \"fields shown\" list nobody kept in sync.\n */\n hiddenFields?: string[];\n /**\n * THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the\n * card's own choice, kept apart from `hiddenFields` so a board's \"Card fields\" never hides the\n * grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty\n * list is a choice (every field shown); absent means the card never chose.\n */\n cardHidden?: string[];\n /**\n * THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like\n * `widths`: a key it does not name keeps its Field's own place after the named ones, and a key\n * whose Field is gone is skipped — a column order can never hide or invent a column.\n */\n columnOrder?: string[];\n /**\n * The gallery's card size, remembered the same way the grid remembers its\n * row height — a per-view look, not a global default.\n */\n gallerySize?: \"small\" | \"medium\" | \"large\";\n /**\n * THE BOARD'S OWN LOOK (lane BOARD-SWIMLANES, Notion's board options). All kept on the view, all\n * read only by the kanban layout. Absent means the board's default: columns wear their choice's\n * colour, each heading shows its count, empty columns and lanes are drawn, cards are medium with\n * no preview.\n * boardSize card size: sets the column width, the preview's height and, small, drops the card's extras\n * boardPreview the FILE field drawn as a cover at the top of each card (absent = no preview)\n * boardHideEmpty leave out a column or a lane that holds no card\n * boardColorColumns false = neutral headings, no accent bar on the cards\n * boardCounts false = no count beside the heading\n * boardLanesShut the swimlanes kept shut to a strip, by the lane's name\n */\n boardSize?: \"small\" | \"medium\" | \"large\";\n boardPreview?: string;\n boardHideEmpty?: boolean;\n boardColorColumns?: boolean;\n boardCounts?: boolean;\n boardLanesShut?: string[];\n /**\n * A LOCKED VIEW (lane NOTION-SMALL-2): its layout, filters, sorts and settings are not saved over until\n * somebody unlocks it. An editing convenience against an accidental change, never a permission.\n */\n locked?: boolean;\n /**\n * THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane\n * DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means \"whatever the organization\n * says\"; present is the view's word. `freezeFirst: false` is an explicit NO over an\n * organization that freezes the first column — an empty `frozen` cannot say that.\n */\n wrap?: boolean;\n /** NOTION'S \"WRAP COLUMN\" (N5): the columns that wrap their text when the view as a whole does not. */\n wrapColumns?: string[];\n fit?: \"auto\" | \"fit\" | \"scroll\";\n freezeFirst?: boolean;\n /**\n * THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column\n * (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out\n * by `custom.record_aggregate` over the whole table.\n */\n summaries?: Record<string, string>;\n /** THE TIMELINE'S GANTT MODE (kept as `presentation.gantt`): see `GanttLook`. */\n gantt?: GanttLook;\n /**\n * WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the\n * pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's\n * way, and the default when a view says nothing), or `inline`, right after the last row with\n * the page scrolling. Read by the Grid and a host's Sheet.\n */\n footer?: FooterPlacement;\n};\n\n/** Where a grid's footer sits. */\nexport type FooterPlacement = \"sticky\" | \"inline\";\n\n/** What a view that says nothing gets — the older grid's pinned footer. */\nexport const DEFAULT_FOOTER: FooterPlacement = \"sticky\";\n\n/** Where THIS view's footer sits, said or defaulted. */\nexport function footerOf(p: GridPresentation | null | undefined): FooterPlacement {\n return p?.footer ?? DEFAULT_FOOTER;\n}\n\nexport const EMPTY_PRESENTATION: GridPresentation = {};\n\n/** A frozen column needs a known width, or the table declines to freeze at all. */\nexport const FROZEN_COLUMN_WIDTH = 200;\n\n/**\n * WHAT A ROW HEIGHT IS ALLOWED TO BE. Below the floor the text is clipped and\n * no editor fits; above the ceiling one record fills the screen. A view asking\n * for anything outside it is DROPPED rather than obeyed, because a saved look\n * must not be able to make a table unreadable — and the Fields' own heights\n * still draw, which is the tolerant half.\n */\nexport const MIN_ROW_HEIGHT = 24;\nexport const MAX_ROW_HEIGHT = 240;\n\n/**\n * The aggregate kinds and orders the primitive actually implements. Listed here\n * because they are the tolerant parser's allow-list and a view is hostile input:\n * `satisfies` ties them to the primitive's own union, so a kind it renames stops\n * this file compiling instead of quietly parsing into a subtotal nobody draws.\n */\nconst AGGREGATE_KINDS = [\n \"count\",\n \"sum\",\n \"average\",\n \"min\",\n \"max\",\n \"empty\",\n \"filled\",\n \"unique\",\n \"checked\",\n \"unchecked\",\n \"percentFilled\",\n] as const satisfies readonly GroupAggregateKind[];\n\nconst GROUP_ORDERS = [\n \"value-asc\",\n \"value-desc\",\n \"count-desc\",\n \"first-seen\",\n] as const satisfies readonly GroupOrder[];\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Read a presentation out of whatever the view record holds — an object, or the\n * JSON string a text field stores.\n *\n * Tolerant in exactly the way `parseTableStyle` is: an unreadable half is\n * dropped and the rest still applies, because a table must keep drawing when\n * one piece of its styling goes out of date.\n */\nexport function parseGridPresentation(raw: unknown): GridPresentation {\n let value = raw;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return {};\n try {\n value = JSON.parse(trimmed);\n } catch {\n return {};\n }\n }\n if (!isRecord(value)) return {};\n\n const out: GridPresentation = {};\n\n const style = parseTableStyle(value[\"style\"]);\n if (!tableStyleIsEmpty(style)) out.style = style;\n\n if (isRecord(value[\"formats\"])) {\n const formats: Record<string, FieldFormatConfig> = {};\n for (const [key, cfg] of Object.entries(value[\"formats\"])) {\n if (!isRecord(cfg)) continue;\n const id = cfg[\"id\"];\n if (typeof id !== \"string\") continue;\n const options = cfg[\"options\"];\n formats[key] = isRecord(options)\n ? {\n id: id as FieldFormatConfig[\"id\"],\n options: options as NonNullable<FieldFormatConfig[\"options\"]>,\n }\n : { id: id as FieldFormatConfig[\"id\"] };\n }\n if (Object.keys(formats).length > 0) out.formats = formats;\n }\n\n if (Array.isArray(value[\"frozen\"])) {\n const frozen = value[\"frozen\"].filter((k): k is string => typeof k === \"string\");\n if (frozen.length > 0) out.frozen = frozen;\n }\n\n const grouping = parseGrouping(value[\"grouping\"]);\n if (grouping) out.grouping = grouping;\n\n if (isRecord(value[\"widths\"])) {\n const widths: Record<string, number> = {};\n for (const [key, raw] of Object.entries(value[\"widths\"])) {\n // A width that is not a usable number is one column's loss, never the\n // view's: the rest of the widths still apply and that column is measured.\n if (typeof raw !== \"number\" || !Number.isFinite(raw) || raw <= 0) continue;\n widths[key] = Math.round(raw);\n }\n if (Object.keys(widths).length > 0) out.widths = widths;\n }\n\n const rowHeight = value[\"rowHeight\"];\n if (\n typeof rowHeight === \"number\" &&\n Number.isFinite(rowHeight) &&\n rowHeight >= MIN_ROW_HEIGHT &&\n rowHeight <= MAX_ROW_HEIGHT\n ) {\n out.rowHeight = Math.round(rowHeight);\n }\n\n if (Array.isArray(value[\"hiddenFields\"])) {\n const hiddenFields = value[\"hiddenFields\"].filter((k): k is string => typeof k === \"string\");\n if (hiddenFields.length > 0) out.hiddenFields = hiddenFields;\n }\n if (Array.isArray(value[\"cardHidden\"])) {\n out.cardHidden = value[\"cardHidden\"].filter((k): k is string => typeof k === \"string\");\n }\n\n if (Array.isArray(value[\"columnOrder\"])) {\n const columnOrder = value[\"columnOrder\"].filter((k): k is string => typeof k === \"string\");\n if (columnOrder.length > 0) out.columnOrder = columnOrder;\n }\n\n const gallerySize = value[\"gallerySize\"];\n if (gallerySize === \"small\" || gallerySize === \"medium\" || gallerySize === \"large\") {\n out.gallerySize = gallerySize;\n }\n\n const boardSize = value[\"boardSize\"];\n if (boardSize === \"small\" || boardSize === \"medium\" || boardSize === \"large\") out.boardSize = boardSize;\n if (typeof value[\"boardPreview\"] === \"string\" && value[\"boardPreview\"] !== \"\") out.boardPreview = value[\"boardPreview\"];\n if (typeof value[\"boardHideEmpty\"] === \"boolean\") out.boardHideEmpty = value[\"boardHideEmpty\"];\n if (typeof value[\"boardColorColumns\"] === \"boolean\") out.boardColorColumns = value[\"boardColorColumns\"];\n if (typeof value[\"boardCounts\"] === \"boolean\") out.boardCounts = value[\"boardCounts\"];\n if (value[\"locked\"] === true) out.locked = true;\n if (Array.isArray(value[\"boardLanesShut\"])) {\n const shut = value[\"boardLanesShut\"].filter((k): k is string => typeof k === \"string\");\n if (shut.length > 0) out.boardLanesShut = shut;\n }\n\n if (typeof value[\"wrap\"] === \"boolean\") out.wrap = value[\"wrap\"];\n if (Array.isArray(value[\"wrapColumns\"])) {\n const wrapColumns = value[\"wrapColumns\"].filter((k): k is string => typeof k === \"string\");\n if (wrapColumns.length > 0) out.wrapColumns = wrapColumns;\n }\n const fit = value[\"fit\"];\n if (fit === \"auto\" || fit === \"fit\" || fit === \"scroll\") out.fit = fit;\n if (typeof value[\"freezeFirst\"] === \"boolean\") out.freezeFirst = value[\"freezeFirst\"];\n const footer = value[\"footer\"];\n if (footer === \"sticky\" || footer === \"inline\") out.footer = footer;\n {\n const gantt = parseGanttLook(value[\"gantt\"]);\n if (gantt) out.gantt = gantt;\n }\n if (isRecord(value[\"summaries\"])) {\n const summaries: Record<string, string> = {};\n for (const [key, op] of Object.entries(value[\"summaries\"])) {\n // A measure name is judged by the store when it is asked; a non-string is nobody's.\n if (typeof op === \"string\" && op.trim() !== \"\") summaries[key] = op;\n }\n if (Object.keys(summaries).length > 0) out.summaries = summaries;\n }\n\n return out;\n}\n\n/**\n * A grouping, or nothing. A grouping with no Field to group BY is not a partial\n * grouping — it is no grouping at all, so it is dropped whole; an unreadable\n * subtotal or a section order this primitive does not have costs only itself.\n */\nfunction parseGrouping(raw: unknown): GridGrouping | undefined {\n if (!isRecord(raw)) return undefined;\n const field = raw[\"field\"];\n if (typeof field !== \"string\" || field.trim() === \"\") return undefined;\n\n const out: GridGrouping = { field };\n\n if (isRecord(raw[\"aggregates\"])) {\n const aggregates: Record<string, GroupAggregateSpec> = {};\n for (const [key, spec] of Object.entries(raw[\"aggregates\"])) {\n if (!isRecord(spec)) continue;\n const kind = spec[\"kind\"];\n if (typeof kind !== \"string\") continue;\n if (!(AGGREGATE_KINDS as readonly string[]).includes(kind)) continue;\n const label = spec[\"label\"];\n aggregates[key] =\n typeof label === \"string\"\n ? { kind: kind as GroupAggregateKind, label }\n : { kind: kind as GroupAggregateKind };\n }\n if (Object.keys(aggregates).length > 0) out.aggregates = aggregates;\n }\n\n const order = raw[\"order\"];\n if (typeof order === \"string\" && (GROUP_ORDERS as readonly string[]).includes(order)) {\n out.order = order as GroupOrder;\n }\n\n if (Array.isArray(raw[\"collapsed\"])) {\n const collapsed = raw[\"collapsed\"].filter((k): k is string => typeof k === \"string\");\n if (collapsed.length > 0) out.collapsed = collapsed;\n }\n\n if (typeof raw[\"showColumn\"] === \"boolean\") out.showColumn = raw[\"showColumn\"];\n\n if (Array.isArray(raw[\"then\"])) {\n const then: GridSubGroup[] = [];\n for (const level of raw[\"then\"]) {\n if (!isRecord(level)) continue;\n const sub = level[\"field\"];\n if (typeof sub !== \"string\" || sub.trim() === \"\") continue;\n const subOrder = level[\"order\"];\n const subGrain = level[\"grain\"];\n then.push({\n field: sub,\n ...(typeof subOrder === \"string\" && (GROUP_ORDERS as readonly string[]).includes(subOrder)\n ? { order: subOrder as GroupOrder }\n : {}),\n ...(typeof subGrain === \"string\" && subGrain !== \"\" ? { grain: subGrain } : {}),\n });\n }\n if (then.length > 0) out.then = then.slice(0, 2);\n }\n\n const grain = raw[\"grain\"];\n if (typeof grain === \"string\" && grain !== \"\") out.grain = grain;\n const across = raw[\"across\"];\n if (typeof across === \"string\" && across.trim() !== \"\") out.across = across;\n if (Array.isArray(raw[\"show\"])) {\n const show = raw[\"show\"].filter((k): k is string => typeof k === \"string\" && k !== \"\");\n if (show.length > 0) out.show = show;\n }\n if (Array.isArray(raw[\"trail\"])) {\n const trail: GridDrillStep[] = [];\n for (const step of raw[\"trail\"]) {\n if (!isRecord(step) || typeof step[\"dim\"] !== \"string\" || step[\"dim\"] === \"\") continue;\n const value = step[\"value\"];\n if (value !== null && typeof value !== \"string\") continue;\n trail.push({ dim: step[\"dim\"], value });\n }\n if (trail.length > 0) out.trail = trail;\n }\n\n return out;\n}\n\n/** True when nothing here would change how anything looks. */\nexport function presentationIsEmpty(p: GridPresentation): boolean {\n return (\n (p.style === undefined || tableStyleIsEmpty(p.style)) &&\n Object.keys(p.formats ?? {}).length === 0 &&\n (p.frozen?.length ?? 0) === 0 &&\n p.grouping === undefined &&\n Object.keys(p.widths ?? {}).length === 0 &&\n p.rowHeight === undefined &&\n (p.hiddenFields?.length ?? 0) === 0 &&\n p.cardHidden === undefined &&\n (p.columnOrder?.length ?? 0) === 0 &&\n p.gallerySize === undefined &&\n p.boardSize === undefined &&\n p.boardPreview === undefined &&\n p.boardHideEmpty === undefined &&\n p.boardColorColumns === undefined &&\n p.boardCounts === undefined &&\n (p.boardLanesShut?.length ?? 0) === 0 &&\n p.locked === undefined &&\n p.wrap === undefined &&\n p.fit === undefined &&\n p.freezeFirst === undefined &&\n p.footer === undefined &&\n Object.keys(p.summaries ?? {}).length === 0 &&\n p.gantt === undefined\n );\n}\n\n/** The string a view record stores. Empty string for \"this view styles nothing\". */\nexport function presentationDocument(p: GridPresentation): string {\n return presentationIsEmpty(p) ? \"\" : JSON.stringify(p);\n}\n\n/** Kinds whose value is a control the record renderer draws, never a formatter's string. */\nconst DRAWN_KINDS = new Set([\"select\", \"multi_select\", \"relation\", \"member\", \"person\", \"attachment\", \"file\", \"entity_reference\", \"checkbox\", \"boolean\", \"signature\", \"json\"]);\n\n/**\n * The display format a column's settings chose for it, when it is one the platform registry\n * knows and the column is not one the record renderer draws itself. `undefined` otherwise.\n */\nexport function shownAsOf(field: Field): FieldFormatConfig | undefined {\n if (DRAWN_KINDS.has(String(editorKindFor(field)))) return undefined;\n const raw = (field as unknown as { display_format?: unknown }).display_format;\n if (!raw || typeof raw !== \"object\") return undefined;\n const id = (raw as { id?: unknown }).id;\n // `formula` is the column's KIND, never a look: a worked-out column shows as its result format.\n if (typeof id !== \"string\" || id === \"formula\" || !getFieldFormat(id)) return undefined;\n const options = (raw as { options?: unknown }).options;\n return options && typeof options === \"object\" && Object.keys(options).length > 0\n ? ({ id, options } as FieldFormatConfig)\n : ({ id } as FieldFormatConfig);\n}\n\n/**\n * The format a Field's OWN declaration implies, before any view says anything.\n *\n * Returns `undefined` for a Field whose value the record renderer already draws\n * better than a formatter could — a relation chip, a person, an attachment, a\n * choice — because handing those to a text formatter would REPLACE a working\n * control with a string.\n */\nexport function formatForField(field: Field): FieldFormatConfig | undefined {\n // \"SHOWS AS\" — THE FORMAT THE COLUMN'S OWN SETTINGS CHOSE (merge tranche 6a/6e). The column\n // keeps it on the Field (`display_format`, written by `custom.field_update`), so the grid, the\n // record panel and the export read one answer. A column the record renderer draws better than\n // a formatter (a choice, a link, a person, a file) keeps its own drawing.\n const shownAs = shownAsOf(field);\n // A RATING'S TOP STAR IS ITS MAX RULE (lane 10 P4), so changing the rule changes the stars.\n if (newerKindOf(field) === \"rating\" && (!shownAs || shownAs.id === \"rating\")) {\n return { id: \"rating\", options: { ...(shownAs?.options ?? {}), ratingMax: ratingMaxOf(field) } };\n }\n if (shownAs) return shownAs;\n // THE STORE'S OWN STAMPS (Created time, Last changed time) are instants and print like\n // every other date-and-time cell — `dates.ts` answers, the same helper every card reads.\n const stamp = dateFormatFor(field);\n if (stamp && editorKindFor(field) !== \"datetime\") return stamp;\n switch (editorKindFor(field)) {\n case \"currency\":\n return {\n id: \"currency\",\n options: { currency: currencyCodeFor(field.unit) },\n };\n case \"percent\":\n return { id: \"percent\" };\n case \"datetime\":\n // The COLUMN's declaration: a `kind: \"date\"` column is a date column, anything else can\n // hold a moment. Each VALUE is still printed in its own shape (`RecordValue` →\n // `dates.dateFormatFor(field, value)`), so a bare date in a datetime column reads as a date.\n return String(field.config?.[\"kind\"] ?? \"\") === \"date\" ? { id: \"date\" } : { id: \"datetime\" };\n case \"email\":\n return { id: \"email\" };\n case \"url\":\n return { id: \"url\" };\n case \"phone\":\n return { id: \"phone\" };\n case \"number\":\n return { id: \"number\" };\n default:\n return undefined;\n }\n}\n\n/**\n * THE FORMAT ONE CELL OF A COLUMN IS DRAWN IN — the grid's cell and the gallery's card alike\n * (guard G4 \"one format\", lane VIEWS-AND-FIELDS W1-B): the view's own format for the column when\n * it set one, else the Field's own (`formatForField`). One function, so no layout formats money\n * (or a date, or a percent) its own way.\n */\nexport function cellFormatFor(field: Field, viewFormat?: FieldFormatConfig | undefined): FieldFormatConfig | undefined {\n return viewFormat ?? formatForField(field);\n}\n\n/**\n * The store lets a person type the SYMBOL they think in (`$`, `€`), and\n * `Intl.NumberFormat` wants the ISO code. The symbols people actually enter are\n * mapped; anything else is passed straight through, because a real ISO code is\n * the other thing that lands in this field, and an unknown code still renders\n * (`1,234.00 XYZ`) rather than blanking — THE FALLBACK LAW.\n */\nconst SYMBOL_TO_CODE: Record<string, string> = {\n $: \"USD\",\n \"£\": \"GBP\",\n \"€\": \"EUR\",\n \"¥\": \"JPY\",\n \"₹\": \"INR\",\n \"₽\": \"RUB\",\n \"₩\": \"KRW\",\n \"R$\": \"BRL\",\n \"C$\": \"CAD\",\n \"A$\": \"AUD\",\n};\n\nexport function currencyCodeFor(unit: string | null | undefined): string {\n const raw = (unit ?? \"\").trim();\n if (raw === \"\") return \"USD\";\n return SYMBOL_TO_CODE[raw] ?? raw.toUpperCase();\n}\n\n/**\n * The color a value paints with when a view colors BY a column.\n *\n * NOTHING FAILS SILENTLY, so this never returns \"no color\" for a value that\n * exists. The store keeps a list column's choices as records of another Table,\n * and this grid has not read that Table's option colors, so the color is\n * derived from the value's own text: the same word always gets the same color,\n * different words get different ones, and `true` is green the way every\n * checklist reads. When those option colors do land, this is the ONE function\n * that changes and every screen follows.\n */\nexport const colorFromTheValue: ChoiceColorLookup = (_fieldKey, value) => {\n if (value === true) return \"green\";\n if (value === false || value === null || value === undefined) return undefined;\n const text = String(value).trim().toLowerCase();\n if (text === \"\") return undefined;\n let hash = 0;\n for (let i = 0; i < text.length; i += 1) {\n hash = (hash * 31 + text.charCodeAt(i)) >>> 0;\n }\n return COLOR_ROTATION[hash % COLOR_ROTATION.length] as StyleColor;\n};\n\n/**\n * G1's TABLE-LEVEL COLORS (`custom.table_decorations` / `client.tableDecorations`),\n * TRANSLATED INTO A `TableStyle` — the same document a saved view's own `presentation.style`\n * is, so one resolver (`resolveTableStyle`, below) can treat \"the table's colors\" and \"this\n * view's colors\" as two layers of the SAME thing instead of two unrelated systems.\n *\n * THE TRANSLATION IS NECESSARY AND IT IS THE ONLY ONE. G1 addresses a Field by its ID\n * (REC-17 — a column renamed keeps its color), because it is the STORE's own door; a\n * `TableStyle` addresses a Field by its KEY, because it reads straight off `row.document`,\n * which is keyed by Field key. A rule or a color-by whose Field has been deleted (its id no\n * longer resolves to a key) is dropped rather than crashing the whole style — a defect in\n * one rule costs that rule, never the read.\n */\nexport function styleFromDecorations(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n): TableStyle {\n if (!decorations) return { version: 1 };\n const keyOf = (id: string) => fields.find((f) => f.id === id)?.key;\n const style: TableStyle = { version: 1 };\n\n if (decorations.color_by?.field) {\n const field = keyOf(decorations.color_by.field);\n if (field) style.colorBy = { field, target: decorations.color_by.target };\n }\n\n if (Array.isArray(decorations.rules) && decorations.rules.length > 0) {\n const rules: ColorRule[] = [];\n decorations.rules.forEach((rule, index) => {\n const field = keyOf(rule.field);\n if (!field) return;\n rules.push({\n id: rule.id ?? `decoration:${index}`,\n field,\n op: rule.op,\n color: rule.color,\n target: rule.target,\n ...(rule.value === undefined || rule.value === null ? {} : { value: String(rule.value) }),\n });\n });\n if (rules.length > 0) style.rules = rules;\n }\n\n if (decorations.rows && Object.keys(decorations.rows).length > 0) {\n style.rows = { ...decorations.rows };\n }\n\n const columns: Record<string, StyleColor> = {};\n for (const [id, color] of Object.entries(decorations.columns ?? {})) {\n const key = keyOf(id);\n if (key) columns[key] = color;\n }\n if (Object.keys(columns).length > 0) style.columns = columns;\n\n const cells: Record<string, Record<string, StyleColor>> = {};\n for (const [rowId, byField] of Object.entries(decorations.cells ?? {})) {\n for (const [id, color] of Object.entries(byField ?? {})) {\n const key = keyOf(id);\n if (!key) continue;\n (cells[rowId] ??= {})[key] = color;\n }\n }\n if (Object.keys(cells).length > 0) style.cells = cells;\n\n return style;\n}\n\n/**\n * THE ONE PRESENTATION RESOLVER every colored surface in this package reads —\n * `Grid`, and (via `Card`) the kanban board, the calendar and the gallery.\n *\n * THE CLASS THIS CLOSES (lane UI-QUICK-1, TABLE-PARITY review, 2026-09-24). Threading\n * `view.presentation.style` into `Card` closed the instance the CALENDAR-GALLERY bench\n * found — a VIEW that had its own color-by looked right everywhere — but it left the\n * class open: a table whose colors are the ORGANIZATION's decorations (G1's\n * `custom.table_decorations` — \"color rows by Account Type\", \"MAXED and NEAR rules\" on\n * Arman's own Coding Accounts table) and whose views never set a `presentation.style` of\n * their own drew NOTHING, on the grid exactly as much as on every other layout, because\n * nothing in this package had ever read G1's door at all.\n *\n * TWO LAYERS, ONE DOCUMENT. The table's decorations are the base coat — what every view\n * of this table shows until a view says otherwise. A view's OWN `presentation.style` is\n * an override, piece by piece: a view that only ever set manual row highlights still\n * inherits the table's color-by and rules for everything it never touched, and a view\n * that fully redecorated (its own `colorBy` AND its own `rules`) is not haunted by a\n * table rule it meant to replace. Per-piece, never all-or-nothing, because a view is a\n * LOOK on top of the table, not a wholesale substitute for it.\n */\nexport function resolveTableStyle(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n viewStyle: TableStyle | undefined,\n): TableStyle {\n const base = styleFromDecorations(decorations, fields);\n if (!viewStyle) return base;\n const colorBy = viewStyle.colorBy ?? base.colorBy;\n const rules = viewStyle.rules ?? base.rules;\n const rows = viewStyle.rows ?? base.rows;\n const columns = viewStyle.columns ?? base.columns;\n const cells = viewStyle.cells ?? base.cells;\n return {\n version: 1,\n ...(colorBy !== undefined ? { colorBy } : {}),\n ...(rules !== undefined ? { rules } : {}),\n ...(rows !== undefined ? { rows } : {}),\n ...(columns !== undefined ? { columns } : {}),\n ...(cells !== undefined ? { cells } : {}),\n };\n}\n","\"use client\";\n\n// src/viewKeys.ts — @ai-matrx/records-ui · lane S1-PRIME VIEW-KEYS\n//\n// THE VIEW-KEY REGISTRY MOVED TO `@ai-matrx/records/views` (step 10, 2026-10-08): the declared keys, the one\n// reader and the one writer are headless there. What stays here is this package's LOOK: the grid\n// presentation (colors, formats, frozen columns — `presentation.ts`, built on design-system) bound as the\n// view's presentation codec. Every name this file exported is unchanged.\n\nimport * as base from \"@ai-matrx/records/views\";\nimport { parseGridPresentation, presentationDocument, presentationIsEmpty, type GridPresentation } from \"./presentation\";\n\nexport {\n STORE_KEEPS_TIMELINE,\n STORE_KEPT_LAYOUTS,\n VIEW_KEYS,\n storeKeepsLayout,\n viewKeysFor,\n viewSettingsFor,\n type ViewKeyDeclaration,\n type ViewKeyLayout,\n type ViewKeyWriter,\n type ViewSortKey,\n} from \"@ai-matrx/records/views\";\n\n/** This package's look, as the view's presentation codec. */\nexport const gridPresentationCodec: base.ViewPresentationCodec<GridPresentation> = {\n parse: parseGridPresentation,\n isEmpty: presentationIsEmpty,\n toDocument: (presentation) => JSON.parse(presentationDocument(presentation)) as Record<string, unknown>,\n};\n\n/** EVERY DECLARED KEY OF A VIEW, TYPED, with this package's grid look. */\nexport type ViewSettings = base.ViewSettings<GridPresentation>;\nexport type ViewSettingsPatch = base.ViewSettingsPatch<GridPresentation>;\n\n/** Every view and look door with this package's grid look bound — bindings of records' one definition. */\nexport const boundViews = base.bindViewCodec<GridPresentation>(gridPresentationCodec);\n\n/** THE READER (a stored `definition` and the row's flat `filters` in, every declared key out) and THE WRITER'S SHAPE. */\nexport const { viewSettings, viewSettingsPatch } = boundViews;\n","// src/firstPage.ts — @ai-matrx/records-ui/first-page · lane PAGE-BUNDLE-2\n//\n// THE GRID'S FIRST PAGE, ASKED BEFORE THE GRID EXISTS. The logic lives in `@ai-matrx/records/first-page` (headless,\n// step 10, 2026-10-08); this entry binds this package's grid look (`gridPresentationCodec`) so a view is read exactly\n// as the screen reads it. No React here (server-safe entry).\n\nimport { bindFirstPageCodec } from \"@ai-matrx/records/first-page\";\nimport { gridPresentationCodec } from \"./viewKeys\";\n\nexport {\n EMBEDDED_PAGE_SIZE,\n TABLE_PAGE_SIZE,\n firstGridPageArgs,\n serverRowsOf,\n viewFilterOf,\n type FirstGridPageArgs,\n} from \"@ai-matrx/records/first-page\";\n\nexport const { firstGridPageRequest, askTablePageSeed } = bindFirstPageCodec(gridPresentationCodec);\n\n// Entity blocks and chart tiles (lane SEED-ENTITY).\nexport * from \"./firstPageEntity\";\n"]}
|
package/dist/first-page.js
CHANGED
|
@@ -2,7 +2,6 @@ import { bindFirstPageCodec } from '@ai-matrx/records/first-page';
|
|
|
2
2
|
export { EMBEDDED_PAGE_SIZE, TABLE_PAGE_SIZE, askChartSeed, askEntityBlockSeed, askTileSeed, firstGridPageArgs, mergeSeeds, serverRowsOf, viewFilterOf } from '@ai-matrx/records/first-page';
|
|
3
3
|
import * as base from '@ai-matrx/records/views';
|
|
4
4
|
import { tableStyleIsEmpty, parseTableStyle } from '@ai-matrx/design-system/data-table/table-style';
|
|
5
|
-
import '@ai-matrx/design-system/data-table';
|
|
6
5
|
import '@ai-matrx/design-system/field-formats';
|
|
7
6
|
import '@ai-matrx/records';
|
|
8
7
|
|
package/dist/first-page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/presentation.ts","../src/viewKeys.ts","../src/firstPage.ts"],"names":["raw"],"mappings":"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","file":"first-page.js","sourcesContent":["/**\n * The Gantt mode of a timeline view, by Field key. `mode` turns the left task list on; `critical` paints the longest\n * chain; `progress` is a percent/number column; `milestone` a yes/no column; `baselineStart` / `baselineEnd` the two\n * date columns \"Save baseline\" writes; `dependency` the link column that dependencies are written to.\n */\nexport interface GanttLook {\n mode?: boolean;\n critical?: boolean;\n progress?: string;\n milestone?: string;\n baselineStart?: string;\n baselineEnd?: string;\n dependency?: string;\n}\n\n/** Read a stored Gantt look; unknown keys and wrong shapes are dropped. */\nexport function parseGanttLook(raw: unknown): GanttLook | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const v = raw as Record<string, unknown>;\n const out: GanttLook = {};\n if (v[\"mode\"] === true) out.mode = true;\n if (v[\"critical\"] === true) out.critical = true;\n for (const k of [\"progress\", \"milestone\", \"baselineStart\", \"baselineEnd\", \"dependency\"] as const) {\n const x = v[k];\n if (typeof x === \"string\" && x !== \"\") out[k] = x;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * HOW A TABLE LOOKS — colors, formats and the frozen column.\n *\n * THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a\n * currency, a percent, a date, a person. A VIEW declares HOW that reads and\n * what it is worth looking at: two decimal places or none, tint the row when\n * Status is Blocked, keep Name in place while the rest scrolls. Airtable draws\n * the line in exactly this place, and it has to be here: two people read one\n * table two different ways, and neither reading is a fact about the records.\n *\n * So nothing in this module is a new column on a Field and nothing here needed\n * a migration. A presentation lives on the saved-view record (`views.ts`), which\n * is an ordinary record of an ordinary Table — which means an agent sets it\n * through the same `records` tool it uses for everything else, and it travels\n * with the table the way every other record does.\n *\n * A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`\n * reads the Field's OWN declaration — a `currency` parity type with unit `$`, a\n * `percent`, a `datetime` with `config.kind` — and returns the format that\n * follows from it. The view's `formats` map only OVERRIDES that. A table nobody\n * has ever styled still shows `$1,250.50` and `45%`.\n */\n\n// The style parser from its own client-free subpath (lane SSR-ROWS): `@ai-matrx/records-ui/first-page`\n// runs in a host's SERVER component, and every export of the \"use client\" `data-table` entry is a\n// client reference there — calling `parseTableStyle` through it threw on the server.\nimport { parseTableStyle, tableStyleIsEmpty } from \"@ai-matrx/design-system/data-table/table-style\";\nimport {\n COLOR_ROTATION,\n type ChoiceColorLookup,\n type GroupAggregateKind,\n type GroupAggregateSpec,\n type GroupOrder,\n type StyleColor,\n type TableStyle,\n} from \"@ai-matrx/design-system/data-table\";\nimport { getFieldFormat, type FieldFormatConfig } from \"@ai-matrx/design-system/field-formats\";\nimport type { ColorRule } from \"@ai-matrx/design-system/data-table\";\nimport { newerKindOf, ratingMaxOf, type Field, type TableDecorations } from \"@ai-matrx/records\";\n\nimport { editorKindFor } from \"./parity\";\nimport { dateFormatFor } from \"./dates\";\n\n/**\n * SECTIONS BY A FIELD — the view's own grouping.\n *\n * `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,\n * because a view is written against the Table's declaration and never against\n * whatever id a screen happened to build its columns with.\n *\n * `aggregates` is keyed by Field key too, and `collapsed` holds the data\n * table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the\n * primitive emits and the only thing that survives a value rendering the same\n * text as another.\n *\n * NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every\n * object in JavaScript already has all three, so an optional field by one of\n * those names is NEVER absent and a `config.valueOf ? … : …` check silently\n * reads `Object.prototype`. It cost the primitive a whole grouping once.\n */\nexport type GridGrouping = {\n /** The Field key whose value makes the sections. */\n field: string;\n /** Per-Field-key subtotal on every section header. */\n aggregates?: Record<string, GroupAggregateSpec>;\n /** Section order. The primitive's default is `value-asc`; blanks sort last. */\n order?: GroupOrder;\n /** The sections this view keeps shut, by the primitive's own group key. */\n collapsed?: string[];\n /** The grouped column stays drawn as a column (Airtable). Absent = shown; `false` = the person stepped it aside. */\n showColumn?: boolean;\n /**\n * SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS \"sub-grouping\"): up to two more levels\n * inside each section, by Field key. At most three levels in all — the store refuses a fourth\n * (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,\n * because this list is parsed and written back whole with the rest of the look.\n */\n then?: GridSubGroup[];\n /**\n * DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or\n * `<key>:<grain>` for a date cut into periods.\n */\n across?: string;\n /** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */\n grain?: string;\n /** The Measures shown (\"what to display\"), by the store's own keys (`count`, `sum_quantity`). */\n show?: string[];\n /** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */\n trail?: GridDrillStep[];\n};\n\n/** One crumb of a saved drill trail. `value: null` is \"no value\". */\nexport type GridDrillStep = { dim: string; value: string | null };\n\n/** One sub-group level: a Field key and its section order. */\nexport type GridSubGroup = { field: string; order?: GroupOrder; grain?: string };\n\n/** Everything a view says about how its table LOOKS. */\nexport type GridPresentation = {\n /** Color-by-a-column, rules and manual highlights. */\n style?: TableStyle;\n /** Per-column format overrides, by the Field's key. */\n formats?: Record<string, FieldFormatConfig>;\n /**\n * The columns that stay in place while the table scrolls sideways, by Field\n * key. Only the LEADING run of the view's column order can actually freeze —\n * the data table decides that, and declines rather than pinning a column at a\n * guessed offset.\n */\n frozen?: string[];\n /** Collapsible sections by one Field, with subtotals on each header. */\n grouping?: GridGrouping;\n /**\n * The width a person dragged a column to, by Field key. SPARSE on purpose —\n * a view saved before widths existed carries none and still parses, and a\n * column nobody resized is left to the table's own measurement.\n */\n widths?: Record<string, number>;\n /** The body row height this view remembers, in px. */\n rowHeight?: number;\n /**\n * Field keys hidden from this view — the SAME hidden-column list the grid's\n * own column picker holds, now given a place on the view so it survives a\n * reload and a layout switch reads the identical set gallery and grid agree\n * on, rather than a second \"fields shown\" list nobody kept in sync.\n */\n hiddenFields?: string[];\n /**\n * THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the\n * card's own choice, kept apart from `hiddenFields` so a board's \"Card fields\" never hides the\n * grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty\n * list is a choice (every field shown); absent means the card never chose.\n */\n cardHidden?: string[];\n /**\n * THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like\n * `widths`: a key it does not name keeps its Field's own place after the named ones, and a key\n * whose Field is gone is skipped — a column order can never hide or invent a column.\n */\n columnOrder?: string[];\n /**\n * The gallery's card size, remembered the same way the grid remembers its\n * row height — a per-view look, not a global default.\n */\n gallerySize?: \"small\" | \"medium\" | \"large\";\n /**\n * THE BOARD'S OWN LOOK (lane BOARD-SWIMLANES, Notion's board options). All kept on the view, all\n * read only by the kanban layout. Absent means the board's default: columns wear their choice's\n * colour, each heading shows its count, empty columns and lanes are drawn, cards are medium with\n * no preview.\n * boardSize card size: sets the column width, the preview's height and, small, drops the card's extras\n * boardPreview the FILE field drawn as a cover at the top of each card (absent = no preview)\n * boardHideEmpty leave out a column or a lane that holds no card\n * boardColorColumns false = neutral headings, no accent bar on the cards\n * boardCounts false = no count beside the heading\n * boardLanesShut the swimlanes kept shut to a strip, by the lane's name\n */\n boardSize?: \"small\" | \"medium\" | \"large\";\n boardPreview?: string;\n boardHideEmpty?: boolean;\n boardColorColumns?: boolean;\n boardCounts?: boolean;\n boardLanesShut?: string[];\n /**\n * A LOCKED VIEW (lane NOTION-SMALL-2): its layout, filters, sorts and settings are not saved over until\n * somebody unlocks it. An editing convenience against an accidental change, never a permission.\n */\n locked?: boolean;\n /**\n * THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane\n * DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means \"whatever the organization\n * says\"; present is the view's word. `freezeFirst: false` is an explicit NO over an\n * organization that freezes the first column — an empty `frozen` cannot say that.\n */\n wrap?: boolean;\n /** NOTION'S \"WRAP COLUMN\" (N5): the columns that wrap their text when the view as a whole does not. */\n wrapColumns?: string[];\n fit?: \"auto\" | \"fit\" | \"scroll\";\n freezeFirst?: boolean;\n /**\n * THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column\n * (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out\n * by `custom.record_aggregate` over the whole table.\n */\n summaries?: Record<string, string>;\n /** THE TIMELINE'S GANTT MODE (kept as `presentation.gantt`): see `GanttLook`. */\n gantt?: GanttLook;\n /**\n * WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the\n * pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's\n * way, and the default when a view says nothing), or `inline`, right after the last row with\n * the page scrolling. Read by the Grid and a host's Sheet.\n */\n footer?: FooterPlacement;\n};\n\n/** Where a grid's footer sits. */\nexport type FooterPlacement = \"sticky\" | \"inline\";\n\n/** What a view that says nothing gets — the older grid's pinned footer. */\nexport const DEFAULT_FOOTER: FooterPlacement = \"sticky\";\n\n/** Where THIS view's footer sits, said or defaulted. */\nexport function footerOf(p: GridPresentation | null | undefined): FooterPlacement {\n return p?.footer ?? DEFAULT_FOOTER;\n}\n\nexport const EMPTY_PRESENTATION: GridPresentation = {};\n\n/** A frozen column needs a known width, or the table declines to freeze at all. */\nexport const FROZEN_COLUMN_WIDTH = 200;\n\n/**\n * WHAT A ROW HEIGHT IS ALLOWED TO BE. Below the floor the text is clipped and\n * no editor fits; above the ceiling one record fills the screen. A view asking\n * for anything outside it is DROPPED rather than obeyed, because a saved look\n * must not be able to make a table unreadable — and the Fields' own heights\n * still draw, which is the tolerant half.\n */\nexport const MIN_ROW_HEIGHT = 24;\nexport const MAX_ROW_HEIGHT = 240;\n\n/**\n * The aggregate kinds and orders the primitive actually implements. Listed here\n * because they are the tolerant parser's allow-list and a view is hostile input:\n * `satisfies` ties them to the primitive's own union, so a kind it renames stops\n * this file compiling instead of quietly parsing into a subtotal nobody draws.\n */\nconst AGGREGATE_KINDS = [\n \"count\",\n \"sum\",\n \"average\",\n \"min\",\n \"max\",\n \"empty\",\n \"filled\",\n \"unique\",\n \"checked\",\n \"unchecked\",\n \"percentFilled\",\n] as const satisfies readonly GroupAggregateKind[];\n\nconst GROUP_ORDERS = [\n \"value-asc\",\n \"value-desc\",\n \"count-desc\",\n \"first-seen\",\n] as const satisfies readonly GroupOrder[];\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Read a presentation out of whatever the view record holds — an object, or the\n * JSON string a text field stores.\n *\n * Tolerant in exactly the way `parseTableStyle` is: an unreadable half is\n * dropped and the rest still applies, because a table must keep drawing when\n * one piece of its styling goes out of date.\n */\nexport function parseGridPresentation(raw: unknown): GridPresentation {\n let value = raw;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return {};\n try {\n value = JSON.parse(trimmed);\n } catch {\n return {};\n }\n }\n if (!isRecord(value)) return {};\n\n const out: GridPresentation = {};\n\n const style = parseTableStyle(value[\"style\"]);\n if (!tableStyleIsEmpty(style)) out.style = style;\n\n if (isRecord(value[\"formats\"])) {\n const formats: Record<string, FieldFormatConfig> = {};\n for (const [key, cfg] of Object.entries(value[\"formats\"])) {\n if (!isRecord(cfg)) continue;\n const id = cfg[\"id\"];\n if (typeof id !== \"string\") continue;\n const options = cfg[\"options\"];\n formats[key] = isRecord(options)\n ? {\n id: id as FieldFormatConfig[\"id\"],\n options: options as NonNullable<FieldFormatConfig[\"options\"]>,\n }\n : { id: id as FieldFormatConfig[\"id\"] };\n }\n if (Object.keys(formats).length > 0) out.formats = formats;\n }\n\n if (Array.isArray(value[\"frozen\"])) {\n const frozen = value[\"frozen\"].filter((k): k is string => typeof k === \"string\");\n if (frozen.length > 0) out.frozen = frozen;\n }\n\n const grouping = parseGrouping(value[\"grouping\"]);\n if (grouping) out.grouping = grouping;\n\n if (isRecord(value[\"widths\"])) {\n const widths: Record<string, number> = {};\n for (const [key, raw] of Object.entries(value[\"widths\"])) {\n // A width that is not a usable number is one column's loss, never the\n // view's: the rest of the widths still apply and that column is measured.\n if (typeof raw !== \"number\" || !Number.isFinite(raw) || raw <= 0) continue;\n widths[key] = Math.round(raw);\n }\n if (Object.keys(widths).length > 0) out.widths = widths;\n }\n\n const rowHeight = value[\"rowHeight\"];\n if (\n typeof rowHeight === \"number\" &&\n Number.isFinite(rowHeight) &&\n rowHeight >= MIN_ROW_HEIGHT &&\n rowHeight <= MAX_ROW_HEIGHT\n ) {\n out.rowHeight = Math.round(rowHeight);\n }\n\n if (Array.isArray(value[\"hiddenFields\"])) {\n const hiddenFields = value[\"hiddenFields\"].filter((k): k is string => typeof k === \"string\");\n if (hiddenFields.length > 0) out.hiddenFields = hiddenFields;\n }\n if (Array.isArray(value[\"cardHidden\"])) {\n out.cardHidden = value[\"cardHidden\"].filter((k): k is string => typeof k === \"string\");\n }\n\n if (Array.isArray(value[\"columnOrder\"])) {\n const columnOrder = value[\"columnOrder\"].filter((k): k is string => typeof k === \"string\");\n if (columnOrder.length > 0) out.columnOrder = columnOrder;\n }\n\n const gallerySize = value[\"gallerySize\"];\n if (gallerySize === \"small\" || gallerySize === \"medium\" || gallerySize === \"large\") {\n out.gallerySize = gallerySize;\n }\n\n const boardSize = value[\"boardSize\"];\n if (boardSize === \"small\" || boardSize === \"medium\" || boardSize === \"large\") out.boardSize = boardSize;\n if (typeof value[\"boardPreview\"] === \"string\" && value[\"boardPreview\"] !== \"\") out.boardPreview = value[\"boardPreview\"];\n if (typeof value[\"boardHideEmpty\"] === \"boolean\") out.boardHideEmpty = value[\"boardHideEmpty\"];\n if (typeof value[\"boardColorColumns\"] === \"boolean\") out.boardColorColumns = value[\"boardColorColumns\"];\n if (typeof value[\"boardCounts\"] === \"boolean\") out.boardCounts = value[\"boardCounts\"];\n if (value[\"locked\"] === true) out.locked = true;\n if (Array.isArray(value[\"boardLanesShut\"])) {\n const shut = value[\"boardLanesShut\"].filter((k): k is string => typeof k === \"string\");\n if (shut.length > 0) out.boardLanesShut = shut;\n }\n\n if (typeof value[\"wrap\"] === \"boolean\") out.wrap = value[\"wrap\"];\n if (Array.isArray(value[\"wrapColumns\"])) {\n const wrapColumns = value[\"wrapColumns\"].filter((k): k is string => typeof k === \"string\");\n if (wrapColumns.length > 0) out.wrapColumns = wrapColumns;\n }\n const fit = value[\"fit\"];\n if (fit === \"auto\" || fit === \"fit\" || fit === \"scroll\") out.fit = fit;\n if (typeof value[\"freezeFirst\"] === \"boolean\") out.freezeFirst = value[\"freezeFirst\"];\n const footer = value[\"footer\"];\n if (footer === \"sticky\" || footer === \"inline\") out.footer = footer;\n {\n const gantt = parseGanttLook(value[\"gantt\"]);\n if (gantt) out.gantt = gantt;\n }\n if (isRecord(value[\"summaries\"])) {\n const summaries: Record<string, string> = {};\n for (const [key, op] of Object.entries(value[\"summaries\"])) {\n // A measure name is judged by the store when it is asked; a non-string is nobody's.\n if (typeof op === \"string\" && op.trim() !== \"\") summaries[key] = op;\n }\n if (Object.keys(summaries).length > 0) out.summaries = summaries;\n }\n\n return out;\n}\n\n/**\n * A grouping, or nothing. A grouping with no Field to group BY is not a partial\n * grouping — it is no grouping at all, so it is dropped whole; an unreadable\n * subtotal or a section order this primitive does not have costs only itself.\n */\nfunction parseGrouping(raw: unknown): GridGrouping | undefined {\n if (!isRecord(raw)) return undefined;\n const field = raw[\"field\"];\n if (typeof field !== \"string\" || field.trim() === \"\") return undefined;\n\n const out: GridGrouping = { field };\n\n if (isRecord(raw[\"aggregates\"])) {\n const aggregates: Record<string, GroupAggregateSpec> = {};\n for (const [key, spec] of Object.entries(raw[\"aggregates\"])) {\n if (!isRecord(spec)) continue;\n const kind = spec[\"kind\"];\n if (typeof kind !== \"string\") continue;\n if (!(AGGREGATE_KINDS as readonly string[]).includes(kind)) continue;\n const label = spec[\"label\"];\n aggregates[key] =\n typeof label === \"string\"\n ? { kind: kind as GroupAggregateKind, label }\n : { kind: kind as GroupAggregateKind };\n }\n if (Object.keys(aggregates).length > 0) out.aggregates = aggregates;\n }\n\n const order = raw[\"order\"];\n if (typeof order === \"string\" && (GROUP_ORDERS as readonly string[]).includes(order)) {\n out.order = order as GroupOrder;\n }\n\n if (Array.isArray(raw[\"collapsed\"])) {\n const collapsed = raw[\"collapsed\"].filter((k): k is string => typeof k === \"string\");\n if (collapsed.length > 0) out.collapsed = collapsed;\n }\n\n if (typeof raw[\"showColumn\"] === \"boolean\") out.showColumn = raw[\"showColumn\"];\n\n if (Array.isArray(raw[\"then\"])) {\n const then: GridSubGroup[] = [];\n for (const level of raw[\"then\"]) {\n if (!isRecord(level)) continue;\n const sub = level[\"field\"];\n if (typeof sub !== \"string\" || sub.trim() === \"\") continue;\n const subOrder = level[\"order\"];\n const subGrain = level[\"grain\"];\n then.push({\n field: sub,\n ...(typeof subOrder === \"string\" && (GROUP_ORDERS as readonly string[]).includes(subOrder)\n ? { order: subOrder as GroupOrder }\n : {}),\n ...(typeof subGrain === \"string\" && subGrain !== \"\" ? { grain: subGrain } : {}),\n });\n }\n if (then.length > 0) out.then = then.slice(0, 2);\n }\n\n const grain = raw[\"grain\"];\n if (typeof grain === \"string\" && grain !== \"\") out.grain = grain;\n const across = raw[\"across\"];\n if (typeof across === \"string\" && across.trim() !== \"\") out.across = across;\n if (Array.isArray(raw[\"show\"])) {\n const show = raw[\"show\"].filter((k): k is string => typeof k === \"string\" && k !== \"\");\n if (show.length > 0) out.show = show;\n }\n if (Array.isArray(raw[\"trail\"])) {\n const trail: GridDrillStep[] = [];\n for (const step of raw[\"trail\"]) {\n if (!isRecord(step) || typeof step[\"dim\"] !== \"string\" || step[\"dim\"] === \"\") continue;\n const value = step[\"value\"];\n if (value !== null && typeof value !== \"string\") continue;\n trail.push({ dim: step[\"dim\"], value });\n }\n if (trail.length > 0) out.trail = trail;\n }\n\n return out;\n}\n\n/** True when nothing here would change how anything looks. */\nexport function presentationIsEmpty(p: GridPresentation): boolean {\n return (\n (p.style === undefined || tableStyleIsEmpty(p.style)) &&\n Object.keys(p.formats ?? {}).length === 0 &&\n (p.frozen?.length ?? 0) === 0 &&\n p.grouping === undefined &&\n Object.keys(p.widths ?? {}).length === 0 &&\n p.rowHeight === undefined &&\n (p.hiddenFields?.length ?? 0) === 0 &&\n p.cardHidden === undefined &&\n (p.columnOrder?.length ?? 0) === 0 &&\n p.gallerySize === undefined &&\n p.boardSize === undefined &&\n p.boardPreview === undefined &&\n p.boardHideEmpty === undefined &&\n p.boardColorColumns === undefined &&\n p.boardCounts === undefined &&\n (p.boardLanesShut?.length ?? 0) === 0 &&\n p.locked === undefined &&\n p.wrap === undefined &&\n p.fit === undefined &&\n p.freezeFirst === undefined &&\n p.footer === undefined &&\n Object.keys(p.summaries ?? {}).length === 0 &&\n p.gantt === undefined\n );\n}\n\n/** The string a view record stores. Empty string for \"this view styles nothing\". */\nexport function presentationDocument(p: GridPresentation): string {\n return presentationIsEmpty(p) ? \"\" : JSON.stringify(p);\n}\n\n/** Kinds whose value is a control the record renderer draws, never a formatter's string. */\nconst DRAWN_KINDS = new Set([\"select\", \"multi_select\", \"relation\", \"member\", \"person\", \"attachment\", \"file\", \"entity_reference\", \"checkbox\", \"boolean\", \"signature\", \"json\"]);\n\n/**\n * The display format a column's settings chose for it, when it is one the platform registry\n * knows and the column is not one the record renderer draws itself. `undefined` otherwise.\n */\nexport function shownAsOf(field: Field): FieldFormatConfig | undefined {\n if (DRAWN_KINDS.has(String(editorKindFor(field)))) return undefined;\n const raw = (field as unknown as { display_format?: unknown }).display_format;\n if (!raw || typeof raw !== \"object\") return undefined;\n const id = (raw as { id?: unknown }).id;\n // `formula` is the column's KIND, never a look: a worked-out column shows as its result format.\n if (typeof id !== \"string\" || id === \"formula\" || !getFieldFormat(id)) return undefined;\n const options = (raw as { options?: unknown }).options;\n return options && typeof options === \"object\" && Object.keys(options).length > 0\n ? ({ id, options } as FieldFormatConfig)\n : ({ id } as FieldFormatConfig);\n}\n\n/**\n * The format a Field's OWN declaration implies, before any view says anything.\n *\n * Returns `undefined` for a Field whose value the record renderer already draws\n * better than a formatter could — a relation chip, a person, an attachment, a\n * choice — because handing those to a text formatter would REPLACE a working\n * control with a string.\n */\nexport function formatForField(field: Field): FieldFormatConfig | undefined {\n // \"SHOWS AS\" — THE FORMAT THE COLUMN'S OWN SETTINGS CHOSE (merge tranche 6a/6e). The column\n // keeps it on the Field (`display_format`, written by `custom.field_update`), so the grid, the\n // record panel and the export read one answer. A column the record renderer draws better than\n // a formatter (a choice, a link, a person, a file) keeps its own drawing.\n const shownAs = shownAsOf(field);\n // A RATING'S TOP STAR IS ITS MAX RULE (lane 10 P4), so changing the rule changes the stars.\n if (newerKindOf(field) === \"rating\" && (!shownAs || shownAs.id === \"rating\")) {\n return { id: \"rating\", options: { ...(shownAs?.options ?? {}), ratingMax: ratingMaxOf(field) } };\n }\n if (shownAs) return shownAs;\n // THE STORE'S OWN STAMPS (Created time, Last changed time) are instants and print like\n // every other date-and-time cell — `dates.ts` answers, the same helper every card reads.\n const stamp = dateFormatFor(field);\n if (stamp && editorKindFor(field) !== \"datetime\") return stamp;\n switch (editorKindFor(field)) {\n case \"currency\":\n return {\n id: \"currency\",\n options: { currency: currencyCodeFor(field.unit) },\n };\n case \"percent\":\n return { id: \"percent\" };\n case \"datetime\":\n // The COLUMN's declaration: a `kind: \"date\"` column is a date column, anything else can\n // hold a moment. Each VALUE is still printed in its own shape (`RecordValue` →\n // `dates.dateFormatFor(field, value)`), so a bare date in a datetime column reads as a date.\n return String(field.config?.[\"kind\"] ?? \"\") === \"date\" ? { id: \"date\" } : { id: \"datetime\" };\n case \"email\":\n return { id: \"email\" };\n case \"url\":\n return { id: \"url\" };\n case \"phone\":\n return { id: \"phone\" };\n case \"number\":\n return { id: \"number\" };\n default:\n return undefined;\n }\n}\n\n/**\n * THE FORMAT ONE CELL OF A COLUMN IS DRAWN IN — the grid's cell and the gallery's card alike\n * (guard G4 \"one format\", lane VIEWS-AND-FIELDS W1-B): the view's own format for the column when\n * it set one, else the Field's own (`formatForField`). One function, so no layout formats money\n * (or a date, or a percent) its own way.\n */\nexport function cellFormatFor(field: Field, viewFormat?: FieldFormatConfig | undefined): FieldFormatConfig | undefined {\n return viewFormat ?? formatForField(field);\n}\n\n/**\n * The store lets a person type the SYMBOL they think in (`$`, `€`), and\n * `Intl.NumberFormat` wants the ISO code. The symbols people actually enter are\n * mapped; anything else is passed straight through, because a real ISO code is\n * the other thing that lands in this field, and an unknown code still renders\n * (`1,234.00 XYZ`) rather than blanking — THE FALLBACK LAW.\n */\nconst SYMBOL_TO_CODE: Record<string, string> = {\n $: \"USD\",\n \"£\": \"GBP\",\n \"€\": \"EUR\",\n \"¥\": \"JPY\",\n \"₹\": \"INR\",\n \"₽\": \"RUB\",\n \"₩\": \"KRW\",\n \"R$\": \"BRL\",\n \"C$\": \"CAD\",\n \"A$\": \"AUD\",\n};\n\nexport function currencyCodeFor(unit: string | null | undefined): string {\n const raw = (unit ?? \"\").trim();\n if (raw === \"\") return \"USD\";\n return SYMBOL_TO_CODE[raw] ?? raw.toUpperCase();\n}\n\n/**\n * The color a value paints with when a view colors BY a column.\n *\n * NOTHING FAILS SILENTLY, so this never returns \"no color\" for a value that\n * exists. The store keeps a list column's choices as records of another Table,\n * and this grid has not read that Table's option colors, so the color is\n * derived from the value's own text: the same word always gets the same color,\n * different words get different ones, and `true` is green the way every\n * checklist reads. When those option colors do land, this is the ONE function\n * that changes and every screen follows.\n */\nexport const colorFromTheValue: ChoiceColorLookup = (_fieldKey, value) => {\n if (value === true) return \"green\";\n if (value === false || value === null || value === undefined) return undefined;\n const text = String(value).trim().toLowerCase();\n if (text === \"\") return undefined;\n let hash = 0;\n for (let i = 0; i < text.length; i += 1) {\n hash = (hash * 31 + text.charCodeAt(i)) >>> 0;\n }\n return COLOR_ROTATION[hash % COLOR_ROTATION.length] as StyleColor;\n};\n\n/**\n * G1's TABLE-LEVEL COLORS (`custom.table_decorations` / `client.tableDecorations`),\n * TRANSLATED INTO A `TableStyle` — the same document a saved view's own `presentation.style`\n * is, so one resolver (`resolveTableStyle`, below) can treat \"the table's colors\" and \"this\n * view's colors\" as two layers of the SAME thing instead of two unrelated systems.\n *\n * THE TRANSLATION IS NECESSARY AND IT IS THE ONLY ONE. G1 addresses a Field by its ID\n * (REC-17 — a column renamed keeps its color), because it is the STORE's own door; a\n * `TableStyle` addresses a Field by its KEY, because it reads straight off `row.document`,\n * which is keyed by Field key. A rule or a color-by whose Field has been deleted (its id no\n * longer resolves to a key) is dropped rather than crashing the whole style — a defect in\n * one rule costs that rule, never the read.\n */\nexport function styleFromDecorations(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n): TableStyle {\n if (!decorations) return { version: 1 };\n const keyOf = (id: string) => fields.find((f) => f.id === id)?.key;\n const style: TableStyle = { version: 1 };\n\n if (decorations.color_by?.field) {\n const field = keyOf(decorations.color_by.field);\n if (field) style.colorBy = { field, target: decorations.color_by.target };\n }\n\n if (Array.isArray(decorations.rules) && decorations.rules.length > 0) {\n const rules: ColorRule[] = [];\n decorations.rules.forEach((rule, index) => {\n const field = keyOf(rule.field);\n if (!field) return;\n rules.push({\n id: rule.id ?? `decoration:${index}`,\n field,\n op: rule.op,\n color: rule.color,\n target: rule.target,\n ...(rule.value === undefined || rule.value === null ? {} : { value: String(rule.value) }),\n });\n });\n if (rules.length > 0) style.rules = rules;\n }\n\n if (decorations.rows && Object.keys(decorations.rows).length > 0) {\n style.rows = { ...decorations.rows };\n }\n\n const columns: Record<string, StyleColor> = {};\n for (const [id, color] of Object.entries(decorations.columns ?? {})) {\n const key = keyOf(id);\n if (key) columns[key] = color;\n }\n if (Object.keys(columns).length > 0) style.columns = columns;\n\n const cells: Record<string, Record<string, StyleColor>> = {};\n for (const [rowId, byField] of Object.entries(decorations.cells ?? {})) {\n for (const [id, color] of Object.entries(byField ?? {})) {\n const key = keyOf(id);\n if (!key) continue;\n (cells[rowId] ??= {})[key] = color;\n }\n }\n if (Object.keys(cells).length > 0) style.cells = cells;\n\n return style;\n}\n\n/**\n * THE ONE PRESENTATION RESOLVER every colored surface in this package reads —\n * `Grid`, and (via `Card`) the kanban board, the calendar and the gallery.\n *\n * THE CLASS THIS CLOSES (lane UI-QUICK-1, TABLE-PARITY review, 2026-09-24). Threading\n * `view.presentation.style` into `Card` closed the instance the CALENDAR-GALLERY bench\n * found — a VIEW that had its own color-by looked right everywhere — but it left the\n * class open: a table whose colors are the ORGANIZATION's decorations (G1's\n * `custom.table_decorations` — \"color rows by Account Type\", \"MAXED and NEAR rules\" on\n * Arman's own Coding Accounts table) and whose views never set a `presentation.style` of\n * their own drew NOTHING, on the grid exactly as much as on every other layout, because\n * nothing in this package had ever read G1's door at all.\n *\n * TWO LAYERS, ONE DOCUMENT. The table's decorations are the base coat — what every view\n * of this table shows until a view says otherwise. A view's OWN `presentation.style` is\n * an override, piece by piece: a view that only ever set manual row highlights still\n * inherits the table's color-by and rules for everything it never touched, and a view\n * that fully redecorated (its own `colorBy` AND its own `rules`) is not haunted by a\n * table rule it meant to replace. Per-piece, never all-or-nothing, because a view is a\n * LOOK on top of the table, not a wholesale substitute for it.\n */\nexport function resolveTableStyle(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n viewStyle: TableStyle | undefined,\n): TableStyle {\n const base = styleFromDecorations(decorations, fields);\n if (!viewStyle) return base;\n const colorBy = viewStyle.colorBy ?? base.colorBy;\n const rules = viewStyle.rules ?? base.rules;\n const rows = viewStyle.rows ?? base.rows;\n const columns = viewStyle.columns ?? base.columns;\n const cells = viewStyle.cells ?? base.cells;\n return {\n version: 1,\n ...(colorBy !== undefined ? { colorBy } : {}),\n ...(rules !== undefined ? { rules } : {}),\n ...(rows !== undefined ? { rows } : {}),\n ...(columns !== undefined ? { columns } : {}),\n ...(cells !== undefined ? { cells } : {}),\n };\n}\n","\"use client\";\n\n// src/viewKeys.ts — @ai-matrx/records-ui · lane S1-PRIME VIEW-KEYS\n//\n// THE VIEW-KEY REGISTRY MOVED TO `@ai-matrx/records/views` (step 10, 2026-10-08): the declared keys, the one\n// reader and the one writer are headless there. What stays here is this package's LOOK: the grid\n// presentation (colors, formats, frozen columns — `presentation.ts`, built on design-system) bound as the\n// view's presentation codec. Every name this file exported is unchanged.\n\nimport * as base from \"@ai-matrx/records/views\";\nimport { parseGridPresentation, presentationDocument, presentationIsEmpty, type GridPresentation } from \"./presentation\";\n\nexport {\n STORE_KEEPS_TIMELINE,\n STORE_KEPT_LAYOUTS,\n VIEW_KEYS,\n storeKeepsLayout,\n viewKeysFor,\n viewSettingsFor,\n type ViewKeyDeclaration,\n type ViewKeyLayout,\n type ViewKeyWriter,\n type ViewSortKey,\n} from \"@ai-matrx/records/views\";\n\n/** This package's look, as the view's presentation codec. */\nexport const gridPresentationCodec: base.ViewPresentationCodec<GridPresentation> = {\n parse: parseGridPresentation,\n isEmpty: presentationIsEmpty,\n toDocument: (presentation) => JSON.parse(presentationDocument(presentation)) as Record<string, unknown>,\n};\n\n/** EVERY DECLARED KEY OF A VIEW, TYPED, with this package's grid look. */\nexport type ViewSettings = base.ViewSettings<GridPresentation>;\nexport type ViewSettingsPatch = base.ViewSettingsPatch<GridPresentation>;\n\n/** Every view and look door with this package's grid look bound — bindings of records' one definition. */\nexport const boundViews = base.bindViewCodec<GridPresentation>(gridPresentationCodec);\n\n/** THE READER (a stored `definition` and the row's flat `filters` in, every declared key out) and THE WRITER'S SHAPE. */\nexport const { viewSettings, viewSettingsPatch } = boundViews;\n","// src/firstPage.ts — @ai-matrx/records-ui/first-page · lane PAGE-BUNDLE-2\n//\n// THE GRID'S FIRST PAGE, ASKED BEFORE THE GRID EXISTS. The logic lives in `@ai-matrx/records/first-page` (headless,\n// step 10, 2026-10-08); this entry binds this package's grid look (`gridPresentationCodec`) so a view is read exactly\n// as the screen reads it. No React here (server-safe entry).\n\nimport { bindFirstPageCodec } from \"@ai-matrx/records/first-page\";\nimport { gridPresentationCodec } from \"./viewKeys\";\n\nexport {\n EMBEDDED_PAGE_SIZE,\n TABLE_PAGE_SIZE,\n firstGridPageArgs,\n serverRowsOf,\n viewFilterOf,\n type FirstGridPageArgs,\n} from \"@ai-matrx/records/first-page\";\n\nexport const { firstGridPageRequest, askTablePageSeed } = bindFirstPageCodec(gridPresentationCodec);\n\n// Entity blocks and chart tiles (lane SEED-ENTITY).\nexport * from \"./firstPageEntity\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/presentation.ts","../src/viewKeys.ts","../src/firstPage.ts"],"names":["raw"],"mappings":"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","file":"first-page.js","sourcesContent":["/**\n * The Gantt mode of a timeline view, by Field key. `mode` turns the left task list on; `critical` paints the longest\n * chain; `progress` is a percent/number column; `milestone` a yes/no column; `baselineStart` / `baselineEnd` the two\n * date columns \"Save baseline\" writes; `dependency` the link column that dependencies are written to.\n */\nexport interface GanttLook {\n mode?: boolean;\n critical?: boolean;\n progress?: string;\n milestone?: string;\n baselineStart?: string;\n baselineEnd?: string;\n dependency?: string;\n}\n\n/** Read a stored Gantt look; unknown keys and wrong shapes are dropped. */\nexport function parseGanttLook(raw: unknown): GanttLook | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const v = raw as Record<string, unknown>;\n const out: GanttLook = {};\n if (v[\"mode\"] === true) out.mode = true;\n if (v[\"critical\"] === true) out.critical = true;\n for (const k of [\"progress\", \"milestone\", \"baselineStart\", \"baselineEnd\", \"dependency\"] as const) {\n const x = v[k];\n if (typeof x === \"string\" && x !== \"\") out[k] = x;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * HOW A TABLE LOOKS — colors, formats and the frozen column.\n *\n * THE SPLIT, AND WHY IT IS THIS ONE. A Field declares WHAT it holds: a\n * currency, a percent, a date, a person. A VIEW declares HOW that reads and\n * what it is worth looking at: two decimal places or none, tint the row when\n * Status is Blocked, keep Name in place while the rest scrolls. Airtable draws\n * the line in exactly this place, and it has to be here: two people read one\n * table two different ways, and neither reading is a fact about the records.\n *\n * So nothing in this module is a new column on a Field and nothing here needed\n * a migration. A presentation lives on the saved-view record (`views.ts`), which\n * is an ordinary record of an ordinary Table — which means an agent sets it\n * through the same `records` tool it uses for everything else, and it travels\n * with the table the way every other record does.\n *\n * A FIELD STILL GETS ITS MONEY RIGHT WITH NO CONFIGURATION. `formatForField`\n * reads the Field's OWN declaration — a `currency` parity type with unit `$`, a\n * `percent`, a `datetime` with `config.kind` — and returns the format that\n * follows from it. The view's `formats` map only OVERRIDES that. A table nobody\n * has ever styled still shows `$1,250.50` and `45%`.\n */\n\n// The style parser from its own client-free subpath (lane SSR-ROWS): `@ai-matrx/records-ui/first-page`\n// runs in a host's SERVER component, and every export of the \"use client\" `data-table` entry is a\n// client reference there — calling `parseTableStyle` through it threw on the server.\nimport { COLOR_ROTATION, parseTableStyle, tableStyleIsEmpty } from \"@ai-matrx/design-system/data-table/table-style\";\nimport type {\n ChoiceColorLookup,\n GroupAggregateKind,\n GroupAggregateSpec,\n GroupOrder,\n StyleColor,\n TableStyle,\n} from \"@ai-matrx/design-system/data-table\";\nimport { getFieldFormat, type FieldFormatConfig } from \"@ai-matrx/design-system/field-formats\";\nimport type { ColorRule } from \"@ai-matrx/design-system/data-table\";\nimport { newerKindOf, ratingMaxOf, type Field, type TableDecorations } from \"@ai-matrx/records\";\n\nimport { editorKindFor } from \"./parity\";\nimport { dateFormatFor } from \"./dates\";\n\n/**\n * SECTIONS BY A FIELD — the view's own grouping.\n *\n * `field` is a Field KEY, exactly like `frozen` and the keys of `formats`,\n * because a view is written against the Table's declaration and never against\n * whatever id a screen happened to build its columns with.\n *\n * `aggregates` is keyed by Field key too, and `collapsed` holds the data\n * table's own group keys (`s:Blocked`, `n:3`, `empty`), which is what the\n * primitive emits and the only thing that survives a value rendering the same\n * text as another.\n *\n * NOTHING HERE IS EVER CALLED `valueOf`, `toString` or `hasOwnProperty`: every\n * object in JavaScript already has all three, so an optional field by one of\n * those names is NEVER absent and a `config.valueOf ? … : …` check silently\n * reads `Object.prototype`. It cost the primitive a whole grouping once.\n */\nexport type GridGrouping = {\n /** The Field key whose value makes the sections. */\n field: string;\n /** Per-Field-key subtotal on every section header. */\n aggregates?: Record<string, GroupAggregateSpec>;\n /** Section order. The primitive's default is `value-asc`; blanks sort last. */\n order?: GroupOrder;\n /** The sections this view keeps shut, by the primitive's own group key. */\n collapsed?: string[];\n /** The grouped column stays drawn as a column (Airtable). Absent = shown; `false` = the person stepped it aside. */\n showColumn?: boolean;\n /**\n * SUB-GROUPS (lane S1-PRIME VIEW-KEYS; ui-bench LISTS \"sub-grouping\"): up to two more levels\n * inside each section, by Field key. At most three levels in all — the store refuses a fourth\n * (`custom.view_declare`). A layout that draws one level ignores the rest; it never drops them,\n * because this list is parsed and written back whole with the rest of the look.\n */\n then?: GridSubGroup[];\n /**\n * DRILL (lane DRILL-TABLE-PRIMITIVE): the Dimension pivoted across the top — a Field key, or\n * `<key>:<grain>` for a date cut into periods.\n */\n across?: string;\n /** DRILL: the period `field` is cut into when it is a date (`month`); the store keeps `field` a Field key. */\n grain?: string;\n /** The Measures shown (\"what to display\"), by the store's own keys (`count`, `sum_quantity`). */\n show?: string[];\n /** The drill trail, in order: each crumb a Dimension reference and the value it narrowed to. */\n trail?: GridDrillStep[];\n};\n\n/** One crumb of a saved drill trail. `value: null` is \"no value\". */\nexport type GridDrillStep = { dim: string; value: string | null };\n\n/** One sub-group level: a Field key and its section order. */\nexport type GridSubGroup = { field: string; order?: GroupOrder; grain?: string };\n\n/** Everything a view says about how its table LOOKS. */\nexport type GridPresentation = {\n /** Color-by-a-column, rules and manual highlights. */\n style?: TableStyle;\n /** Per-column format overrides, by the Field's key. */\n formats?: Record<string, FieldFormatConfig>;\n /**\n * The columns that stay in place while the table scrolls sideways, by Field\n * key. Only the LEADING run of the view's column order can actually freeze —\n * the data table decides that, and declines rather than pinning a column at a\n * guessed offset.\n */\n frozen?: string[];\n /** Collapsible sections by one Field, with subtotals on each header. */\n grouping?: GridGrouping;\n /**\n * The width a person dragged a column to, by Field key. SPARSE on purpose —\n * a view saved before widths existed carries none and still parses, and a\n * column nobody resized is left to the table's own measurement.\n */\n widths?: Record<string, number>;\n /** The body row height this view remembers, in px. */\n rowHeight?: number;\n /**\n * Field keys hidden from this view — the SAME hidden-column list the grid's\n * own column picker holds, now given a place on the view so it survives a\n * reload and a layout switch reads the identical set gallery and grid agree\n * on, rather than a second \"fields shown\" list nobody kept in sync.\n */\n hiddenFields?: string[];\n /**\n * THE FIELDS A CARD LEAVES OFF (board, list, gallery, calendar, timeline), by Field key — the\n * card's own choice, kept apart from `hiddenFields` so a board's \"Card fields\" never hides the\n * grid's columns when the view switches layout (v7 TABLE-EXPERIENCE, walk 2026-10-05). An empty\n * list is a choice (every field shown); absent means the card never chose.\n */\n cardHidden?: string[];\n /**\n * THE ORDER A PERSON DRAGGED THE COLUMNS INTO, by Field key (merge tranche 6a). Sparse like\n * `widths`: a key it does not name keeps its Field's own place after the named ones, and a key\n * whose Field is gone is skipped — a column order can never hide or invent a column.\n */\n columnOrder?: string[];\n /**\n * The gallery's card size, remembered the same way the grid remembers its\n * row height — a per-view look, not a global default.\n */\n gallerySize?: \"small\" | \"medium\" | \"large\";\n /**\n * THE BOARD'S OWN LOOK (lane BOARD-SWIMLANES, Notion's board options). All kept on the view, all\n * read only by the kanban layout. Absent means the board's default: columns wear their choice's\n * colour, each heading shows its count, empty columns and lanes are drawn, cards are medium with\n * no preview.\n * boardSize card size: sets the column width, the preview's height and, small, drops the card's extras\n * boardPreview the FILE field drawn as a cover at the top of each card (absent = no preview)\n * boardHideEmpty leave out a column or a lane that holds no card\n * boardColorColumns false = neutral headings, no accent bar on the cards\n * boardCounts false = no count beside the heading\n * boardLanesShut the swimlanes kept shut to a strip, by the lane's name\n */\n boardSize?: \"small\" | \"medium\" | \"large\";\n boardPreview?: string;\n boardHideEmpty?: boolean;\n boardColorColumns?: boolean;\n boardCounts?: boolean;\n boardLanesShut?: string[];\n /**\n * A LOCKED VIEW (lane NOTION-SMALL-2): its layout, filters, sorts and settings are not saved over until\n * somebody unlocks it. An editing convenience against an accidental change, never a permission.\n */\n locked?: boolean;\n /**\n * THIS VIEW'S OWN LAYOUT CHOICES, over the organization's `custom/grid_layout` knob (lane\n * DEFAULT-GRID-PARITY, TABLE-PARITY D1 D5 D6). Absent means \"whatever the organization\n * says\"; present is the view's word. `freezeFirst: false` is an explicit NO over an\n * organization that freezes the first column — an empty `frozen` cannot say that.\n */\n wrap?: boolean;\n /** NOTION'S \"WRAP COLUMN\" (N5): the columns that wrap their text when the view as a whole does not. */\n wrapColumns?: string[];\n fit?: \"auto\" | \"fit\" | \"scroll\";\n freezeFirst?: boolean;\n /**\n * THE SUMMARY BAR (TABLE-PARITY F1): Field key → the store measure shown under that column\n * (`count`, `sum`, `avg`, `min`, `max`, `median`, `filled`, `empty`, `unique`), worked out\n * by `custom.record_aggregate` over the whole table.\n */\n summaries?: Record<string, string>;\n /** THE TIMELINE'S GANTT MODE (kept as `presentation.gantt`): see `GanttLook`. */\n gantt?: GanttLook;\n /**\n * WHERE THE FOOTER SITS (lane TABLE-PAGE-CHROME, owner 2026-09-25) — the row count and the\n * pages: `sticky`, pinned to the bottom of the screen while the rows scroll (the older grid's\n * way, and the default when a view says nothing), or `inline`, right after the last row with\n * the page scrolling. Read by the Grid and a host's Sheet.\n */\n footer?: FooterPlacement;\n};\n\n/** Where a grid's footer sits. */\nexport type FooterPlacement = \"sticky\" | \"inline\";\n\n/** What a view that says nothing gets — the older grid's pinned footer. */\nexport const DEFAULT_FOOTER: FooterPlacement = \"sticky\";\n\n/** Where THIS view's footer sits, said or defaulted. */\nexport function footerOf(p: GridPresentation | null | undefined): FooterPlacement {\n return p?.footer ?? DEFAULT_FOOTER;\n}\n\nexport const EMPTY_PRESENTATION: GridPresentation = {};\n\n/** A frozen column needs a known width, or the table declines to freeze at all. */\nexport const FROZEN_COLUMN_WIDTH = 200;\n\n/**\n * WHAT A ROW HEIGHT IS ALLOWED TO BE. Below the floor the text is clipped and\n * no editor fits; above the ceiling one record fills the screen. A view asking\n * for anything outside it is DROPPED rather than obeyed, because a saved look\n * must not be able to make a table unreadable — and the Fields' own heights\n * still draw, which is the tolerant half.\n */\nexport const MIN_ROW_HEIGHT = 24;\nexport const MAX_ROW_HEIGHT = 240;\n\n/**\n * The aggregate kinds and orders the primitive actually implements. Listed here\n * because they are the tolerant parser's allow-list and a view is hostile input:\n * `satisfies` ties them to the primitive's own union, so a kind it renames stops\n * this file compiling instead of quietly parsing into a subtotal nobody draws.\n */\nconst AGGREGATE_KINDS = [\n \"count\",\n \"sum\",\n \"average\",\n \"min\",\n \"max\",\n \"empty\",\n \"filled\",\n \"unique\",\n \"checked\",\n \"unchecked\",\n \"percentFilled\",\n] as const satisfies readonly GroupAggregateKind[];\n\nconst GROUP_ORDERS = [\n \"value-asc\",\n \"value-desc\",\n \"count-desc\",\n \"first-seen\",\n] as const satisfies readonly GroupOrder[];\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Read a presentation out of whatever the view record holds — an object, or the\n * JSON string a text field stores.\n *\n * Tolerant in exactly the way `parseTableStyle` is: an unreadable half is\n * dropped and the rest still applies, because a table must keep drawing when\n * one piece of its styling goes out of date.\n */\nexport function parseGridPresentation(raw: unknown): GridPresentation {\n let value = raw;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return {};\n try {\n value = JSON.parse(trimmed);\n } catch {\n return {};\n }\n }\n if (!isRecord(value)) return {};\n\n const out: GridPresentation = {};\n\n const style = parseTableStyle(value[\"style\"]);\n if (!tableStyleIsEmpty(style)) out.style = style;\n\n if (isRecord(value[\"formats\"])) {\n const formats: Record<string, FieldFormatConfig> = {};\n for (const [key, cfg] of Object.entries(value[\"formats\"])) {\n if (!isRecord(cfg)) continue;\n const id = cfg[\"id\"];\n if (typeof id !== \"string\") continue;\n const options = cfg[\"options\"];\n formats[key] = isRecord(options)\n ? {\n id: id as FieldFormatConfig[\"id\"],\n options: options as NonNullable<FieldFormatConfig[\"options\"]>,\n }\n : { id: id as FieldFormatConfig[\"id\"] };\n }\n if (Object.keys(formats).length > 0) out.formats = formats;\n }\n\n if (Array.isArray(value[\"frozen\"])) {\n const frozen = value[\"frozen\"].filter((k): k is string => typeof k === \"string\");\n if (frozen.length > 0) out.frozen = frozen;\n }\n\n const grouping = parseGrouping(value[\"grouping\"]);\n if (grouping) out.grouping = grouping;\n\n if (isRecord(value[\"widths\"])) {\n const widths: Record<string, number> = {};\n for (const [key, raw] of Object.entries(value[\"widths\"])) {\n // A width that is not a usable number is one column's loss, never the\n // view's: the rest of the widths still apply and that column is measured.\n if (typeof raw !== \"number\" || !Number.isFinite(raw) || raw <= 0) continue;\n widths[key] = Math.round(raw);\n }\n if (Object.keys(widths).length > 0) out.widths = widths;\n }\n\n const rowHeight = value[\"rowHeight\"];\n if (\n typeof rowHeight === \"number\" &&\n Number.isFinite(rowHeight) &&\n rowHeight >= MIN_ROW_HEIGHT &&\n rowHeight <= MAX_ROW_HEIGHT\n ) {\n out.rowHeight = Math.round(rowHeight);\n }\n\n if (Array.isArray(value[\"hiddenFields\"])) {\n const hiddenFields = value[\"hiddenFields\"].filter((k): k is string => typeof k === \"string\");\n if (hiddenFields.length > 0) out.hiddenFields = hiddenFields;\n }\n if (Array.isArray(value[\"cardHidden\"])) {\n out.cardHidden = value[\"cardHidden\"].filter((k): k is string => typeof k === \"string\");\n }\n\n if (Array.isArray(value[\"columnOrder\"])) {\n const columnOrder = value[\"columnOrder\"].filter((k): k is string => typeof k === \"string\");\n if (columnOrder.length > 0) out.columnOrder = columnOrder;\n }\n\n const gallerySize = value[\"gallerySize\"];\n if (gallerySize === \"small\" || gallerySize === \"medium\" || gallerySize === \"large\") {\n out.gallerySize = gallerySize;\n }\n\n const boardSize = value[\"boardSize\"];\n if (boardSize === \"small\" || boardSize === \"medium\" || boardSize === \"large\") out.boardSize = boardSize;\n if (typeof value[\"boardPreview\"] === \"string\" && value[\"boardPreview\"] !== \"\") out.boardPreview = value[\"boardPreview\"];\n if (typeof value[\"boardHideEmpty\"] === \"boolean\") out.boardHideEmpty = value[\"boardHideEmpty\"];\n if (typeof value[\"boardColorColumns\"] === \"boolean\") out.boardColorColumns = value[\"boardColorColumns\"];\n if (typeof value[\"boardCounts\"] === \"boolean\") out.boardCounts = value[\"boardCounts\"];\n if (value[\"locked\"] === true) out.locked = true;\n if (Array.isArray(value[\"boardLanesShut\"])) {\n const shut = value[\"boardLanesShut\"].filter((k): k is string => typeof k === \"string\");\n if (shut.length > 0) out.boardLanesShut = shut;\n }\n\n if (typeof value[\"wrap\"] === \"boolean\") out.wrap = value[\"wrap\"];\n if (Array.isArray(value[\"wrapColumns\"])) {\n const wrapColumns = value[\"wrapColumns\"].filter((k): k is string => typeof k === \"string\");\n if (wrapColumns.length > 0) out.wrapColumns = wrapColumns;\n }\n const fit = value[\"fit\"];\n if (fit === \"auto\" || fit === \"fit\" || fit === \"scroll\") out.fit = fit;\n if (typeof value[\"freezeFirst\"] === \"boolean\") out.freezeFirst = value[\"freezeFirst\"];\n const footer = value[\"footer\"];\n if (footer === \"sticky\" || footer === \"inline\") out.footer = footer;\n {\n const gantt = parseGanttLook(value[\"gantt\"]);\n if (gantt) out.gantt = gantt;\n }\n if (isRecord(value[\"summaries\"])) {\n const summaries: Record<string, string> = {};\n for (const [key, op] of Object.entries(value[\"summaries\"])) {\n // A measure name is judged by the store when it is asked; a non-string is nobody's.\n if (typeof op === \"string\" && op.trim() !== \"\") summaries[key] = op;\n }\n if (Object.keys(summaries).length > 0) out.summaries = summaries;\n }\n\n return out;\n}\n\n/**\n * A grouping, or nothing. A grouping with no Field to group BY is not a partial\n * grouping — it is no grouping at all, so it is dropped whole; an unreadable\n * subtotal or a section order this primitive does not have costs only itself.\n */\nfunction parseGrouping(raw: unknown): GridGrouping | undefined {\n if (!isRecord(raw)) return undefined;\n const field = raw[\"field\"];\n if (typeof field !== \"string\" || field.trim() === \"\") return undefined;\n\n const out: GridGrouping = { field };\n\n if (isRecord(raw[\"aggregates\"])) {\n const aggregates: Record<string, GroupAggregateSpec> = {};\n for (const [key, spec] of Object.entries(raw[\"aggregates\"])) {\n if (!isRecord(spec)) continue;\n const kind = spec[\"kind\"];\n if (typeof kind !== \"string\") continue;\n if (!(AGGREGATE_KINDS as readonly string[]).includes(kind)) continue;\n const label = spec[\"label\"];\n aggregates[key] =\n typeof label === \"string\"\n ? { kind: kind as GroupAggregateKind, label }\n : { kind: kind as GroupAggregateKind };\n }\n if (Object.keys(aggregates).length > 0) out.aggregates = aggregates;\n }\n\n const order = raw[\"order\"];\n if (typeof order === \"string\" && (GROUP_ORDERS as readonly string[]).includes(order)) {\n out.order = order as GroupOrder;\n }\n\n if (Array.isArray(raw[\"collapsed\"])) {\n const collapsed = raw[\"collapsed\"].filter((k): k is string => typeof k === \"string\");\n if (collapsed.length > 0) out.collapsed = collapsed;\n }\n\n if (typeof raw[\"showColumn\"] === \"boolean\") out.showColumn = raw[\"showColumn\"];\n\n if (Array.isArray(raw[\"then\"])) {\n const then: GridSubGroup[] = [];\n for (const level of raw[\"then\"]) {\n if (!isRecord(level)) continue;\n const sub = level[\"field\"];\n if (typeof sub !== \"string\" || sub.trim() === \"\") continue;\n const subOrder = level[\"order\"];\n const subGrain = level[\"grain\"];\n then.push({\n field: sub,\n ...(typeof subOrder === \"string\" && (GROUP_ORDERS as readonly string[]).includes(subOrder)\n ? { order: subOrder as GroupOrder }\n : {}),\n ...(typeof subGrain === \"string\" && subGrain !== \"\" ? { grain: subGrain } : {}),\n });\n }\n if (then.length > 0) out.then = then.slice(0, 2);\n }\n\n const grain = raw[\"grain\"];\n if (typeof grain === \"string\" && grain !== \"\") out.grain = grain;\n const across = raw[\"across\"];\n if (typeof across === \"string\" && across.trim() !== \"\") out.across = across;\n if (Array.isArray(raw[\"show\"])) {\n const show = raw[\"show\"].filter((k): k is string => typeof k === \"string\" && k !== \"\");\n if (show.length > 0) out.show = show;\n }\n if (Array.isArray(raw[\"trail\"])) {\n const trail: GridDrillStep[] = [];\n for (const step of raw[\"trail\"]) {\n if (!isRecord(step) || typeof step[\"dim\"] !== \"string\" || step[\"dim\"] === \"\") continue;\n const value = step[\"value\"];\n if (value !== null && typeof value !== \"string\") continue;\n trail.push({ dim: step[\"dim\"], value });\n }\n if (trail.length > 0) out.trail = trail;\n }\n\n return out;\n}\n\n/** True when nothing here would change how anything looks. */\nexport function presentationIsEmpty(p: GridPresentation): boolean {\n return (\n (p.style === undefined || tableStyleIsEmpty(p.style)) &&\n Object.keys(p.formats ?? {}).length === 0 &&\n (p.frozen?.length ?? 0) === 0 &&\n p.grouping === undefined &&\n Object.keys(p.widths ?? {}).length === 0 &&\n p.rowHeight === undefined &&\n (p.hiddenFields?.length ?? 0) === 0 &&\n p.cardHidden === undefined &&\n (p.columnOrder?.length ?? 0) === 0 &&\n p.gallerySize === undefined &&\n p.boardSize === undefined &&\n p.boardPreview === undefined &&\n p.boardHideEmpty === undefined &&\n p.boardColorColumns === undefined &&\n p.boardCounts === undefined &&\n (p.boardLanesShut?.length ?? 0) === 0 &&\n p.locked === undefined &&\n p.wrap === undefined &&\n p.fit === undefined &&\n p.freezeFirst === undefined &&\n p.footer === undefined &&\n Object.keys(p.summaries ?? {}).length === 0 &&\n p.gantt === undefined\n );\n}\n\n/** The string a view record stores. Empty string for \"this view styles nothing\". */\nexport function presentationDocument(p: GridPresentation): string {\n return presentationIsEmpty(p) ? \"\" : JSON.stringify(p);\n}\n\n/** Kinds whose value is a control the record renderer draws, never a formatter's string. */\nconst DRAWN_KINDS = new Set([\"select\", \"multi_select\", \"relation\", \"member\", \"person\", \"attachment\", \"file\", \"entity_reference\", \"checkbox\", \"boolean\", \"signature\", \"json\"]);\n\n/**\n * The display format a column's settings chose for it, when it is one the platform registry\n * knows and the column is not one the record renderer draws itself. `undefined` otherwise.\n */\nexport function shownAsOf(field: Field): FieldFormatConfig | undefined {\n if (DRAWN_KINDS.has(String(editorKindFor(field)))) return undefined;\n const raw = (field as unknown as { display_format?: unknown }).display_format;\n if (!raw || typeof raw !== \"object\") return undefined;\n const id = (raw as { id?: unknown }).id;\n // `formula` is the column's KIND, never a look: a worked-out column shows as its result format.\n if (typeof id !== \"string\" || id === \"formula\" || !getFieldFormat(id)) return undefined;\n const options = (raw as { options?: unknown }).options;\n return options && typeof options === \"object\" && Object.keys(options).length > 0\n ? ({ id, options } as FieldFormatConfig)\n : ({ id } as FieldFormatConfig);\n}\n\n/**\n * The format a Field's OWN declaration implies, before any view says anything.\n *\n * Returns `undefined` for a Field whose value the record renderer already draws\n * better than a formatter could — a relation chip, a person, an attachment, a\n * choice — because handing those to a text formatter would REPLACE a working\n * control with a string.\n */\nexport function formatForField(field: Field): FieldFormatConfig | undefined {\n // \"SHOWS AS\" — THE FORMAT THE COLUMN'S OWN SETTINGS CHOSE (merge tranche 6a/6e). The column\n // keeps it on the Field (`display_format`, written by `custom.field_update`), so the grid, the\n // record panel and the export read one answer. A column the record renderer draws better than\n // a formatter (a choice, a link, a person, a file) keeps its own drawing.\n const shownAs = shownAsOf(field);\n // A RATING'S TOP STAR IS ITS MAX RULE (lane 10 P4), so changing the rule changes the stars.\n if (newerKindOf(field) === \"rating\" && (!shownAs || shownAs.id === \"rating\")) {\n return { id: \"rating\", options: { ...(shownAs?.options ?? {}), ratingMax: ratingMaxOf(field) } };\n }\n if (shownAs) return shownAs;\n // THE STORE'S OWN STAMPS (Created time, Last changed time) are instants and print like\n // every other date-and-time cell — `dates.ts` answers, the same helper every card reads.\n const stamp = dateFormatFor(field);\n if (stamp && editorKindFor(field) !== \"datetime\") return stamp;\n switch (editorKindFor(field)) {\n case \"currency\":\n return {\n id: \"currency\",\n options: { currency: currencyCodeFor(field.unit) },\n };\n case \"percent\":\n return { id: \"percent\" };\n case \"datetime\":\n // The COLUMN's declaration: a `kind: \"date\"` column is a date column, anything else can\n // hold a moment. Each VALUE is still printed in its own shape (`RecordValue` →\n // `dates.dateFormatFor(field, value)`), so a bare date in a datetime column reads as a date.\n return String(field.config?.[\"kind\"] ?? \"\") === \"date\" ? { id: \"date\" } : { id: \"datetime\" };\n case \"email\":\n return { id: \"email\" };\n case \"url\":\n return { id: \"url\" };\n case \"phone\":\n return { id: \"phone\" };\n case \"number\":\n return { id: \"number\" };\n default:\n return undefined;\n }\n}\n\n/**\n * THE FORMAT ONE CELL OF A COLUMN IS DRAWN IN — the grid's cell and the gallery's card alike\n * (guard G4 \"one format\", lane VIEWS-AND-FIELDS W1-B): the view's own format for the column when\n * it set one, else the Field's own (`formatForField`). One function, so no layout formats money\n * (or a date, or a percent) its own way.\n */\nexport function cellFormatFor(field: Field, viewFormat?: FieldFormatConfig | undefined): FieldFormatConfig | undefined {\n return viewFormat ?? formatForField(field);\n}\n\n/**\n * The store lets a person type the SYMBOL they think in (`$`, `€`), and\n * `Intl.NumberFormat` wants the ISO code. The symbols people actually enter are\n * mapped; anything else is passed straight through, because a real ISO code is\n * the other thing that lands in this field, and an unknown code still renders\n * (`1,234.00 XYZ`) rather than blanking — THE FALLBACK LAW.\n */\nconst SYMBOL_TO_CODE: Record<string, string> = {\n $: \"USD\",\n \"£\": \"GBP\",\n \"€\": \"EUR\",\n \"¥\": \"JPY\",\n \"₹\": \"INR\",\n \"₽\": \"RUB\",\n \"₩\": \"KRW\",\n \"R$\": \"BRL\",\n \"C$\": \"CAD\",\n \"A$\": \"AUD\",\n};\n\nexport function currencyCodeFor(unit: string | null | undefined): string {\n const raw = (unit ?? \"\").trim();\n if (raw === \"\") return \"USD\";\n return SYMBOL_TO_CODE[raw] ?? raw.toUpperCase();\n}\n\n/**\n * The color a value paints with when a view colors BY a column.\n *\n * NOTHING FAILS SILENTLY, so this never returns \"no color\" for a value that\n * exists. The store keeps a list column's choices as records of another Table,\n * and this grid has not read that Table's option colors, so the color is\n * derived from the value's own text: the same word always gets the same color,\n * different words get different ones, and `true` is green the way every\n * checklist reads. When those option colors do land, this is the ONE function\n * that changes and every screen follows.\n */\nexport const colorFromTheValue: ChoiceColorLookup = (_fieldKey, value) => {\n if (value === true) return \"green\";\n if (value === false || value === null || value === undefined) return undefined;\n const text = String(value).trim().toLowerCase();\n if (text === \"\") return undefined;\n let hash = 0;\n for (let i = 0; i < text.length; i += 1) {\n hash = (hash * 31 + text.charCodeAt(i)) >>> 0;\n }\n return COLOR_ROTATION[hash % COLOR_ROTATION.length] as StyleColor;\n};\n\n/**\n * G1's TABLE-LEVEL COLORS (`custom.table_decorations` / `client.tableDecorations`),\n * TRANSLATED INTO A `TableStyle` — the same document a saved view's own `presentation.style`\n * is, so one resolver (`resolveTableStyle`, below) can treat \"the table's colors\" and \"this\n * view's colors\" as two layers of the SAME thing instead of two unrelated systems.\n *\n * THE TRANSLATION IS NECESSARY AND IT IS THE ONLY ONE. G1 addresses a Field by its ID\n * (REC-17 — a column renamed keeps its color), because it is the STORE's own door; a\n * `TableStyle` addresses a Field by its KEY, because it reads straight off `row.document`,\n * which is keyed by Field key. A rule or a color-by whose Field has been deleted (its id no\n * longer resolves to a key) is dropped rather than crashing the whole style — a defect in\n * one rule costs that rule, never the read.\n */\nexport function styleFromDecorations(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n): TableStyle {\n if (!decorations) return { version: 1 };\n const keyOf = (id: string) => fields.find((f) => f.id === id)?.key;\n const style: TableStyle = { version: 1 };\n\n if (decorations.color_by?.field) {\n const field = keyOf(decorations.color_by.field);\n if (field) style.colorBy = { field, target: decorations.color_by.target };\n }\n\n if (Array.isArray(decorations.rules) && decorations.rules.length > 0) {\n const rules: ColorRule[] = [];\n decorations.rules.forEach((rule, index) => {\n const field = keyOf(rule.field);\n if (!field) return;\n rules.push({\n id: rule.id ?? `decoration:${index}`,\n field,\n op: rule.op,\n color: rule.color,\n target: rule.target,\n ...(rule.value === undefined || rule.value === null ? {} : { value: String(rule.value) }),\n });\n });\n if (rules.length > 0) style.rules = rules;\n }\n\n if (decorations.rows && Object.keys(decorations.rows).length > 0) {\n style.rows = { ...decorations.rows };\n }\n\n const columns: Record<string, StyleColor> = {};\n for (const [id, color] of Object.entries(decorations.columns ?? {})) {\n const key = keyOf(id);\n if (key) columns[key] = color;\n }\n if (Object.keys(columns).length > 0) style.columns = columns;\n\n const cells: Record<string, Record<string, StyleColor>> = {};\n for (const [rowId, byField] of Object.entries(decorations.cells ?? {})) {\n for (const [id, color] of Object.entries(byField ?? {})) {\n const key = keyOf(id);\n if (!key) continue;\n (cells[rowId] ??= {})[key] = color;\n }\n }\n if (Object.keys(cells).length > 0) style.cells = cells;\n\n return style;\n}\n\n/**\n * THE ONE PRESENTATION RESOLVER every colored surface in this package reads —\n * `Grid`, and (via `Card`) the kanban board, the calendar and the gallery.\n *\n * THE CLASS THIS CLOSES (lane UI-QUICK-1, TABLE-PARITY review, 2026-09-24). Threading\n * `view.presentation.style` into `Card` closed the instance the CALENDAR-GALLERY bench\n * found — a VIEW that had its own color-by looked right everywhere — but it left the\n * class open: a table whose colors are the ORGANIZATION's decorations (G1's\n * `custom.table_decorations` — \"color rows by Account Type\", \"MAXED and NEAR rules\" on\n * Arman's own Coding Accounts table) and whose views never set a `presentation.style` of\n * their own drew NOTHING, on the grid exactly as much as on every other layout, because\n * nothing in this package had ever read G1's door at all.\n *\n * TWO LAYERS, ONE DOCUMENT. The table's decorations are the base coat — what every view\n * of this table shows until a view says otherwise. A view's OWN `presentation.style` is\n * an override, piece by piece: a view that only ever set manual row highlights still\n * inherits the table's color-by and rules for everything it never touched, and a view\n * that fully redecorated (its own `colorBy` AND its own `rules`) is not haunted by a\n * table rule it meant to replace. Per-piece, never all-or-nothing, because a view is a\n * LOOK on top of the table, not a wholesale substitute for it.\n */\nexport function resolveTableStyle(\n decorations: TableDecorations | null | undefined,\n fields: readonly Field[],\n viewStyle: TableStyle | undefined,\n): TableStyle {\n const base = styleFromDecorations(decorations, fields);\n if (!viewStyle) return base;\n const colorBy = viewStyle.colorBy ?? base.colorBy;\n const rules = viewStyle.rules ?? base.rules;\n const rows = viewStyle.rows ?? base.rows;\n const columns = viewStyle.columns ?? base.columns;\n const cells = viewStyle.cells ?? base.cells;\n return {\n version: 1,\n ...(colorBy !== undefined ? { colorBy } : {}),\n ...(rules !== undefined ? { rules } : {}),\n ...(rows !== undefined ? { rows } : {}),\n ...(columns !== undefined ? { columns } : {}),\n ...(cells !== undefined ? { cells } : {}),\n };\n}\n","\"use client\";\n\n// src/viewKeys.ts — @ai-matrx/records-ui · lane S1-PRIME VIEW-KEYS\n//\n// THE VIEW-KEY REGISTRY MOVED TO `@ai-matrx/records/views` (step 10, 2026-10-08): the declared keys, the one\n// reader and the one writer are headless there. What stays here is this package's LOOK: the grid\n// presentation (colors, formats, frozen columns — `presentation.ts`, built on design-system) bound as the\n// view's presentation codec. Every name this file exported is unchanged.\n\nimport * as base from \"@ai-matrx/records/views\";\nimport { parseGridPresentation, presentationDocument, presentationIsEmpty, type GridPresentation } from \"./presentation\";\n\nexport {\n STORE_KEEPS_TIMELINE,\n STORE_KEPT_LAYOUTS,\n VIEW_KEYS,\n storeKeepsLayout,\n viewKeysFor,\n viewSettingsFor,\n type ViewKeyDeclaration,\n type ViewKeyLayout,\n type ViewKeyWriter,\n type ViewSortKey,\n} from \"@ai-matrx/records/views\";\n\n/** This package's look, as the view's presentation codec. */\nexport const gridPresentationCodec: base.ViewPresentationCodec<GridPresentation> = {\n parse: parseGridPresentation,\n isEmpty: presentationIsEmpty,\n toDocument: (presentation) => JSON.parse(presentationDocument(presentation)) as Record<string, unknown>,\n};\n\n/** EVERY DECLARED KEY OF A VIEW, TYPED, with this package's grid look. */\nexport type ViewSettings = base.ViewSettings<GridPresentation>;\nexport type ViewSettingsPatch = base.ViewSettingsPatch<GridPresentation>;\n\n/** Every view and look door with this package's grid look bound — bindings of records' one definition. */\nexport const boundViews = base.bindViewCodec<GridPresentation>(gridPresentationCodec);\n\n/** THE READER (a stored `definition` and the row's flat `filters` in, every declared key out) and THE WRITER'S SHAPE. */\nexport const { viewSettings, viewSettingsPatch } = boundViews;\n","// src/firstPage.ts — @ai-matrx/records-ui/first-page · lane PAGE-BUNDLE-2\n//\n// THE GRID'S FIRST PAGE, ASKED BEFORE THE GRID EXISTS. The logic lives in `@ai-matrx/records/first-page` (headless,\n// step 10, 2026-10-08); this entry binds this package's grid look (`gridPresentationCodec`) so a view is read exactly\n// as the screen reads it. No React here (server-safe entry).\n\nimport { bindFirstPageCodec } from \"@ai-matrx/records/first-page\";\nimport { gridPresentationCodec } from \"./viewKeys\";\n\nexport {\n EMBEDDED_PAGE_SIZE,\n TABLE_PAGE_SIZE,\n firstGridPageArgs,\n serverRowsOf,\n viewFilterOf,\n type FirstGridPageArgs,\n} from \"@ai-matrx/records/first-page\";\n\nexport const { firstGridPageRequest, askTablePageSeed } = bindFirstPageCodec(gridPresentationCodec);\n\n// Entity blocks and chart tiles (lane SEED-ENTITY).\nexport * from \"./firstPageEntity\";\n"]}
|