@ai-matrx/records-ui 0.115.70 → 0.115.72
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 +11 -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 +16 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dataSource.ts","../src/personZone.ts","../src/rights.ts","../src/tableRights.ts","../src/names.ts","../src/whoChanged.ts","../src/withheldNotice.ts","../src/choiceLooks.ts","../src/docs.ts","../src/fieldTypes.ts","../src/parity.ts","../src/tablePickerEntries.ts","../src/presentation.ts"],"names":["globalSlot","setColorRuleTimeZone","atLeast","humanizeIdentifier","isUuidShape","formatRelativeTime","DECLARABLE_FIELD_KINDS","NEWER_FIELD_KINDS","KERNEL_TABLES","PARITY_FIELD_TYPES","fieldKindFor","isButtonField","newerKindOf"],"mappings":"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","file":"helpers.cjs","sourcesContent":["// src/dataSource.ts — @ai-matrx/records-ui (pure; also served alone by `@ai-matrx/records-ui/helpers`)\n//\n// The two helpers every host calls before it draws anything: who is acting (`personActor`) and the\n// store's data seam over a supabase-js client (`recordsDataSource`). No React and no screen here, so\n// a service, a registry or a server module that needs only these never pulls the package's screens.\n// `RecordsMount` re-exports both, so the root entry keeps them.\n\nimport type { RecordsActor, RecordsDataSource, Uuid } from \"@ai-matrx/records\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\nexport function personActor(userId: Uuid | null | undefined): RecordsActor {\n return userId ? { actor: \"user\", user_id: userId } : { actor: \"user\" };\n}\n\n/**\n * A supabase-js client AS this package's data seam.\n *\n * `RecordsDataSource` is deliberately a small structural subset of\n * `SupabaseClient` — the package holds no key and opens no connection, the host\n * hands it a client that already carries the person's session.\n *\n * IT IS NOT AN IDENTITY CAST, and it cost a live screen to learn why\n * (2026-09-19, /data's first load): the port's `rpc(fn, args, { schema })`\n * says the schema OUT LOUD, because every door of this store is `custom.<name>`\n * and every door of the trust layer is `iam.<name>`. supabase-js's own\n * `rpc(fn, args, options)` has no `schema` option at all — its third argument is\n * `{ head, get, count }` — so a client passed straight through silently asked\n * its default PostgREST profile and the store answered, correctly and uselessly,\n * \"Could not find the function public.table_kernel_id ... PGRST202\". The schema\n * is chosen the one way supabase-js offers: `client.schema(name).rpc(...)`.\n *\n * `schema(name).from(table)` already agrees structurally and is passed through.\n *\n * `fallbackSchema` is the profile a call that says NOTHING is sent to. No caller\n * in `@ai-matrx/records` omits the option — `callDoor` always says `custom` and\n * `trustDoor` always says `iam` — so it never fires today; it exists so that a\n * call which one day forgets lands on the store rather than silently on\n * `public`. It is a FALLBACK and not the answer: a version of this adapter that\n * sent EVERY door to one fixed schema would route the `iam` trust doors (the\n * external principal, the publish binding) into `custom` and get a PGRST202\n * naming the wrong schema.\n *\n * The cast itself stays HERE, once, where it is explained: a generated\n * `Database` type makes `schema().from().select()` enormous, and a host app that\n * passes its client straight into `config` gets `TS2589: Type instantiation is\n * excessively deep and possibly infinite` in its own build (matrx-frontend,\n * 2026-09-18, on all three campaign routes at once).\n */\n/*\n * ONE DATA SOURCE PER SUPABASE CLIENT (owner, 2026-10-02). The record store's data layer keys its\n * cache by the data source object, so a host that builds this adapter again on every mount (a\n * board tile woken or brought back by Undo) must get the SAME object back for the same client —\n * otherwise every remount is a stranger to the answers the last mount read.\n */\n// One cache per realm, never per bundle copy: this module ships in BOTH the root entry and\n// `@ai-matrx/records-ui/helpers` (`splitting: false`), and a module `let` would split it in two.\nconst dataSources = globalSlot(\"ai-matrx.records-ui.data-sources\", () => new WeakMap<object, Map<string, RecordsDataSource>>());\n\nexport function recordsDataSource(client: object, fallbackSchema = \"custom\"): RecordsDataSource {\n let bySchema = dataSources.get(client);\n if (!bySchema) {\n bySchema = new Map();\n dataSources.set(client, bySchema);\n }\n const held = bySchema.get(fallbackSchema);\n if (held) return held;\n const made = makeRecordsDataSource(client, fallbackSchema);\n bySchema.set(fallbackSchema, made);\n return made;\n}\n\nfunction makeRecordsDataSource(client: object, fallbackSchema: string): RecordsDataSource {\n const supabase = client as unknown as {\n rpc: (fn: string, args: Record<string, unknown>) => PromiseLike<unknown>;\n schema: (name: string) => {\n rpc: (fn: string, args: Record<string, unknown>) => PromiseLike<unknown>;\n from: (table: string) => unknown;\n };\n auth?: { getSession?: () => Promise<{ data?: { session?: unknown } | null }> };\n };\n return {\n // PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.\n opensInOneCall: true,\n async rpc(fn, args, options) {\n const schema = options?.schema ?? fallbackSchema;\n // 🚨 NO STORE DOOR LEAVES WITHOUT A PERSON (lane MONITOR-TRIAGE, 2026-10-01). `anon` holds\n // no USAGE on schema `custom`, so a door called with no session can only come back\n // \"42501 permission denied for schema custom\" (HTTP 401) — seven of them per signed-out\n // /data load (inbox, counts, home, portals…), every one a red row in ops.system_error.\n // A `custom` door with no session is answered HERE, with the store's own signed-out\n // sentence and no network call. Other schemas (`iam`, `platform`) serve guests by design\n // and are never short-circuited. A client without `auth` (a server or test seam) is\n // passed straight through.\n if (schema === \"custom\" && supabase.auth?.getSession && !(await hasSession(supabase.auth.getSession))) {\n return { data: null, error: SIGNED_OUT } as Awaited<ReturnType<RecordsDataSource[\"rpc\"]>>;\n }\n const on = supabase.schema(schema);\n return (await on.rpc(fn, args)) as Awaited<ReturnType<RecordsDataSource[\"rpc\"]>>;\n },\n schema(name) {\n return supabase.schema(name) as ReturnType<RecordsDataSource[\"schema\"]>;\n },\n };\n}\n\n/** The read door's own sentence for a call with nobody signed in (`custom.read_records_page`, DOOR-1). */\nconst SIGNED_OUT = {\n code: \"42501\",\n message: \"Nobody is signed in, so there is nothing to read.\",\n hint: \"Sign in, then try again.\",\n details: null,\n} as const;\n\nasync function hasSession(getSession: () => Promise<{ data?: { session?: unknown } | null }>): Promise<boolean> {\n try {\n const answer = await getSession();\n return Boolean(answer?.data?.session);\n } catch {\n // Auth could not say: let the request go and the database's answer stand.\n return true;\n }\n}\n","// The person's time zone, as the app saved it (userPreferences.display.timeZone).\n//\n// The host app tells this package once (`setPersonTimeZone(\"America/Los_Angeles\")`); every \"today\" and\n// \"now\" a screen draws (the calendar's today marker and now-line, a timeline's today, a date picker's\n// today) is read in that zone instead of the device's. With nothing set the device zone applies, as before.\n// Nothing here stores a zone: the host's saved preference is the one place, this is its mirror.\n\nimport { setColorRuleTimeZone } from \"@ai-matrx/design-system/data-table/table-style\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\n// No React here: `@ai-matrx/records-ui/helpers` serves this file alone (the host sets the zone at\n// boot), and the root entry carries its own copy — so the zone lives in ONE realm-wide slot that\n// both copies read, never a module `let` that would split in two. The hook is `./personTime`.\nconst state = globalSlot(\"ai-matrx.records-ui.person-time-zone\", () => ({\n zone: null as string | null,\n listeners: new Set<() => void>(),\n}));\n\nfunction valid(z: unknown): z is string {\n if (typeof z !== \"string\" || !z.trim()) return false;\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: z });\n return true;\n } catch {\n return false;\n }\n}\n\n/** The host app's saved zone (IANA). `null` / junk clears it: the device zone applies. */\nexport function setPersonTimeZone(next: string | null | undefined): void {\n const clean = valid(next) ? next : null;\n if (clean === state.zone) return;\n state.zone = clean;\n // \"Today\" in a colour rule is the person's day, not the device's (lane NOTION-SMALL-2).\n setColorRuleTimeZone(clean);\n state.listeners.forEach((l) => l());\n}\n\nexport function getPersonTimeZone(): string | null {\n return state.zone;\n}\n\n/** Calls `listener` whenever the zone changes; returns the unsubscribe. */\nexport function subscribePersonTimeZone(listener: () => void): () => void {\n state.listeners.add(listener);\n return () => {\n state.listeners.delete(listener);\n };\n}\n\n/**\n * `at` as a Date whose LOCAL fields (getFullYear, getHours, ...) read the wall clock in the person's zone.\n * For day / hour math only: never compare it with a stored instant.\n */\nexport function personWallNow(at: Date = new Date()): Date {\n const zone = state.zone;\n if (!zone) return at;\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: zone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hourCycle: \"h23\",\n }).formatToParts(at);\n const n = (t: string) => Number(parts.find((p) => p.type === t)?.value ?? 0);\n return new Date(n(\"year\"), n(\"month\") - 1, n(\"day\"), n(\"hour\"), n(\"minute\"), n(\"second\"));\n}\n","// src/rights.ts — @ai-matrx/records-ui\n//\n// THE ONE PLACE A LEVEL BECOMES A SCREEN.\n//\n// WHAT A PERSON HIT (independent verdict, sixth pass, 19-20 September). A\n// colleague who had been shared a table at viewer opened it, double-clicked a\n// cell, got a live editor, typed a value and pressed Enter — and only THEN was\n// told she may not write. \"The permission is right; the screen told her she\n// could.\" Measured against Airtable, Notion and Linear the verdict called it\n// out by name: \"None of these three products lets you start an action it\n// already knows it will reject.\"\n//\n// WHY THE SCREEN COULD NOT KNOW. The rights question was a PORT with two honest\n// answers and no third: `NO_RIGHTS` (every administering control absent, which\n// is useless for a host whose authority IS the store's) and\n// `storeDecidesRights` — `{admin: true, comment: true, write: true}` for every\n// non-kernel table, for everybody. That second one is the defect, written down:\n// it is the screen ASSUMING the answer and letting the door say no afterwards.\n//\n// WHAT REPLACES IT. The store already answers this, and the client now carries\n// it: `custom.read_records` returns the reader's level on the TABLE on every\n// row (so `Table.your_level` costs nothing), and `custom.my_levels` — the door\n// this lane added — answers the caller's level on any subject they name, one\n// call for a whole page. This file is the only place in the package that turns\n// one of those four words into what a screen may DRAW.\n//\n// THE LADDER IS THE STORE'S, NOT OURS: viewer < commenter < editor < admin, and\n// which rung each door requires is read from the doors themselves —\n// `custom.record_update` asserts `editor` on the record, `custom.field_declare`\n// asserts `admin` on the TABLE (\"the SHAPE of this table, which is an admin's\n// right and not an editor's\"), `custom.io_comment_write` admits a commenter.\n// Nothing here invents a rule; it mirrors the four assertions those doors make\n// and is wrong the moment they change, which is what the suite is for.\n//\n// ABSENT OR HONEST, NEVER DEAD. Every `false` here carries the sentence that\n// says why and what to do about it, so a control can be left out AND explained.\n// A screen that hid a control with nothing to say is the silent half of the\n// same defect.\n\nimport { atLeast, type PermissionLevel, type Table } from \"@ai-matrx/records\";\n\n/** The six things a person does to a record or a table, as the screens offer them. */\nexport type Capability = \"read\" | \"comment\" | \"write\" | \"organise\" | \"remove\" | \"share\" | \"structure\";\n\nexport interface WhatYouMayDo {\n /** The word the store answered, or null when it answered nothing. */\n level: PermissionLevel | null;\n /**\n * Whether the store has ANSWERED yet. `false` is a third state and not a\n * refusal: a screen that drew \"you may not\" while the answer was in flight\n * would blink every control off on every reload, which is its own lie.\n */\n known: boolean;\n read: boolean;\n comment: boolean;\n write: boolean;\n /** Duplicate, merge, restore, make a layout the default: the doors that ask `editor`, above content. */\n organise: boolean;\n remove: boolean;\n share: boolean;\n structure: boolean;\n /** Why this person may not do that, in one sentence naming the way in. */\n why: (what: Capability) => string;\n}\n\n/** What each rung of the ladder is called where a person reads it. */\nexport const LEVEL_WORD: Record<PermissionLevel, string> = {\n viewer: \"Viewer\",\n commenter: \"Commenter\",\n edit_content: \"Content editor\",\n editor: \"Editor\",\n admin: \"Admin\",\n};\n\n/**\n * WHICH RUNG EACH DOOR ASKS FOR. Read off the doors themselves, in one place,\n * so a screen never re-decides it and the suite has one thing to check.\n */\nconst NEEDS: Record<Capability, PermissionLevel> = {\n read: \"viewer\",\n // custom.io_comment_write\n comment: \"commenter\",\n // custom.record_update / custom.record_write — `edit_content` changes rows and page content;\n // columns, views and settings (structure) stay with admin below, and sharing with admin.\n write: \"edit_content\",\n // custom.view_designate / view_declare / migrate_merge / record_restore — all ask `editor`: more than\n // changing content (`edit_content`), less than changing columns (`admin`).\n organise: \"editor\",\n // custom.record_delete\n remove: \"editor\",\n // custom.share_grant — ONE-LADDER (2026-10-10): Edit may pass View / Comment / Edit on inside the\n // organization (iam.may_grant_as, knobs access.sharing/*). Which levels, and to whom, is the share\n // dialog's own question (iam.my_grantable_levels); the Share button only has to open for Edit.\n share: \"editor\",\n // custom.field_declare / field_update / field_retire / table settings\n structure: \"admin\",\n};\n\n/** The rung a capability asks for — so a short \"Needs Admin access\" never re-decides the ladder. */\nexport function levelNeededFor(what: Capability): PermissionLevel {\n return NEEDS[what];\n}\n\nconst NOT_YET =\n \"Still asking the store what you may do here. Nothing is offered until it has answered.\";\n\nconst NOTHING_AT_ALL =\n \"You have not been given this. Ask somebody who administers it to share it with you.\";\n\n/** The sentence for one refusal: what the level is, what it would have to be. */\nfunction sentenceFor(what: Capability, level: PermissionLevel | null): string {\n if (level === null) return NOTHING_AT_ALL;\n const need = LEVEL_WORD[NEEDS[what]];\n const have = LEVEL_WORD[level];\n const doing: Record<Capability, string> = {\n read: \"open this\",\n comment: \"leave a comment\",\n write: \"change anything here\",\n organise: \"organise this\",\n remove: \"delete this\",\n share: \"pass this on to somebody else\",\n structure: \"change the columns of this table\",\n };\n return `You are ${have} here, and it takes ${need} to ${doing[what]}. Ask somebody who is Admin on it to move you up.`;\n}\n\n/**\n * WHAT ONE LEVEL LETS A PERSON DO. `level` null and `known` true means the\n * store said \"nothing\" — a real answer. `known` false means it has not said.\n */\nexport function whatYouMayDo(level: PermissionLevel | null, known = true): WhatYouMayDo {\n const may = (what: Capability) => known && atLeast(level, NEEDS[what]);\n return {\n level: known ? level : null,\n known,\n read: may(\"read\"),\n comment: may(\"comment\"),\n write: may(\"write\"),\n organise: may(\"organise\"),\n remove: may(\"remove\"),\n share: may(\"share\"),\n structure: may(\"structure\"),\n why: (what) => (known ? sentenceFor(what, level) : NOT_YET),\n };\n}\n\n/** Nothing offered, because nothing is known yet. */\nexport const NOT_ANSWERED_YET: WhatYouMayDo = whatYouMayDo(null, false);\n\n/**\n * REC-27. The nine Tables the platform ships are not administered from a\n * person's screen whatever level they hold on them — so the structure controls\n * are absent for a kernel Table and say why, and everything else follows the\n * level as usual.\n */\nexport const KERNEL_REASON =\n \"This is one of the tables the platform ships, so its columns are not changed from a screen. \" +\n \"Make your own table to change columns.\";\n\nexport function whatYouMayDoWithTable(\n table: Table | null | undefined,\n level: PermissionLevel | null,\n known: boolean,\n): WhatYouMayDo {\n const base = whatYouMayDo(level, known);\n // A LOCKED DATABASE (lane NOTION-SMALL-2): the columns and the table's settings stay as they are until\n // somebody unlocks it. An editing convenience, never a permission — the lock changes what is OFFERED, the\n // store's ladder is untouched and anyone who may edit the table may unlock it.\n if (!table?.is_kernel) return table && tableIsLocked(table) ? lockedStructure(base) : base;\n return {\n ...base,\n structure: false,\n why: (what) => (what === \"structure\" ? KERNEL_REASON : base.why(what)),\n };\n}\n\n/** Is this table locked against column and settings changes? Kept on the table's own metadata. */\nexport function tableIsLocked(table: Pick<Table, \"metadata\"> | null | undefined): boolean {\n return table?.metadata?.[\"locked\"] === true;\n}\n\nexport const DATABASE_LOCKED_REASON = \"This database is locked, so its columns and settings are read-only. Unlock it to change them.\";\n\nfunction lockedStructure(base: WhatYouMayDo): WhatYouMayDo {\n return { ...base, structure: false, why: (what) => (what === \"structure\" ? DATABASE_LOCKED_REASON : base.why(what)) };\n}\n","// src/tableRights.ts — @ai-matrx/records-ui (pure; also served by `@ai-matrx/records-ui/helpers`)\n//\n// What a person may do to one table, as the screens read it. `./host.tsx` (the React context the\n// screens ask) re-exports everything here; a host that builds rights by hand (`tableRightsAt`) at\n// boot reads this file alone and never pulls a screen.\n\nimport type { PermissionLevel } from \"@ai-matrx/records\";\nimport { NOT_ANSWERED_YET, whatYouMayDo, type WhatYouMayDo } from \"./rights\";\n\n/**\n * What a person may do to one table. The four words and the ladder are the\n * store's (`rights.ts`), not ours.\n *\n * `admin`, `comment` and `write` are kept with the names they have always had,\n * because every screen in this package and in the host apps reads them; they\n * are now the store's ANSWER rather than a host's guess.\n */\nexport interface TableRights extends WhatYouMayDo {\n /** SCR-2: holders of `admin` on the table see the \"+\" and the settings panel. */\n admin: boolean;\n /**\n * Why this answer, in one sentence. Kept for callers that show a single\n * reason; `why(what)` is the per-control sentence and is what the screens use.\n */\n reason: string;\n}\n\n/** Internal: the rights record for an answer (`host.tsx` folds the store's answers through it). */\nexport function tableRights(may: WhatYouMayDo, overallReason?: string): TableRights {\n return {\n ...may,\n admin: may.structure,\n reason: overallReason ?? (may.structure ? \"\" : may.why(\"structure\")),\n };\n}\n\n/**\n * Kept so a host that imported it still compiles. It is no longer any screen's\n * default: the store answers this question now, and a package that said \"I\n * assume nothing\" while holding the answer would be hiding controls from the\n * people who hold them.\n */\nexport const NO_RIGHTS: TableRights = tableRights(NOT_ANSWERED_YET);\n\n/**\n * THE ONE WAY TO BUILD A `TableRights` BY HAND — for a host with its own\n * authority, and for a suite that wants to mount one seat's screen. It takes\n * the LEVEL, never six booleans, so nobody can hand a screen a combination the\n * ladder cannot produce (write without read, share without edit).\n */\nexport function tableRightsAt(level: PermissionLevel | null): TableRights {\n return tableRights(whatYouMayDo(level, true));\n}\n","// src/names.ts — @ai-matrx/records-ui\n//\n// WHAT A PERSON READS, NEVER WHAT THE MACHINE STORES.\n//\n// Measured on the real screens (independent verdict, 19 September): a table row\n// read `Saved views · 9 fields · records_ui_view`, a relation cell read a bare\n// `8f2c…` identifier, a header read `stage` because the Field's label was\n// blank, and the type word on a column tooltip read `long_text`. Airtable,\n// Notion and Linear never show a person an internal name; the verdict called\n// this one of the three things a first-time user feels within a minute.\n//\n// So every place this package prints something a person reads goes through one\n// of these three functions. They are deliberately dumb: they choose the best\n// human string that IS AVAILABLE and, when none is, say what the thing is\n// rather than printing the token. No screen in this package formats a name of\n// its own any more.\n\nimport type { Field, ReadRow, RecordDocument, Table } from \"@ai-matrx/records\";\nimport { isUuidShape } from \"@ai-matrx/kit/uuid\";\nimport { humanizeIdentifier } from \"@ai-matrx/kit/text-case\";\n\n/** The name of one Field, as a header, a form label or a peek's term. */\nexport function fieldName(field: Field): string {\n const label = (field.label ?? \"\").trim();\n // A LABEL THAT IS ONLY A MACHINE KEY is not a name (merged-grid review 2026-09-26): a table made\n // by an import or an agent kept `label: \"job_no\"`, and the header read `job_no`. A key joined by\n // underscores is written the way a person writes it (\"Job no\"). A single lowercase word the\n // owner typed (\"mobile\", \"title\") is their own words and is shown as they wrote it.\n if (label !== \"\" && label === field.key && /^[a-z0-9]+(?:[_.-][a-z0-9]+)+$/.test(label)) return humanizeIdentifier(label);\n if (label !== \"\") return label;\n const name = (field.name ?? \"\").trim();\n if (name !== \"\") return name;\n return humanizeIdentifier(field.key);\n}\n\n/** The name of one Table, as a card, a heading or a breadcrumb. */\nexport function tableName(table: Table): string {\n const name = (table.name ?? \"\").trim();\n if (name !== \"\") return name;\n const singular = (table.label_singular ?? \"\").trim();\n if (singular !== \"\") return singular;\n return humanizeIdentifier(table.slug ?? \"\");\n}\n\n/**\n * The name of one RECORD, for a chip, a card, a relation cell or a peek title.\n *\n * `titleKey` is the Table's own `title_field` when the caller knows the Table —\n * that is the store's answer to \"what makes this record a chip\" (REC-2) — and\n * the conventional keys are tried after it. A record with nothing readable is\n * NOT shown as a truncated uuid: it says it has no name yet, because \"8f2c1b0a\"\n * tells a person nothing and looks like a bug.\n */\nexport function recordName(\n document: RecordDocument | undefined,\n titleKey?: string | null,\n fallback = \"Untitled\",\n): string {\n const data = (document ?? {}) as Record<string, unknown>;\n const keys = [titleKey, \"title\", \"name\", \"label\", \"full_name\", \"subject\", \"summary\"].filter(\n (key): key is string => typeof key === \"string\" && key !== \"\",\n );\n for (const key of keys) {\n const value = data[key];\n // AN ID IS NEVER A NAME, AND THIS FUNCTION USED TO RETURN ONE (TAILS-2, 2026-09-21).\n // A Table's title column may be a RELATION, and a relation's stored value IS the other\n // record's id (FLD-6 / REL-1) — so `recordName` handed every caller a bare uuid for\n // every Table titled that way, which is how the board's unplaced list came to read\n // \"11 records are not in any of these columns — ca5f1ac7-…\". A caller that can resolve\n // the id resolves it (`scalarText` with the label lookup); a caller that cannot is\n // better off reading the next column, and then the fallback, than a hex number.\n if (typeof value === \"string\" && value.trim() !== \"\" && !looksLikeId(value.trim())) {\n return value.trim();\n }\n if (typeof value === \"number\") return String(value);\n }\n return fallback;\n}\n\n\n/**\n * Is this string the store's id for something, rather than words a person wrote?\n *\n * The same question `custom._card_words` asks on the database, with the same regex, so the\n * screen and the store agree about what is a name (TAILS-2, 2026-09-21).\n */\nexport function looksLikeId(value: unknown): boolean {\n return typeof value === \"string\" && isUuidShape(value.trim());\n}\n\n/** The same question for a row the read door returned. */\nexport function rowName(row: ReadRow | undefined, titleKey?: string | null, fallback = \"Untitled\"): string {\n return recordName(row?.document, titleKey, fallback);\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════════════\n// NAMING A RECORD WHOSE TABLE YOU ALREADY HOLD — ONE CALL, NOT A COPY PER SCREEN.\n// ═══════════════════════════════════════════════════════════════════════════════════════\n//\n// 🚨 WHY THESE TWO EXIST (lane RELATION-DISPLAY-2, 2026-09-21).\n//\n// `check:one-reference-builder` shipped with a baseline of SIX, and its own header said the\n// number was not zero because \"moving six surfaces onto the store door is real work\". Three\n// of the six were not asking the store's question at all. `Grid.tsx` names the row a person\n// is looking at for the Talk action; `Peek.tsx` names the record being peeked at;\n// `PortalsPanel.tsx` names the rows of the portal's own client Table; and this repo's\n// `TryEverythingScreen.tsx` does the same in two places. NONE of them is resolving a\n// RELATION — they hold the Table and they hold the document, and there is nothing to ask a\n// door about. What they were each doing by hand was the two-line join between them:\n//\n// recordName(row.document, table.title_field)\n//\n// Written out five times in four files, which is how a Table whose title column is `cname`\n// came to read \"Untitled\" on one screen and correctly on the next. It is the same question\n// as the store's, one rung down the ladder, and the same one answer should serve it — so it\n// lives HERE, in the file the guard exempts BY CONSTRUCTION as the primitive itself, and\n// every screen calls it instead of re-deriving it.\n//\n// WHAT THESE ARE NOT. They are not a second resolver and must never become one. They take a\n// Table a caller ALREADY HAS and a document it ALREADY HAS. The moment a caller is holding\n// an ID and wants words, that is the store's question and the answer is\n// `client.relationWords` / `client.relationWordsMany` — which ask the read-access ladder\n// BEFORE they read a name, apply that column's own display spec, and return the withheld\n// sentence for a record the reader may not open. Nothing in here can do any of that, and\n// nothing in here should try.\n\n/** A Table as far as naming is concerned — its own declared title column (REC-2). */\ntype TitledTable = Pick<Table, \"title_field\"> | null | undefined;\n\n/**\n * THE NAME OF A RECORD WHOSE TABLE YOU ALREADY HOLD.\n *\n * `table` is the Table the record belongs to; its `title_field` is the store's answer to\n * \"what makes this record a chip\" (REC-2) and is tried first, then the conventional keys,\n * then the fallback. A Table that has not loaded yet is `null` and the conventional keys\n * still answer — a name arriving a tick late is better than a screen that shows nothing.\n */\nexport function recordNameIn(\n table: TitledTable,\n document: RecordDocument | undefined,\n fallback = \"Untitled\",\n): string {\n return recordName(document, table?.title_field ?? null, fallback);\n}\n\n/**\n * THE NAME A CHANGE NOTICE USES — the record as it is AFTER the change (merged-grid tails,\n * 2026-09-26). Typing a first name into a row's label column read \"Changed Title on Untitled\":\n * the notice named the row from the document held before the write. The row is called what it is\n * called now; only when the change emptied its name does the notice use the name it had.\n */\nexport function changedRecordName(\n table: TitledTable,\n document: RecordDocument | undefined,\n key: string,\n after: unknown,\n): string {\n const now = recordName({ ...((document ?? {}) as Record<string, unknown>), [key]: after } as RecordDocument, table?.title_field ?? null, \"\");\n return now !== \"\" ? now : recordNameIn(table, document);\n}\n\n/** The same, for a row the read door returned. */\nexport function rowNameIn(table: TitledTable, row: ReadRow | undefined, fallback = \"Untitled\"): string {\n return recordName(row?.document, table?.title_field ?? null, fallback);\n}\n\n/**\n * WHAT A RELATION CHIP SAYS WHEN THE RECORD IT POINTS AT IS NOT YOURS.\n *\n * A relation joins two records in two tables, and holding one end says nothing\n * about the other: a Job shared with you may point at a Client nobody shared.\n * The chip then has three possible answers and only one of them is honest —\n * the target's TITLE is a leak, a bare id is a bug with a hex number in it, and\n * a permanent \"Loading…\" is a lie that never resolves.\n *\n * This is the SAME sentence `platform.relation_withheld_label()` answers on the\n * database, word for word, so a person reads one wording whether the screen or\n * the store worked it out. Changing it changes both (RELATION-DECLARE,\n * 2026-09-20).\n */\nexport const WITHHELD_RECORD_LABEL = \"A record you have not been given access to\";\n/**\n * WHEN THE STORE COULD NOT BE ASKED (a 503, a dropped connection) the name is UNREAD, not\n * withheld: saying \"not given access\" for a failed read lies about the data (W1.1 verifier,\n * 2026-10-02). This is what a reference reads until a later read answers.\n */\nexport const UNREAD_RECORD_LABEL = \"Couldn't load this name\";\n\n/**\n * THE PLURAL A PERSON READS, NEVER A MACHINE'S \"…ss\" (VERIFIER-16 M3 / VERIFIER-17 M3).\n *\n * A table the store or the mover never gave a real plural carries `name + \"s\"` — so a table\n * already named in the plural reads \"Customerss\", \"Status optionss\". That word was made by a\n * machine appending one letter, and it says nothing the name does not; it is never shown.\n * Returns the table's own plural when a person (or the example library) wrote one, and `null`\n * when the only plural on record is that appended letter.\n */\nexport function realPlural(table: { name?: string | null; label_singular?: string | null; label_plural?: string | null }): string | null {\n const plural = (table.label_plural ?? \"\").trim();\n if (plural === \"\") return null;\n for (const base of [table.name, table.label_singular]) {\n const b = (base ?? \"\").trim();\n if (b !== \"\" && plural === `${b}s`) return null;\n }\n return plural;\n}\n\n/**\n * ONE RECORD'S NOUN, IN A SENTENCE (\"Book a 30-minute visit\") — lane HANDOVER, 2026-09-28.\n *\n * Cedar Ridge Physical Therapy's booking page was titled \"Book a 30-minute Visits appointment\":\n * the table's NAME (a plural, a heading) was put where one record's noun goes. The noun is the\n * table's own singular (\"Visit\"), else its name, else the fallback; a Title-case word is written\n * the way a sentence writes it (\"visit\"), an acronym is kept (\"MRI scan\").\n */\nexport function singularNoun(\n table: { name?: string | null; label_singular?: string | null } | null | undefined,\n fallback = \"record\",\n): string {\n const word = (table?.label_singular ?? \"\").trim() || (table?.name ?? \"\").trim() || fallback;\n return word\n .split(/\\s+/)\n .map((w) => (/^[A-Z][a-z'’-]*$/.test(w) ? w.toLowerCase() : w))\n .join(\" \");\n}\n\n/**\n * A plural for a sentence (\"Check in ran on 2 appointments\"): the table's real plural, else the\n * singular with an \"s\" only when that does not double a final \"s\".\n */\nexport function pluralNoun(table: { name?: string | null; label_singular?: string | null; label_plural?: string | null } | null | undefined, fallback = \"record\"): string {\n const real = table ? realPlural(table) : null;\n if (real) return real;\n const singular = (table?.label_singular ?? \"\").trim() || fallback;\n return /s$/i.test(singular) ? singular : `${singular}s`;\n}\n\n\n// ═══════════════════════════════════════════════════════════════════════════════════════\n// WHAT A CARD IS TITLED BY (lane UI-FIX-18, VERIFIER-18 H5).\n// ═══════════════════════════════════════════════════════════════════════════════════════\n//\n// MEASURED 2026-09-24 on production: *ZZZ UI-QUICK-1 Rincon-style Jobs* (title column `title`)\n// grouped by Stage titled every kanban card \"false\" or \"true\". The board took the FIRST Field\n// the read door answered that was not the group column — here the *Urgent* checkbox — as the\n// title. A card is named by what names its record: the Table's own `title_field` first (REC-2),\n// then the conventional name keys, then the first words column in declared order. A yes/no\n// column, the column the board is grouped by and the store's own stamps never title a card.\n\nconst CONVENTIONAL_TITLE_KEYS = [\"title\", \"name\", \"label\", \"full_name\", \"subject\", \"summary\"];\n\nfunction canTitle(field: Field, excluded: ReadonlySet<string>): boolean {\n if (excluded.has(field.key)) return false;\n if (field.type === \"boolean\") return false;\n if (field.type === \"formula\" && (field.config ?? {})[\"system\"]) return false;\n return true;\n}\n\n/**\n * THE FIELD A CARD IS TITLED BY — kanban, calendar, gallery and pipeline alike.\n *\n * Order: the Table's `title_field` → a conventional name key (`title`, `name`, …) → the first\n * text column in declared `sort` order → the first other column that can title (never a\n * checkbox, never the group column, never a system stamp). `undefined` when nothing can, and\n * the card falls back to `rowName`, which says \"Untitled\" rather than \"false\".\n */\nexport function cardTitleField(\n fields: readonly Field[],\n titleKey: string | null | undefined,\n exclude: readonly (string | null | undefined)[] = [],\n): Field | undefined {\n const excluded = new Set(exclude.filter((k): k is string => typeof k === \"string\" && k !== \"\"));\n const ordered = [...fields].sort((a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key));\n const byKey = (key: string) => ordered.find((f) => f.key === key && canTitle(f, excluded));\n if (titleKey) {\n const declared = byKey(titleKey);\n if (declared) return declared;\n }\n for (const key of CONVENTIONAL_TITLE_KEYS) {\n const found = byKey(key);\n if (found) return found;\n }\n return (\n ordered.find((f) => f.type === \"text\" && canTitle(f, excluded)) ?? ordered.find((f) => canTitle(f, excluded))\n );\n}\n","import { formatRelativeTime } from \"@ai-matrx/kit/format\";\nimport { humanizeIdentifier } from \"@ai-matrx/kit/text-case\";\n// src/whoChanged.ts — @ai-matrx/records-ui · SCR-17, the cell's half\n//\n// \"WHO?\" IS A QUESTION. A SCREEN IS SUPPOSED TO ANSWER IT.\n//\n// 🚨 WHAT A CLINIC SAW (crew B2, 2026-09-21, ten real patients on the live app):\n//\n// \"Every single cell shows a small 'who?' hover badge next to its value instead\n// of a resolved author name, an agent/rule label, or even 'system' — the\n// attribution feature is visibly wired up (it renders on every cell) but never\n// resolves to anything readable.\"\n//\n// They were right that it never resolved, and the reason was that nothing ever\n// asked. The badge opened a panel; it never looked anything up. Meanwhile the\n// store had the answer the whole time: `custom.field_history` on those exact ten\n// patients answers `kind: user, name: admin` on every one of them, and has since\n// they were written.\n//\n// So the badge asks, on the gesture that already reaches it — a hover or a Tab —\n// for the ONE cell being asked about. Nothing is fetched for a page of two\n// hundred cells nobody points at.\n//\n// THE THREE WORDS, AND NEVER A UUID. VAL-8 fixes the vocabulary at `user`,\n// `agent`, `system` and there is no fourth. An agent ALWAYS names the person it\n// acted for (AGT-N-4: it carries exactly that person's authority, so a screen\n// showing the machine without the person would be hiding who is answerable). The\n// system is \"the system\" — a platform word a person can read, not a null. And a\n// person whose name the door did not carry is resolved through the app's ONE\n// organization-scoped roster (the `members` port, FIX-7B's rule: the people of\n// THIS organization and nobody else). If even that cannot name them, the sentence\n// says so in words. It never prints an id: SCR-N-5 forbids showing one to a\n// person, and \"87a6e699…\" answers nobody's question anyway.\n\nimport type { FieldHistoryEntry, HistoryActor, Uuid } from \"@ai-matrx/records\";\n\n/** What the roster can tell us about a user id. `null` when it cannot. */\nexport type ResolveName = (userId: Uuid) => string | null;\n\n/** The honest stand-in when nobody and nothing can put a name to the author. */\nexport const SOMEBODY = \"somebody in this organization\";\n\n/** The platform's own word for itself. Never \"null\", never \"system_user\". */\nexport const THE_SYSTEM = \"the system\";\n\n/**\n * WHO, in the words a person reads. One place, so the grid cell, the record\n * timeline and the column history cannot drift into three different answers.\n */\nexport function actorWords(actor: HistoryActor, resolve?: ResolveName | undefined): string {\n if (actor.kind === \"system\") return THE_SYSTEM;\n const named = actor.name?.trim() || (actor.user_id && resolve ? resolve(actor.user_id)?.trim() : null) || null;\n if (actor.kind === \"agent\") {\n // An agent without the person it acted for cannot happen at the door — the\n // store refuses that write — but a screen still says something true if it does.\n const on = actor.on_behalf_of;\n const person = on?.name?.trim() || (on?.user_id && resolve ? resolve(on.user_id)?.trim() : null) || SOMEBODY;\n const approver = actor.approved_by\n ? actor.approved_by.name?.trim() || (resolve ? resolve(actor.approved_by.user_id)?.trim() : null) || SOMEBODY\n : null;\n return `${named ?? \"an assistant\"}, for ${person}${approver ? `, approved by ${approver}` : \"\"}`;\n }\n return named ?? SOMEBODY;\n}\n\n/** How long ago, in the words a person uses out loud. */\nexport function whenWords(at: string, now: Date = new Date()): string {\n const then = new Date(at);\n const age = now.getTime() - then.getTime();\n if (!Number.isFinite(age)) return \"at an unknown time\";\n if (age < 30 * 86_400_000) return formatRelativeTime(then, { style: \"intl\", now: now.getTime() });\n return then.toLocaleDateString();\n}\n\n/**\n * THE WHOLE ANSWER, as one sentence, ending in what to do to see the rest.\n * `null` is a real state and gets its own sentence: this column has never been\n * changed on this record since the store started keeping versions, which is not\n * the same as \"we could not find out\".\n */\n/**\n * WHAT HAPPENED, IN A PERSON'S WORDS (BREAKER-2 B2-29). The store words four operations and\n * lowercases any other (`RETYPE` → \"retype\"), so the panel read \"retype · admin · part of a bigger\n * change\". Every word the store can send is worded here; an unknown one is still written the way\n * a person writes it, never as a token. A soft delete is an archive, and says so.\n */\nconst OPERATION_WORDS: Record<string, string> = {\n created: \"Created\",\n edited: \"Edited\",\n deleted: \"Archived\",\n archived: \"Archived\",\n restored: \"Restored\",\n retype: \"Column type changed\",\n delete: \"Removed\",\n soft_delete: \"Archived\",\n insert: \"Created\",\n update: \"Edited\",\n restore: \"Restored\",\n};\n\nexport function operationWords(label: string | null | undefined): string {\n const raw = (label ?? \"\").trim();\n if (raw === \"\") return \"Changed\";\n const known = OPERATION_WORDS[raw.toLowerCase()];\n if (known) return known;\n const words = humanizeIdentifier(raw).toLowerCase();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nexport function lastChangeSentence(\n fieldLabel: string,\n entry: FieldHistoryEntry | null,\n resolve?: ResolveName | undefined,\n): string {\n if (!entry) {\n return `Nothing is recorded against ${fieldLabel} on this record yet. Press H for the whole history of this column.`;\n }\n const verb = operationWords(entry.operation_label).toLowerCase();\n return `${fieldLabel} — ${verb} by ${actorWords(entry.actor, resolve)}, ${whenWords(entry.occurred_at)}. Press H for the whole history.`;\n}\n\n/**\n * The SHORT form the badge wears once it knows: a name a person recognises at a\n * glance, in the width of a cell corner. Never a uuid, never an email's domain.\n */\nexport function actorBadge(actor: HistoryActor, resolve?: ResolveName | undefined): string {\n if (actor.kind === \"system\") return THE_SYSTEM;\n const words = actorWords(actor, resolve);\n if (words === SOMEBODY) return \"somebody\";\n const first = words.split(\",\")[0]!.trim();\n const at = first.indexOf(\"@\");\n return at > 0 ? first.slice(0, at) : first;\n}\n","// The withheld value's WORDS, with no React (`@ai-matrx/records-ui/helpers` serves this file alone).\n// The cell that shows them is `./withheld.tsx`, which re-exports these.\nimport type { Field, HiddenFieldNotice } from \"@ai-matrx/records\";\nimport { fieldName } from \"./names\";\n\n/** The word a withheld cell shows in place of a value. Never \"—\", which means empty. */\nexport const WITHHELD_WORD = \"Withheld\";\n\ntype Hidden = Readonly<Record<string, HiddenFieldNotice | undefined>> | null | undefined;\n\n/** The store's notice for this column of this row, when the store withheld it. */\nexport function withheldNoticeOf(hidden: Hidden, key: string): HiddenFieldNotice | null {\n const notice = hidden?.[key];\n return notice && typeof notice === \"object\" ? notice : null;\n}\n\n/**\n * THE SENTENCE, in the store's own words: the notice's `says` when the store wrote one (a\n * worked-out column names what it reads), else the notice's reason, the level it needs and the\n * other way in.\n */\nexport function withheldSentence(field: Pick<Field, \"key\" | \"label\" | \"name\">, notice: HiddenFieldNotice): string {\n const extra = notice as HiddenFieldNotice & { says?: unknown; part?: unknown; who?: unknown };\n if (typeof extra.says === \"string\" && extra.says.trim() !== \"\") return extra.says.trim();\n const name = fieldName(field as Field);\n // A PART'S NOTICE (access-setup PLAN §12.1): \"Hourly rate is in Pay — people with Record admin on this row see it.\"\n if (typeof extra.part === \"string\" && extra.part.trim() !== \"\" && typeof extra.who === \"string\" && extra.who.trim() !== \"\") {\n return `${name} is in ${extra.part.trim()} — ${extra.who.trim()} see it.`;\n }\n const reason = notice.reason ? ` It is ${notice.reason}` : \"\";\n const needs = notice.needs ? `${reason ? \",\" : \" It\"} and reading it needs ${notice.needs}` : \"\";\n const or = notice.or ? `${needs || reason ? \", or\" : \" It needs\"} ${notice.or}` : \"\";\n return `${name} is withheld from you.${reason}${needs}${or}${reason || needs || or ? \".\" : \"\"}`;\n}\n","// src/choiceLooks.ts — @ai-matrx/records-ui · merge tranche 6f (early)\n//\n// A CHOICE WEARS THE COLOUR ITS OWNER GAVE IT.\n//\n// The older /data grid let a person paint each choice of a list (\"Completed\" green, \"Scheduled\"\n// blue), and those colours are kept on the Field itself: `display_format.options.choices`, the\n// document `custom.field_update` writes (the /data Sheet's seam writes it the same way, and a\n// column's settings here write it back). The grid used to ignore it and HASH THE WORD into a\n// colour, so a column painted green on /data came up amber here, and a colour a person set in the\n// settings changed nothing on screen — the silent half of a setting.\n//\n// Pure: no React, no store call. The Field the grid already holds carries everything.\n\nimport { newerKindOf, type Field } from \"@ai-matrx/records\";\n// The values from the pure `table-style` subpath (no \"use client\", no grid): `@ai-matrx/records-ui/helpers`\n// reaches this file through `fieldTypeLabel`, and the data-table root would drag the whole grid with it.\nimport { CHOICE_CHIP_CLASS, NEUTRAL_CHIP_CLASS, STYLE_COLORS } from \"@ai-matrx/design-system/data-table/table-style\";\nimport type { ChoiceColorLookup, StyleColor } from \"@ai-matrx/design-system/data-table\";\n\n/** One choice's look, as the Field keeps it. */\nexport interface ChoiceLook {\n value: string;\n label?: string;\n color?: StyleColor;\n}\n\n/** The Field's display document, which the store keeps beside its declared keys. */\nexport function displayFormatOf(field: Field): { id: string; options: Record<string, unknown> } | null {\n const raw = (field as unknown as { display_format?: unknown }).display_format;\n if (!raw || typeof raw !== \"object\") return null;\n const id = (raw as { id?: unknown }).id;\n if (typeof id !== \"string\" || id === \"\") return null;\n const options = (raw as { options?: unknown }).options;\n return { id, options: options && typeof options === \"object\" ? (options as Record<string, unknown>) : {} };\n}\n\nfunction isStyleColor(v: unknown): v is StyleColor {\n return typeof v === \"string\" && (STYLE_COLORS as readonly string[]).includes(v);\n}\n\n/** Every choice look the Field keeps, in its own order. A colour outside the palette is dropped. */\nexport function choiceLooksOf(field: Field): ChoiceLook[] {\n const display = displayFormatOf(field);\n const choices = display?.options[\"choices\"];\n if (!Array.isArray(choices)) return [];\n const out: ChoiceLook[] = [];\n for (const c of choices) {\n if (!c || typeof c !== \"object\") continue;\n const value = (c as { value?: unknown }).value;\n if (typeof value !== \"string\" || value.trim() === \"\") continue;\n const color = (c as { color?: unknown }).color;\n const label = (c as { label?: unknown }).label;\n out.push({\n value: value.trim(),\n ...(typeof label === \"string\" && label.trim() !== \"\" ? { label: label.trim() } : {}),\n ...(isStyleColor(color) ? { color } : {}),\n });\n }\n return out;\n}\n\n/**\n * ONE WORD, HOWEVER IT WAS WRITTEN. A moved list keeps its words as `completed` / `truck_4` and\n * shows them as `Completed` / `Truck 4`; a colour kept against either spelling is the same choice.\n */\nexport function choiceWord(value: unknown): string {\n return String(value ?? \"\")\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\n/**\n * NOTION'S STATUS COLOURS, BY GROUP: To-do grey, In progress blue, Complete green. A status choice\n * the column paints nothing for wears its group's colour, so a fresh Status column reads the way\n * Notion's does with nobody picking a colour. A choice with a colour of its own keeps it\n * (`storedChoiceColor` asks the column first).\n */\nexport const STATUS_GROUP_COLOR = { todo: \"gray\", in_progress: \"blue\", done: \"green\" } as const satisfies Record<string, StyleColor>;\n\n/** The colour a status choice's GROUP gives it, or `undefined` when the column is not a Status. */\nexport function statusGroupColor(field: Field, value: unknown): StyleColor | undefined {\n if (newerKindOf(field) !== \"status\" || value === null || value === undefined || value === \"\") return undefined;\n const word = choiceWord(value);\n if (word === \"\") return undefined;\n const groups = ((field.config ?? {}) as Record<string, unknown>)[\"status_groups\"];\n if (groups && typeof groups === \"object\") {\n // the group is kept under the choice's KEY; the cell may hold its WORDS (\"In progress\" ↔ in_progress)\n for (const [key, group] of Object.entries(groups as Record<string, unknown>)) {\n if (choiceWord(key) !== word) continue;\n return group === \"in_progress\" ? STATUS_GROUP_COLOR.in_progress : group === \"done\" ? STATUS_GROUP_COLOR.done : STATUS_GROUP_COLOR.todo;\n }\n }\n return STATUS_GROUP_COLOR.todo;\n}\n\n/** The colour this Field keeps for this value, or `undefined` when it keeps none. */\nexport function storedChoiceColor(field: Field, value: unknown): StyleColor | undefined {\n if (value === null || value === undefined || value === \"\") return undefined;\n const word = choiceWord(value);\n if (word === \"\") return undefined;\n for (const look of choiceLooksOf(field)) {\n if (!look.color) continue;\n if (choiceWord(look.value) === word || (look.label && choiceWord(look.label) === word)) return look.color;\n }\n return statusGroupColor(field, value);\n}\n\n/**\n * THE COLOUR-BY LOOKUP, STORED COLOURS FIRST. A value whose column keeps a colour for it wears\n * that colour; any other value still paints, deterministically, through `fallback` — a colour-by\n * that silently did nothing would be the screen lying about a setting a person just changed.\n */\nexport function storedChoiceColorLookup(\n fields: readonly Field[],\n fallback: ChoiceColorLookup,\n): ChoiceColorLookup {\n const byKey = new Map(fields.map((f) => [f.key, f] as const));\n return (fieldKey, value) => {\n const field = byKey.get(fieldKey);\n return (field ? storedChoiceColor(field, value) : undefined) ?? fallback(fieldKey, value);\n };\n}\n\n/**\n * THE ORDER A NEW CHOICE IS PAINTED IN (Airtable): each choice added to a list takes the next\n * palette colour the list does not wear yet, so a fresh list reads as distinct chips with nobody\n * picking a colour. Grey last — it reads as \"no colour\".\n */\nexport const NEW_CHOICE_PALETTE: readonly StyleColor[] = [\"blue\", \"green\", \"amber\", \"red\", \"violet\", \"teal\", \"slate\"];\n\n/** The colour the next choice of a list takes, given the colours its choices already wear. */\nexport function nextChoiceColor(worn: ReadonlyArray<StyleColor | null | undefined>): StyleColor {\n const taken = new Set(worn.filter((c): c is StyleColor => Boolean(c)));\n const free = NEW_CHOICE_PALETTE.find((c) => !taken.has(c));\n return free ?? NEW_CHOICE_PALETTE[worn.length % NEW_CHOICE_PALETTE.length]!;\n}\n\n/**\n * THE ONE CHIP A CHOICE IS DRAWN AS — the grid cell, the picker's list and its chip, a form's\n * pills: the colour the column keeps for the value (by any of the words given — its key, its\n * label), else the neutral chip. One answer, so no surface paints a choice its own way.\n */\nexport function choiceChipLookOf(field: Field, ...words: unknown[]): { className: string; color: StyleColor | null } {\n for (const word of words) {\n const color = storedChoiceColor(field, word);\n if (color) return { className: CHOICE_CHIP_CLASS[color], color };\n }\n return { className: NEUTRAL_CHIP_CLASS, color: null };\n}\n","// src/docs.ts — @ai-matrx/records-ui · SCR-20, SCR-21, SCR-22\n//\n// DOCUMENTS OVER RECORDS. A template is a body whose tokens point at Fields BY\n// ID; a render is written ONCE and frozen with the SHA-256 of its own text; a\n// signature seals that exact version and is immutable once made.\n//\n// Three things this package refuses to re-implement, because the store already\n// does them and doing them twice is how two answers appear:\n// · which tokens a body contains — `custom.doc_tokens`;\n// · which of them this Table cannot answer, and why — `custom.doc_unresolved_tokens`;\n// · whether a signature still holds — `custom.doc_signature_intact`.\n//\n// The token syntax is the store's own: `{{field:<the field's id>}}`. A name\n// would be a token that silently stops resolving the day somebody renames a\n// Field, which is the exact failure REC-17 exists to prevent.\n\nimport type { Field, Uuid } from \"@ai-matrx/records\";\n\n/** The one token form, written the way the store's own pattern reads it. */\nexport function fieldToken(fieldId: Uuid): string {\n return `{{field:${fieldId}}}`;\n}\n\n/** THE DECLARATIVE SPEC an agent writes. One template, whole, in one object. */\nexport interface DocTemplateSpec {\n name: string;\n /** The body, tokens and all. */\n body: string;\n}\n\n/**\n * A template written in terms of Field KEYS — which is how an agent thinks and\n * how a person reads — turned into the store's id-based tokens. The agent writes\n * `{{title}}`; what is stored points at the Field's id, so a rename never\n * silently empties the document.\n *\n * A key this Table does not have is LEFT EXACTLY AS IT IS, so\n * `custom.doc_unresolved_tokens` names it and the screen shows the store's own\n * reason — it is never quietly deleted from the body.\n */\nexport function bodyFromKeys(body: string, fields: Field[]): string {\n const byKey = new Map(fields.map((f) => [f.key, f.id]));\n return body.replace(/\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g, (whole, key: string) => {\n const id = byKey.get(key);\n return id ? fieldToken(id) : whole;\n });\n}\n\n/** The reverse, for a person reading a stored body: ids shown as the Field's label. */\nexport function bodyForReading(body: string, fields: Field[]): string {\n const byId = new Map(fields.map((f) => [f.id, f.label || f.key]));\n return body.replace(/\\{\\{\\s*field:([0-9a-fA-F-]{36})\\s*\\}\\}/g, (whole, id: string) => {\n const label = byId.get(id);\n return label ? `{{${label}}}` : whole;\n });\n}\n\n/** VAL-10: a signature is a Value on a text Field whose format is `signature`. */\nexport function isSignatureField(field: Field): boolean {\n return field.type === \"text\" && (field.format ?? \"\").toLowerCase() === \"signature\";\n}\n","// src/fieldTypes.ts — @ai-matrx/records-ui\n//\n// EVERY KIND OF COLUMN THE STORE TAKES, IN THE WORDS A PERSON USES.\n//\n// The store publishes fourteen parity types and, for each, a sentence about what\n// it is MADE OF — \"REC-31: a File record reached through a relation, target the\n// kernel `File` Table\", \"FLD-9: a declared computation, on read or on write,\n// evaluated by custom.rule_eval\", \"config.via names the relation field,\n// config.pick the Value on the far side\". Those sentences are true and they are\n// for us. The 19 September verdict found them printed at the person choosing a\n// column type, alongside contract row ids, and called it what it is: machine\n// identity where a person is looking.\n//\n// So this file holds the OTHER sentence — what the type is FOR — and the field\n// panel shows that one. The store's own `made_of` stays where it belongs, in\n// `PARITY_MADE_OF`, for an engineer reading the package.\n//\n// The LIST itself is still the store's: `PARITY_FIELD_TYPES` is generated from\n// `custom.parity_field_types()`, and `assertEveryParityTypeIsExplained` (run by\n// the suite) fails the moment the store ships a fourteenth and nobody wrote a\n// sentence for it. A type with no explanation would otherwise appear with a\n// blank line under it, which is the silent version of the same defect. It caught the\n// fourteenth — `checkbox`, 2026-09-21 — exactly as it was written to.\n\nimport { DECLARABLE_FIELD_KINDS, NEWER_FIELD_KINDS, PARITY_FIELD_TYPES, type DeclarableFieldKind, type NewerFieldKind, type ParityFieldType } from \"@ai-matrx/records\";\n\n/** The three behaviours that carry no parity type of their own. */\nexport type PlainFieldType = \"text\" | \"long_text\" | \"number\";\n\n/**\n * THE ONE A PERSON COMING FROM AIRTABLE REACHES FOR FIRST, AND IT WAS NOT HERE.\n *\n * The list above offered `member` (a Person) and `attachment` (a File) — the two\n * relations whose target is a Table the PLATFORM keeps — and nothing at all for\n * \"this job belongs to that customer\". The store has taken it since 2026-09-20\n * (`custom.field_declare` with `type: \"relation\"` and a `relation_target`), the\n * Field type has named it since the same day, and `editorKindFor` has answered\n * `\"relation\"` for one since long before that. Only the menu did not offer it,\n * so the guide's section 3 told a person to pick a kind that did not exist.\n *\n * It is NOT a parity type of its own. `custom.parity_field_types()` ships\n * fourteen (the fourteenth is `checkbox`) and a relation is a BEHAVIOUR — the same behaviour `member` and\n * `attachment` are made of — so it sits beside the three plain ones as its own\n * word, and `parityTypesWithNoExplanation()` is unaffected by it in both\n * directions: it does not appear there, and it cannot hide a real gap there.\n */\nexport type RelationFieldType = \"relation\";\n\n/**\n * What a person picks in the panel — AND IT IS THE STORE'S OWN LIST, not a union\n * assembled here.\n *\n * 🚨 THE CLASS FIX (FIX-10B-F6, VERIFIER-10). This used to read\n * `ParityFieldType | PlainFieldType | RelationFieldType` — the parity floor plus two words\n * this file knew about — and that union was the whole defect. `custom.doc_sign` accepts\n * exactly one field, a text column whose format is `signature`, and it is granted to\n * `authenticated`; the Documents screen told a person in so many words to go and declare\n * one; and no list any screen could read named it, so the control did not exist and the\n * e-sign half of Documents was unreachable from the product. A kind the store takes and no\n * screen offers is invisible, and nothing failed — which is the only reason it survived.\n *\n * `custom.field_kinds()` is now the ONE registry and `DECLARABLE_FIELD_KINDS` is generated\n * from it, so the next kind the store learns arrives here as a TYPE ERROR in\n * `FIELD_TYPE_CHOICES`'s coverage and as a red `kindsWithNoChoice()` below — never as a\n * silence.\n */\nexport type PickableFieldType = DeclarableFieldKind | SystemFieldKind | \"entity_reference\" | NewerFieldKind | LookFieldKind;\n\n/**\n * A KIND THAT IS A PLAIN COLUMN WEARING A LOOK (grids review 3, lane H item 5). The store keeps a\n * column's \"Shows as\" on the Field (`display_format`, `custom.display_format_ids()` has `rating` and\n * `time`), so a Rating is a whole number shown as stars and a Time is a time of day kept as its\n * 24-hour text — declared through the ordinary door (`{ plain, display_format }`) the same way the\n * \"Shows as\" picker makes one. Offered whenever the store does not yet publish the kind as its own\n * word (`custom.field_kinds()`): a Rating the store publishes goes as `{ type: \"rating\", max }`.\n */\nexport type LookFieldKind = \"time\";\n\nexport interface LookKind {\n plain: \"number\" | \"text\";\n look: (args: { ratingMax: number }) => { id: string; options?: Record<string, unknown> };\n}\n\nexport const LOOK_KINDS: Partial<Record<PickableFieldType, LookKind>> = {\n rating: { plain: \"number\", look: ({ ratingMax }) => ({ id: \"rating\", options: { ratingMax } }) },\n time: { plain: \"text\", look: () => ({ id: \"time\" }) },\n};\n\n/** Is this kind declared as a plain column wearing a look (because the store does not publish it)? */\nexport function declaredAsLook(kind: string): boolean {\n return kind in LOOK_KINDS && !storePublishesKind(kind);\n}\n\n/**\n * LANE 10 P4: THE NINE NEWER KINDS (rating, duration, created by, last changed by, status, address,\n * barcode, formatted text, count). Each is listed in `FIELD_TYPE_CATALOGUE` below, so every reader\n * (Settings, the column panel, every layout) names a column of one by its own word; the MENU offers\n * one only once `custom.field_kinds()` publishes it (after the chair's apply and\n * `pnpm records:generate`), so a person is never offered a kind the live store would refuse.\n */\nexport function storePublishesKind(kind: string): boolean {\n return DECLARABLE_FIELD_KINDS.some((row) => (row.kind as string) === kind);\n}\n\n/**\n * LANE SC-R: a column that points at a PLATFORM thing (a note, a web site, an agent). Its menu entry\n * is drawn only once `custom.field_kinds()` publishes `entity_reference` (after the chair's apply\n * and `pnpm records:generate`), so the menu never offers a kind the live store would refuse.\n */\nexport const STORE_PUBLISHES_ENTITY_REFERENCE = DECLARABLE_FIELD_KINDS.some(\n (row) => (row.kind as string) === \"entity_reference\",\n);\n\n/**\n * THE STORE'S OWN STAMPS AS COLUMNS (GRID-PRIMITIVES G5; TABLE-PARITY H2 H3, lane\n * DEFAULT-GRID-PARITY). `custom.field_declare` takes these three WORDS and makes each a formula\n * Field whose expression is the system node (`fx.autonumber`, `fx.created_time`,\n * `fx.modified_time`, `config.system` naming which), so nobody types into them and every reader\n * that serves a worked-out value serves them. `custom.field_kinds()` does not publish them,\n * because each is a formula, not a behaviour of its own. So they are named here, sent as their\n * word through the `{ type }` arm, and a new autonumber column numbers the records already in\n * the table through `custom.autonumber_backfill`.\n */\nexport type SystemFieldKind = \"autonumber\" | \"created_time\" | \"modified_time\";\n\nexport const SYSTEM_FIELD_KINDS: readonly SystemFieldKind[] = [\"autonumber\", \"created_time\", \"modified_time\"];\n\nexport function isSystemFieldKind(id: string): id is SystemFieldKind {\n return (SYSTEM_FIELD_KINDS as readonly string[]).includes(id);\n}\n\nexport interface FieldTypeChoice {\n id: PickableFieldType;\n /** The word on the menu. */\n label: string;\n /** One line, under the word, saying what it is FOR. Never what it is made of. */\n explanation: string;\n /** The heading it sits under, so seventeen choices read as four short lists. */\n group: \"Words\" | \"Numbers and dates\" | \"Choices and people\" | \"Worked out\";\n}\n\n/**\n * EVERY KIND THIS PACKAGE CAN NAME AND DRAW, in menu order. The menu (`FIELD_TYPE_CHOICES`) is this\n * list less the kinds the live store does not take yet; every reader names a column from this one.\n */\nexport const FIELD_TYPE_CATALOGUE: FieldTypeChoice[] = [\n { id: \"text\", group: \"Words\", label: \"Text\", explanation: \"A short line of writing — a name, a reference, a title.\" },\n { id: \"long_text\", group: \"Words\", label: \"Long text\", explanation: \"Several lines — a description, notes, an address.\" },\n { id: \"rich_text\", group: \"Words\", label: \"Formatted text\", explanation: \"Notes with bold, lists and links.\" },\n { id: \"email\", group: \"Words\", label: \"Email address\", explanation: \"An email address, checked as you type and ready to write to.\" },\n { id: \"phone\", group: \"Words\", label: \"Phone number\", explanation: \"A phone number, checked as you type.\" },\n { id: \"url\", group: \"Words\", label: \"Link\", explanation: \"A web address that opens in a new tab when you click it.\" },\n { id: \"address\", group: \"Words\", label: \"Address\", explanation: \"A street address in parts, opened in maps.\" },\n { id: \"barcode\", group: \"Words\", label: \"Barcode\", explanation: \"A code a scanner reads, like a wristband or a box.\" },\n { id: \"button\", group: \"Words\", label: \"Button\", explanation: \"A button on each record that opens a link or runs an action.\" },\n\n { id: \"number\", group: \"Numbers and dates\", label: \"Number\", explanation: \"A plain number you can total up and sort by.\" },\n { id: \"currency\", group: \"Numbers and dates\", label: \"Money\", explanation: \"An amount of money, shown with its currency wherever it appears.\" },\n { id: \"percent\", group: \"Numbers and dates\", label: \"Percentage\", explanation: \"A number out of a hundred, kept between 0 and 100.\" },\n { id: \"rating\", group: \"Numbers and dates\", label: \"Rating\", explanation: \"A score in stars, set with one click.\" },\n { id: \"duration\", group: \"Numbers and dates\", label: \"Duration\", explanation: \"A length of time, typed as 45 min or 1:30.\" },\n { id: \"time\", group: \"Numbers and dates\", label: \"Time\", explanation: \"A time of day with no date — a shift start, opening hours.\" },\n { id: \"datetime\", group: \"Numbers and dates\", label: \"Date\", explanation: \"A day, or a day and a time. Dates are what the calendar view reads.\" },\n\n { id: \"select\", group: \"Choices and people\", label: \"Choice\", explanation: \"One answer from a list you write here. Choices are what the board view groups by.\" },\n { id: \"multi_select\", group: \"Choices and people\", label: \"Several choices\", explanation: \"Any number of answers from a list you write here.\" },\n { id: \"status\", group: \"Choices and people\", label: \"Status\", explanation: \"Choices grouped into To do, In progress and Done.\" },\n { id: \"checkbox\", group: \"Choices and people\", label: \"Yes / no\", explanation: \"A box you tick. Left alone it stays unanswered, which is not the same as a no.\" },\n { id: \"member\", group: \"Choices and people\", label: \"Person\", explanation: \"Somebody in this organization, picked from the people who are in it.\" },\n { id: \"attachment\", group: \"Choices and people\", label: \"File\", explanation: \"A file or a picture kept with the record.\" },\n { id: \"signature\", group: \"Choices and people\", label: \"Signature\", explanation: \"Somebody's sign-off on a document made from this record. Nobody types in it — it fills in when they sign.\" },\n { id: \"relation\", group: \"Choices and people\", label: \"Points at another record\", explanation: \"A link to records in another of your tables — the customer a job is for, the lines on an invoice.\" },\n {\n id: \"entity_reference\",\n group: \"Choices and people\",\n label: \"Points at something else on the platform\",\n explanation: \"A note, a web site, an agent, a workbook — anything you keep here that is not one of your tables.\",\n },\n\n { id: \"lookup\", group: \"Worked out\", label: \"Borrowed value\", explanation: \"A value read from a record this one points at, kept in step with it.\" },\n { id: \"rollup\", group: \"Worked out\", label: \"Total of linked records\", explanation: \"A count or a total worked out from the records this one points at.\" },\n { id: \"count\", group: \"Worked out\", label: \"Count of linked records\", explanation: \"How many records this one points at.\" },\n { id: \"formula\", group: \"Worked out\", label: \"Worked out from other columns\", explanation: \"A value this record works out for itself from its own other columns.\" },\n { id: \"autonumber\", group: \"Worked out\", label: \"Record number\", explanation: \"A number the table gives each record, one after another, never reused. Records already here are numbered in the order they were made.\" },\n { id: \"created_time\", group: \"Worked out\", label: \"Created time\", explanation: \"When each record was made. Nobody types it.\" },\n { id: \"modified_time\", group: \"Worked out\", label: \"Last changed time\", explanation: \"When each record last changed. Nobody types it.\" },\n { id: \"created_by\", group: \"Worked out\", label: \"Created by\", explanation: \"Who made each record. Nobody types it.\" },\n { id: \"modified_by\", group: \"Worked out\", label: \"Last changed by\", explanation: \"Who last changed each record. Nobody types it.\" },\n];\n\n/** The kinds the menu offers only once the live store publishes them. */\nconst GATED_KINDS = new Set<string>([\"entity_reference\", ...NEWER_FIELD_KINDS]);\n\nexport const FIELD_TYPE_CHOICES: FieldTypeChoice[] = FIELD_TYPE_CATALOGUE.filter(\n (c) => !GATED_KINDS.has(c.id) || storePublishesKind(c.id) || c.id in LOOK_KINDS,\n);\n\nexport const FIELD_TYPE_GROUPS: FieldTypeChoice[\"group\"][] = [\n \"Words\",\n \"Numbers and dates\",\n \"Choices and people\",\n \"Worked out\",\n];\n\nconst BY_ID = new Map<string, FieldTypeChoice>(FIELD_TYPE_CATALOGUE.map((c) => [c.id, c]));\n\nexport function fieldTypeChoice(id: string): FieldTypeChoice | undefined {\n return BY_ID.get(id);\n}\n\n/** The three that are a plain behaviour rather than one of the fourteen. */\nexport function isPlainFieldType(id: string): id is PlainFieldType {\n return id === \"text\" || id === \"long_text\" || id === \"number\";\n}\n\n/**\n * The one that is a relation a person aims themselves. It is neither plain nor\n * one of the thirteen, so every caller that branched on `isPlainFieldType`\n * alone has to answer this question too — which is why it is a named function\n * and not an inline string comparison at four call sites.\n */\nexport function isRelationFieldType(id: string): id is RelationFieldType {\n return id === \"relation\";\n}\n\n/**\n * One of FLD-11's fourteen, which is what `parity_type` on a declaration means.\n *\n * Read from the store's own generated floor, never from a list here — because a kind that\n * is NOT one of the fourteen (plain text, a person-aimed relation, a signature) has to be\n * declared by its WORD (`type`) and a panel that sent `parity_type` for it would be refused\n * \"There is no field type called …\". That is the shape of the F6 defect: the panel's `save`\n * had exactly two branches, relation and plain, and everything else fell into `parity_type`.\n */\nexport function isParityFieldType(id: string): id is ParityFieldType {\n return PARITY_FIELD_TYPES.some((row) => row.parity_type === id);\n}\n\n/**\n * THE GUARD THIS FILE NEEDS, because the list it explains is generated.\n *\n * Returns the parity types the store ships that nothing here explains. The\n * suite asserts it is empty; a fourteenth type would otherwise reach the menu\n * with a blank line under it and nobody would notice until a person did.\n *\n * `relation` is deliberately NOT one of the thirteen and therefore never\n * appears in this answer: `PARITY_FIELD_TYPES` is the store's own generated\n * list and a behaviour that carries no parity type is not in it.\n */\nexport function parityTypesWithNoExplanation(): string[] {\n return PARITY_FIELD_TYPES.map((row) => row.parity_type).filter((type) => !BY_ID.has(type));\n}\n\n/**\n * 🚨 THE GUARD THAT CLOSES THE CLASS (FIX-10B-F6).\n *\n * `parityTypesWithNoExplanation()` above watches the parity FLOOR, and the floor was never\n * the whole list: the store also declares plain text, long text, number, a person-aimed\n * relation and a signature, and for months nothing anywhere compared the panel's menu with\n * THAT. So `signature` — the one field `custom.doc_sign` accepts, on a door granted to\n * every signed-in person, named to the person by the Documents screen itself — was absent\n * from the menu and every check stayed green.\n *\n * This returns the kinds `custom.field_kinds()` publishes that this panel does not offer.\n * The suite asserts it is empty, so a kind the store learns tomorrow either reaches the\n * person or turns a test red the same day. It is the class, not the instance: it would have\n * caught `signature`, and it will catch the twentieth kind nobody has thought of yet.\n */\nexport function kindsWithNoChoice(): string[] {\n return DECLARABLE_FIELD_KINDS.map((row) => row.kind).filter((kind) => !BY_ID.has(kind));\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// THE WORDS ON THE TWO BUTTONS, IN ONE PLACE — because they used to be the SAME\n// WORDS in two places, and that cost somebody their column.\n//\n// 🚨 THE DEFECT (TAILS-5, 2026-09-21, measured while driving the panel headless\n// on Rincon Plumbing's Summerland branch). The Jobs toolbar's control that OPENS\n// the field panel read exactly \"Add field\", and the panel's own SAVE button read\n// exactly \"Add field\" too — `FieldEditor` says \"Save field\" only when EDITING an\n// existing column, so on the create path the two were character-identical. The\n// toolbar control is a TOGGLE: pressing it while the panel is open closes the\n// panel and throws away everything typed into it. So a person who typed a column\n// name, chose a type, picked the target table and its display columns, then\n// looked up at the words they were told to press, could lose all of it — with no\n// sentence, no request sent, and nothing to say it had happened. A walk written\n// by an agent lost the column twice that way and spent a round diagnosing the\n// store before reading the two labels side by side.\n//\n// WHY THEY LIVE HERE AND NOT IN `@ai-matrx/design-system`. The words are RECORDS\n// vocabulary — \"field\", \"column\" — and the design system knows nothing about\n// either; a labels module there would be a domain word in a generic package, and\n// the next person adding a button would not find it. This file is already the\n// one place that owns what a person READS about a column (`FIELD_TYPE_CHOICES`\n// is right above), and both surfaces that draw these buttons are in this\n// package. One module, one import each, and `fieldTypes.test.ts` asserts the two\n// can never be the same string again.\n//\n// THE RULE: the control that OPENS a panel and the control that COMMITS it never\n// read the same words. The opener is an invitation; the committer says what it\n// will make.\n\n/** The toolbar control that OPENS the add-a-column panel. An invitation. */\nexport const ADD_FIELD_OPEN_LABEL = \"Add column\";\n\n/** The panel's own primary, on the CREATE path. It says what pressing it makes. */\nexport const ADD_FIELD_SAVE_LABEL = \"Create column\";\n\n/** The panel's own primary when an existing column is being changed. */\nexport const EDIT_FIELD_SAVE_LABEL = \"Save column\";\n","// src/parity.ts — @ai-matrx/records-ui\n//\n// FLD-11, the parity floor. A Field stores a BEHAVIOUR (one of five) plus\n// modifiers, unit and format; a person picked one of THIRTEEN parity types in\n// the UI. This resolves the second from the first, using the same facts the\n// store's own `custom.parity_field_types()` describes — which is generated into\n// `@ai-matrx/records` and imported here, never retyped.\n//\n// Nothing in this file decides policy. It decides which editor to draw.\n\nimport { displayFormatOf } from \"./choiceLooks\";\nimport {\n KERNEL_TABLES,\n PARITY_FIELD_TYPES,\n fieldKindFor,\n newerKindOf,\n type Field,\n type FieldKind,\n type KernelTargets,\n type ParityFieldType,\n} from \"@ai-matrx/records\";\nimport { isButtonField } from \"@ai-matrx/records\";\nimport { humanizeIdentifier } from \"@ai-matrx/kit/text-case\";\nimport { isSignatureField } from \"./docs\";\nimport { fieldTypeChoice, isSystemFieldKind, type PickableFieldType } from \"./fieldTypes\";\n\n/** The kernel ids come from the store's own kernel rows, never from a literal here. */\nconst kernelId = (name: string): string =>\n KERNEL_TABLES.find((t) => t.name === name)?.id ?? \"\";\nconst KERNEL_FILE_TABLE = kernelId(\"File\");\nconst KERNEL_PERSON_TABLE = kernelId(\"Person\");\n\nexport const PARITY_LABEL: Record<ParityFieldType, string> = {\n attachment: \"Attachment\",\n checkbox: \"Yes / no\",\n currency: \"Currency\",\n datetime: \"Date\",\n email: \"Email\",\n formula: \"Formula\",\n lookup: \"Lookup\",\n member: \"Person\",\n multi_select: \"Multi-select\",\n percent: \"Percent\",\n phone: \"Phone\",\n rollup: \"Rollup\",\n select: \"Select\",\n url: \"Link\",\n};\n\n/** The \"made of\" sentence the store itself publishes, for the field editor's help text. */\nexport const PARITY_MADE_OF: Record<string, string> = Object.fromEntries(\n PARITY_FIELD_TYPES.map((p) => [p.parity_type, p.made_of]),\n);\n\n/**\n * Plain text and plain number are behaviours with no parity type of their own.\n *\n * 🚨 THIS IS NOW AN ALIAS, AND THAT IS THE POINT (TAILS-3, 2026-09-21). The\n * classifier moved into `@ai-matrx/records` as `FieldKind` so that the editor a\n * person types into and the COERCION applied to what they typed are decided by\n * one body. They were decided by two, and the public booking page paid for it:\n * \"Vehicle Year takes a number, and it was given a string\" on every table with\n * a number field.\n */\nexport type EditorKind = FieldKind;\n\n/** The kernel ids come from the store's own kernel rows, never from a literal here. */\nconst KERNEL: KernelTargets = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };\n\n/**\n * Which editor one Field wants. Reads the Field's own declaration only — the\n * behaviour, the modifiers, the format, the relation target and the config —\n * because those are what the store stores, and a screen that guessed from a\n * value would draw a different editor for an empty record.\n *\n * It is `fieldKindFor` with this package's kernel ids bound. Nothing here\n * decides anything the core does not.\n */\nexport function editorKindFor(field: Field): EditorKind {\n return fieldKindFor(field, KERNEL);\n}\n\n/**\n * WHICH KIND A PERSON PICKED, read back off the Field the store keeps — the ONE reader every\n * \"what does this column hold\" answer goes through (the Settings column list, the column panel).\n *\n * `editorKindFor` answers which EDITOR to draw, and for two families that is not the kind a\n * person picked: a Signature is a text Field whose format is `signature` (it draws a line of\n * text), and the store's stamps — Record number, Created time, Last changed time — are formula\n * Fields whose `config.system` names the stamp. Read through the editor classifier alone, the\n * Settings list called a Signature \"Text\" and a Record number \"Worked out from other columns\"\n * (lane 10 W1-A, guard `every-pickable-kind-reads-back-as-itself.test.tsx`).\n *\n * `undefined` when the Field is none of the kinds the menu offers.\n */\nexport function pickableTypeOf(field: Field): PickableFieldType | undefined {\n if (isSignatureField(field)) return \"signature\";\n // NOTION-PROPS-3: a button is text wearing the format `button`.\n if (isButtonField(field)) return \"button\";\n // LANE 10 P4: the nine newer kinds, each a base type wearing a format (`newerKindOf`).\n const newer = newerKindOf(field);\n if (newer) return newer;\n // A RATING OR A TIME made as a plain column wearing its look (`LOOK_KINDS`): named by that kind.\n const look = displayFormatOf(field)?.id;\n if (look === \"rating\" && field.type === \"range\") return \"rating\";\n if (look === \"time\" && field.type === \"text\") return \"time\";\n const system = (field.config as Record<string, unknown> | undefined)?.[\"system\"];\n if (field.type === \"formula\" && typeof system === \"string\" && isSystemFieldKind(system)) return system;\n const kind = editorKindFor(field);\n return fieldTypeChoice(kind) ? (kind as PickableFieldType) : undefined;\n}\n\n/**\n * The type word a person sees in a header or a settings row — and it is the\n * SAME word the field panel offers, read from `FIELD_TYPE_CHOICES`, so a column\n * added as \"Money\" is never described anywhere else as \"Currency\".\n */\nexport function fieldTypeLabel(field: Field): string {\n const kind = pickableTypeOf(field) ?? editorKindFor(field);\n const known = fieldTypeChoice(kind)?.label;\n if (known) return field.multi && kind !== \"multi_select\" ? `${known} (many)` : known;\n const plain: Record<string, string> = {\n // `boolean` is the BEHAVIOUR word; `checkbox` is the type a person picks. Both are\n // written the way a person writes them, never as a raw token.\n boolean: \"Yes / no\",\n date: \"Date\",\n json: \"Structured data\",\n relation: \"Link to a record\",\n };\n // Never a raw token on a screen: an editor kind this table has not named is\n // still written the way a person writes it.\n return plain[kind] ?? humanizeIdentifier(kind);\n}\n","// src/tablePickerEntries.ts — @ai-matrx/records-ui (pure; also served by `@ai-matrx/records-ui/helpers`)\n//\n// THE RULE for which tables a \"choose a table\" control offers (the full reasoning heads\n// `./tablePicking.tsx`, which re-exports everything here and adds the hook and the toggle).\n// No React, no store call: a host's picker data and a server read ask the same answer.\n\n/** The facts a picker needs about one table, whichever list it came from. */\nexport interface PickerTable {\n id: string;\n name: string;\n platform_owned?: boolean | null | undefined;\n /** The Table document's own key for the same fact (`tableList()` spreads the document onto the Table). */\n kept_by_the_app?: boolean | null | undefined;\n /** `custom.table_facts`: which feature keeps it (`choices` for a value set). */\n kept_for?: string | null | undefined;\n /** `custom.data_home_tables`: the store's one word for what it is (`list` for a value set). */\n kind?: string | null | undefined;\n /** The table a value set belongs to (`custom.table_facts.used_in_table_id`). */\n used_in_table_id?: string | null | undefined;\n /** A checklist's steps, a booking page's slots, a workflow's states. */\n work_kind?: unknown;\n slug?: string | null | undefined;\n /** When the table was made — what tells two lists apart once their column is gone. */\n created_at?: string | null | undefined;\n}\n\n/** The slug prefix of the tables this package keeps for its own bookkeeping (saved views, forms …). */\nconst PLATFORM_TABLE_PREFIX = \"records_ui_\";\n\n/** The store's flag (`platform_owned`) or the Table document's own key (`kept_by_the_app`) — either says the app keeps it. */\nfunction keptByPlatform(table: PickerTable): boolean {\n return table.platform_owned === true || table.kept_by_the_app === true;\n}\n\n/** A value set: the List a choice column keeps its choices in. */\nexport function isValueSet(table: PickerTable): boolean {\n if (table.kept_for === \"choices\") return true;\n return table.kept_for == null && table.kind === \"list\" && keptByPlatform(table);\n}\n\n/** Anything the app keeps for itself rather than a table a person keeps. */\nexport function isKeptTable(table: PickerTable): boolean {\n return (\n keptByPlatform(table) ||\n isValueSet(table) ||\n Boolean(table.work_kind) ||\n (table.slug ?? \"\").startsWith(PLATFORM_TABLE_PREFIX)\n );\n}\n\nexport interface TablePickerEntry<T extends PickerTable> {\n table: T;\n id: string;\n /** Unique among the entries returned. */\n label: string;\n /** A value set (shown only with \"Show lists\", or because it is the chosen one). */\n isList: boolean;\n}\n\nexport interface TablePickerEntries<T extends PickerTable> {\n entries: TablePickerEntry<T>[];\n /** How many value sets \"Show lists\" would add — 0 means the control is not worth drawing. */\n listCount: number;\n}\n\nconst CHOICES_SUFFIX = / choices$/i;\n\n/**\n * THE ONE RULE. Tables first (by name), then — with `showLists` — the value sets, each named\n * \"<column> — <owning table>\". Any names still equal get \" (2)\", \" (3)\" so no two entries match.\n */\nexport function tablePickerEntries<T extends PickerTable>(\n tables: readonly T[],\n options: { showLists?: boolean; keep?: string | null | undefined } = {},\n): TablePickerEntries<T> {\n const byId = new Map(tables.map((t) => [t.id, t]));\n const lists = tables.filter(isValueSet);\n const listLabel = (t: T): string => {\n const column = t.name.replace(CHOICES_SUFFIX, \"\");\n // A LIST WHOSE COLUMN IS GONE: `custom.table_facts` names the list itself (or nothing) as where\n // it is used. Said so, and told apart by the day it was made — never by a counter.\n if (!t.used_in_table_id || t.used_in_table_id === t.id) {\n const made = madeOn(t.created_at);\n return made ? `${column} — no column · ${made}` : `${column} — no column`;\n }\n const owner = byId.get(t.used_in_table_id);\n // Owned by a table this person cannot see: its own name, as the store gave it.\n if (!owner) return t.name;\n return `${column} — ${owner.name}`;\n };\n const plain = tables\n .filter((t) => !isKeptTable(t) || (t.id === options.keep && !isValueSet(t)))\n .map((t) => ({ table: t, id: t.id, label: t.name, isList: false }))\n .sort((a, b) => a.label.localeCompare(b.label));\n const shownLists = lists\n .filter((t) => options.showLists || t.id === options.keep)\n .map((t) => ({ table: t, id: t.id, label: listLabel(t), isList: true }))\n .sort((a, b) => a.label.localeCompare(b.label));\n const entries = [...plain, ...shownLists];\n const seen = new Map<string, number>();\n for (const entry of entries) {\n const key = entry.label.toLowerCase();\n const n = (seen.get(key) ?? 0) + 1;\n seen.set(key, n);\n if (n > 1) entry.label = `${entry.label} (${n})`;\n }\n return { entries, listCount: lists.length };\n}\n\n/** \"Sep 3, 2026\" in the reader's own locale, or null when the store gave no date. */\nfunction madeOn(stamp: string | null | undefined): string | null {\n if (!stamp) return null;\n const at = new Date(stamp);\n if (Number.isNaN(at.getTime())) return null;\n return new Intl.DateTimeFormat(undefined, { month: \"short\", day: \"numeric\", year: \"numeric\" }).format(at);\n}\n","/**\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"]}
|