import { U as Uuid, D as DeclarableFieldKind, o as FieldBehavior, p as ParityFieldType, b as FieldSensitivity, c as ContextPolicy, C as ComputeOn, O as OnDelete, F as FieldSource, g as Timestamp } from './record-C3thPhpR.js'; /** * EVERYTHING THIS FILE NEEDS TO KNOW ABOUT A FIELD, AND NOTHING ELSE. * * A full `Field` satisfies it, and so does every narrower shape the store * hands to a screen β€” `CaptureField` on a phone with no network, the Field * documents `custom.form_public` and `custom.booking_public` publish to a * stranger. Asking for the whole `Field` would have meant the capture sheet * keeping its own `Number(raw)`, which is exactly the duplication this file * exists to end. */ interface CoercibleField { /** Named so the doc above is checkable: a full {@link Field} satisfies this. */ key: string; label?: string | null; name?: string | null; /** FLD-1. One of five behaviors; an unknown word is treated as text. */ type?: string | null; multi?: boolean | null; unit?: string | null; format?: string | null; config?: Record | null | undefined; relation_target?: string | null; } /** * The kind of value one Field holds, resolved from its DECLARATION only β€” the * behavior, the modifiers, the format, the relation target and the config, * because those are what the store stores. A screen that guessed from a value * would answer differently for an empty record. * * `plain_text`, `long_text`, `number` and `boolean` are behaviors that carry * no parity type of their own; every other word here is a parity type. */ declare const FIELD_KINDS: readonly ["attachment", "checkbox", "currency", "datetime", "email", "entity_reference", "formula", "json", "long_text", "lookup", "member", "multi_select", "number", "percent", "phone", "relation", "rollup", "select", "text", "url"]; /** * 🚨 THE UNION IS DERIVED FROM THE LIST ABOVE, and the list is what the * coverage test walks. Add a kind here and the test drives it; give it no arm * in `coerceTypedAnswer` and the test says so, instead of the new kind falling * quietly through `default:` and a value of the wrong shape reaching the store * β€” which is the whole defect this file closes. */ type FieldKind = (typeof FIELD_KINDS)[number]; /** The kernel Table ids REC-31 pins the two special relations to. */ interface KernelTargets { /** The kernel `File` Table β€” a relation pointing here is an attachment. */ file?: string | undefined; /** The kernel `Person` Table β€” a relation pointing here names a person. */ person?: string | undefined; } /** * Which kind of value this Field holds. * * This is the ONE classifier. `@ai-matrx/records-ui`'s `editorKindFor` β€” which * decides which editor to DRAW β€” delegates to it, so the editor a person types * into and the coercion applied to what they typed can never disagree about * the same column. They did disagree before this moved here; that is the whole * class of grid defect September was spent closing. */ declare function fieldKindFor(field: CoercibleField, kernel?: KernelTargets): FieldKind; /** One Field's options, indexed the way a typed word has to be looked up. */ interface ChoiceOptions { /** * Lower-cased label, option key and option id, each pointing at the OPTION * RECORD'S ID β€” which is what a list Field stores (FLD-5/FLD-6). */ byWord: Map; /** The labels, in order, so a refusal can say what is on offer. */ labels: string[]; } /** What one answer became, or the sentence saying why it did not. */ type Coerced = { value: unknown; words?: string; } | { refusal: string; unmatched?: string[]; unmatchedNames?: string[]; ambiguousNames?: string[]; }; /** One record a name matched. */ interface RelationNameMatch { id: string; /** What its name column holds. */ words: string; } /** A relation's target Table, indexed by the names a column holds. */ interface RelationNameIndex { /** `relationNameKey(name)` β†’ the records of that name the reader may see. */ byName: ReadonlyMap; /** The target Table's name, for the sentence. */ tableName: string; /** The target Table's name column, so a screen can make a record a name matched nothing. */ titleField: string | null; } /** The answer of `custom.relation_names_match`. */ interface RelationNamesAnswer { table_id: string; table_name: string | null; title_field?: string | null; names: Array<{ name: string; matches: RelationNameMatch[]; }>; } /** * How two spellings of one name are told apart: case and runs of spacing do not * count. The store's `custom.relation_name_key` is the same rule. */ declare function relationNameKey(name: string): string; /** How an import splits a cell that holds several values: `;`, `,` or `|` β€” the store's own rule. */ declare function splitListCell(text: string): string[]; /** Index the store's answer the way the coercion reads it. */ declare function relationNameIndexOf(answer: RelationNamesAnswer): RelationNameIndex; /** Every name a relation cell carries, split the way the column reads it. */ declare function relationNamesIn(field: Pick, text: string): string[]; interface CoerceOptions { /** This Field's choices, when the caller has loaded them. */ options?: ChoiceOptions | undefined; /** The kernel File/Person Table ids, for classifying relations. */ kernel?: KernelTargets | undefined; /** * The Field's name as this caller writes it. Defaults to the Field's own * label. A screen passes the same words it printed above the box. */ label?: string | undefined; /** * A relation column's target, indexed by name (`relationNameIndexOf`). Without it a name in a * relation column is refused as before, and only ids pass. */ relationNames?: RelationNameIndex | undefined; /** The reader's locale, for the order of a numeric date (10/03/2026). Defaults to the runtime's. */ locale?: string | undefined; /** * The column SHOWS AS a percent (its "Shows as" format, which this package does not read), so a * typed `45%` is a percent here even on a number column. The caller scales it to the column's * own percent scale. */ showsAsPercent?: boolean | undefined; } /** One id, written the way the store writes one (REC-51's shape test). */ /** * SC-R / P12. Whether a relation's config makes it an ENTITY REFERENCE: target * mode `any` (REL-8) restricted by a non-empty `allowed_types`. */ declare function isEntityReferenceConfig(config: Record | null | undefined): boolean; /** The platform kinds an entity-reference Field may name, lower-cased, in its own order. */ declare function entityReferenceKindsOf(field: Pick): string[]; /** The Field's name in plain words β€” never its key on a screen. */ declare function coercionLabel(field: CoercibleField): string; declare function coerceTypedAnswer(field: CoercibleField, raw: string, opts?: CoerceOptions): Coerced; /** What a whole set of typed-in answers became. */ interface CoercedAnswers { /** Ready to send to the store. Only the keys that coerced cleanly. */ values: Record; /** Field key β†’ the sentence saying why that answer was not written. */ refusals: Record; /** The field keys the caller sent that this table has no Field for. */ unknownKeys: string[]; } /** * Every typed-in answer on one screen, coerced together β€” the shape the two * public doors and the import wizard actually need. * * Keys the table has no Field for are reported rather than dropped: the store * refuses an unknown key by name and a caller that quietly deleted it would * hide a real mismatch between a page and its table. */ declare function coerceTypedAnswers(fields: readonly CoercibleField[], answers: Readonly>, opts?: { kernel?: KernelTargets | undefined; /** Choices per Field key, for the list columns. */ options?: ReadonlyMap | undefined; /** Field key β†’ the words that screen printed above the box. */ labels?: Readonly> | undefined; /** Relation columns' targets, indexed by name, by field key (VISION-REACH G1). */ relationNames?: ReadonlyMap | undefined; }): CoercedAnswers; declare const BUTTON_DOES: readonly ["open_url", "run_action", "run_automation", "add_row"]; type ButtonDoes = (typeof BUTTON_DOES)[number]; /** What `config.button` holds. */ interface ButtonSettings { label: string; /** One of the table's color names (blue, green, …); absent, the button wears the neutral look. */ color?: string | undefined; /** Absent: nothing is set up yet, and a press says so. */ do?: ButtonDoes | undefined; url?: string | undefined; action_id?: Uuid | undefined; automation_id?: Uuid | undefined; /** W2-A `add_row`: the table the new row goes in; absent = this table. */ table_id?: Uuid | undefined; /** W2-A `add_row`: the new row's field key β†’ this row's field key, or "@row" for this row's id (a link back). */ prefill?: Record | undefined; /** TEMPLATE-GAPS R3 `add_row`: the target table's row template the new row starts from (its values under prefill). */ template_id?: Uuid | undefined; } /** What `custom.button_press` answers. */ type ButtonPressed = { do: "open_url"; label: string; url: string; } /** W2-A: the row the press added (through custom.record_write), for the screen to open. */ | { do: "add_row"; label: string; table_id: Uuid; record_id: Uuid; template_id?: Uuid; } | { do: "run_action"; label: string; result: { ran?: number; records?: Array<{ record_id: Uuid; version: number; }>; } & Record; } | { do: "run_automation"; label: string; /** null when no automation this button starts met its conditions (status "skipped"). */ run_id: Uuid | null; automation: string | null; status: string | null; says: string | null; /** AUTOMATIONS-AIRTABLE: every automation whose trigger is this button, as each ran on the press. */ runs?: Array<{ run_id: Uuid; automation: string | null; status: string | null; says: string | null; }>; }; /** Whether a Field is a button column β€” `custom.field_kind_of`'s mirror for this one kind. */ declare function isButtonField(field: CoercibleField | null | undefined): boolean; /** A button column's settings, read off its declaration; null for any other column. */ declare function buttonOf(field: (CoercibleField & { label?: string | null; }) | null | undefined): ButtonSettings | null; /** The `{{field id}}` placeholders a link names, in order β€” so a settings screen can show them as names. */ declare function buttonUrlPlaceholders(url: string): string[]; /** * What a button's settings are missing before a press can work, in words; null when it is set up. * The store refuses the same cases (`custom._field_kind_shape_ok`, `custom.button_press`). */ declare function buttonNeeds(b: ButtonSettings): string | null; declare const NEWER_FIELD_KINDS: readonly ["rating", "duration", "created_by", "modified_by", "status", "address", "barcode", "rich_text", "count"]; type NewerFieldKind = (typeof NEWER_FIELD_KINDS)[number]; /** Which of the nine newer kinds a Field is, read off its declaration β€” `custom.field_kind_of`'s mirror. */ declare function newerKindOf(field: CoercibleField): NewerFieldKind | null; /** A rating's top star: its max Rule (1–10), else 5 β€” the store's own default. */ declare function ratingMaxOf(field: CoercibleField): number; /** Seconds as h:mm β€” and h:mm:ss only when there are seconds to show. 2700 β†’ "0:45". */ declare function durationText(seconds: unknown): string | null; /** * A length of time as a person types it, in seconds. "45" and "45 min" are 45 minutes (the column * reads in h:mm, so a bare number is minutes, as in Airtable); "1:30" is an hour and a half; * "1:30:15" has seconds; "1h 30m", "2 hours", "90s" read as written. Null: not a length of time. */ declare function readTypedDuration(text: string): number | null; declare const ADDRESS_PARTS: readonly ["street", "city", "region", "postal_code", "country"]; type AddressPart = (typeof ADDRESS_PARTS)[number]; type AddressValue = Partial>; declare function isAddressValue(value: unknown): value is AddressValue; /** An address on one line, the way an envelope reads: "1420 Cedar Ridge Pkwy, Boulder, CO 80301, US". */ declare function addressValueText(value: unknown): string | null; /** * One typed line as an address in parts. "street, city, region postal, country" is read into its * parts when it has that shape; anything else is kept whole as the street, so nothing typed is lost. */ declare function addressFromText(text: string): AddressValue | null; /** A maps search for an address, opened in a new tab. */ declare function addressMapUrl(value: unknown): string | null; /** * `{id, name, actor}` as the store works it out (`custom.formula_value`). `actor` is the kind of * writer β€” a person (`user`), an agent (`agent`, with `id`/`name` the person it wrote for) or the * platform itself (`system`, with no id and no name). `name` is null for somebody outside the * organization. A value written before the store said `actor` is a person's. */ type PersonStamp = { id: string | null; name: string | null; actor?: "user" | "agent" | "system"; }; declare function isPersonStamp(value: unknown): value is PersonStamp; /** The words for who made or changed a record. */ declare const NOT_A_MEMBER_WORDS = "Not in this organization"; declare const SYSTEM_WRITER_WORDS = "System"; declare const AGENT_WRITER_WORDS = "Agent"; declare function personStampText(value: unknown): string | null; declare const STATUS_GROUPS: readonly ["todo", "in_progress", "done"]; type StatusGroup = (typeof STATUS_GROUPS)[number]; declare const STATUS_GROUP_WORDS: Record; /** Which group a status choice sits in; a choice the column names no group for is To do. */ declare function statusGroupOf(field: CoercibleField, choiceKey: string): StatusGroup; declare const BARCODE_SYMBOLOGIES: readonly ["code128", "code39", "ean13", "ean8", "upc_a", "upc_e", "itf", "qr", "data_matrix", "pdf417"]; /** Markdown read as plain words on one line β€” what a card title or a sentence shows. */ declare function richTextPlain(text: string): string; /** * A link destination as a browser would follow it: HTML entities and backslash escapes decoded, * spaces and control characters removed (`custom._entity_decoded`'s rules). Then only a web page, * an email address or a phone number is a link here; everything else is drawn as words. */ declare function safeLinkHref(raw: string): string | null; /** * The words one value of a newer kind reads as wherever only words fit β€” an export cell, a card, * a sentence. Null: not a newer kind, or nothing particular to say (the caller's own words apply). */ declare function newerKindWords(field: CoercibleField, value: unknown): string | null; /** * The words one value of a newer kind is EXPORTED or COPIED as: an address on one line, a length of * time as h:mm, who by name. A rating, a count and a barcode stay what they are, and rich text * stays its Markdown. Null: the value is written as it is stored. */ declare function newerKindExportWords(field: CoercibleField, value: unknown): string | null; /** FLD-3. A constraint attached to a Field. A Rule, never a behavior. */ interface FieldRule { /** * `unique` (FLD-3 / B1): no two live records of the Table hold the same value, compared * case- and outer-space-blind. The store enforces it under an advisory lock * (`custom._unique_rule_holds`); it is also what lets `recordUpsert` find "the" record by a key. * The mirror never predicts it β€” it needs a query. */ kind: "min" | "max" | "length" | "pattern" | "equals_field" | "differs_from_field" | "unique"; /** * The rule's own number or word: the least / the most for `min` / `max`, the * LONGEST for `length`, the pattern for `pattern`, the other column's key for * `equals_field` / `differs_from_field`. A `length` rule that only says how * SHORT a value may be carries `min` and no `value`. */ value?: string | number; /** * STORE-RULE-GAPS (the older grid's `patternHint`). On a `pattern` rule only: * how a person writes it, e.g. `"949-555-0142"`. The store refuses an example * its own pattern would refuse, and a refused value is told "Enter it like * 949-555-0142." (the hint, and `detail.example`). */ example?: string; /** * STORE-RULE-GAPS (the older grid's `minLength`). On a `length` rule only: the * fewest characters. A blank is `required`'s question, never "too short". */ min?: number; /** FLD-10. Empty means "wherever the Field applies". */ applies_to_types?: string[]; } /** * `custom.field` β€” the ONE definitions table (FLD-8), organization-scoped, so * two organizations add different fields to the same standard table. */ /** * WHICH OF THE OTHER RECORD'S COLUMNS A RELATION SHOWS (REL-DISP, 2026-09-21). * * A Table has ONE stored default reference column β€” its `title_field` β€” and until * this existed every relation pointing at that Table could display that column and * nothing else. A Jobs board could read "Maria Chen" but never "Maria Chen β€” * Rincon", and two columns could not be joined into one string at all. * * ABSENT MEANS THE TABLE'S OWN COLUMN. A field with no `display` resolves exactly * as it did before, so nothing changes for a column nobody has given a spec to. * * The store judges it once, in `custom._display_spec_for`: every column named must * exist on the target Table, and the refusal names the one that does not. It is * resolved by ONE body, `custom._words_for`, which `custom.record_words`, * `custom._card_words`, `custom.relation_words` and `custom.relation_words_many` * are all one-liners onto β€” so the thirteen store-side readers inherit it too. * * A display column that is itself a relation resolves ONE hop and never a second. */ interface RelationDisplay { /** The target's columns, in the order they are shown. */ columns: string[]; /** What goes between them. Defaults to a single space. */ separator: string; } interface Field { id: Uuid; organization_id: Uuid; /** Set when this Field extends a standard entity rather than a custom Table. */ entity_definition_id: Uuid | null; /** The token of the Table this Field belongs to. */ table_token: string | null; key: string; label: string; name: string | null; /** * When this Field was taken away (retired / archived), or null while it is live. Filled by the reads that * can answer a retired Field β€” `entityFields({ include_retired: true })`; absent from reads that never do. */ archived_at?: string | null; /** FLD-1. Exactly one of five. */ type: FieldBehavior; /** FLD-2. The shape of the value, never a second behavior. */ multi: boolean; /** FLD-2. The values carry their own dates β€” the world clock, not History. */ dated: boolean; /** FLD-3. Any number of attached validation Rules. */ rules: FieldRule[]; config: Record; required: boolean; default: unknown; sort: number | null; /** The Table this relation points at. */ relation_target: Uuid | null; relation_max: number | null; on_target_delete: OnDelete | null; /** REL-9. The reverse is visible from both ends and is NOT a second Field. */ inverse_key: string | null; /** REL-DISP. Which of the target's columns this one shows. Null = the Table's own. */ display: RelationDisplay | null; source: FieldSource; source_config: Record; /** FLD-9. A Formula declares whether it computes on read or on write. */ compute_on: ComputeOn | null; /** The unit, on the Field, because it changes the meaning. Never presentation. */ unit: string | null; format: string | null; /** AGT-7. A ceiling a merge field's delivery can never widen. */ sensitivity: FieldSensitivity | null; /** FLD-12. Whether an agent may see this Field's values: include Β· summarize Β· exclude Β· on_request. */ context_policy: ContextPolicy | null; /** AGT-6. How often a `source = agent` Field is refilled. */ review_interval_days: number | null; /** * Β§D says this is on the wrong object and moves to the merge field. It is * still carried here because the live column is, and the client reads what * the store holds β€” never what the plan wishes it held. */ depends_on: Uuid[] | null; /** FLD-10. Which record types this Field applies to. Empty means all of them. */ applies_to_types: string[]; /** FLD-5 / FLD-6. A list field's choices are the records of THIS Table. */ options_table_id: Uuid | null; created_by: Uuid | null; updated_by: Uuid | null; created_at: Timestamp; updated_at: Timestamp; version: number; metadata: Record; visibility: string; } /** * FLD-11. What the person picked, and what the store says it is made of. The * list itself is generated from `custom.parity_field_types()`; this is its type. */ interface ParityFieldTypeDescriptor { parity_type: ParityFieldType; behavior: FieldBehavior; made_of: string; } /** AGT-8 / AGT-4. What an agent (or a person) proposes when a Field is missing. */ interface FieldProposal { table_id: Uuid; key: string; label: string; type: FieldBehavior; multi?: boolean; required?: boolean; config?: Record; relation_target?: Uuid; /** Why this Field should exist β€” what an approver reads before approving. */ because: string; } /** AGT-8. What a table proposal looks like, same shape of question. */ interface TableProposal { name: string; slug: string; title_field: string; fields: Array>; because: string; } /** * WHAT A PERSON SAYS WHEN THEY ADD A COLUMN β€” the argument of * `custom.field_declare`, and deliberately NOT a `Field`. * * A `Field` is what the store STORES: a behavior from a closed set of five, * modifiers beside it, a format, a unit, a relation target, a relation maximum, * an on-delete word, a compute_on, and the attached Rules that make a format * enforceable. A person adding a column knows none of that. They know they want * a date, or a person, or a list with these three choices in it. * * Every client that tried to bridge that gap itself got it wrong β€” the field * panel picked "Person" and the store read a Formula β€” so the bridge is the * STORE's, once, and this is the shape it takes. */ interface NewFieldDeclaration { /** What a person reads on the column. The only thing that is always required. */ label: string; /** One of the thirteen. Absent means one of the three plain behaviors below. */ parity_type?: ParityFieldType; /** Plain text, a long text or a plain number β€” the three that carry no parity type. */ plain?: "text" | "long_text" | "number"; /** * THE ONE WORD EVERY OTHER CALLER REACHES FOR. An agent, a script, an import * and this package's own table-declaring path all say `type`, and until * 2026-09-20 `custom._field_document_for` threw it away β€” a `number` became a * text column in silence. It now resolves all three vocabularies, so this is * named here rather than left as a shape only the store knows about. */ type?: FieldTypeWord; /** Derived from the label when absent, which is why the panel never asks for it. */ key?: string; /** T8 / CD-CORE-15. The record types this column applies to (its table's type field's choices). Absent: every record. */ applies_to_types?: string[]; required?: boolean; multi?: boolean; dated?: boolean; sort?: number; sensitivity?: FieldSensitivity; context_policy?: ContextPolicy; rules?: FieldRule[]; /** DEFECTS 7: `true` is the plain way to say `rules: [{ kind: "unique" }]`; the store adds the rule once. */ unique?: boolean; /** A choice list's choices, as words. The store keeps them as a Table (FLD-5 / FLD-6). */ options?: string[]; /** …or the Table they already come from, when a person picked an existing list. */ options_table_id?: Uuid; /** The currency symbol or code a currency field's numbers are in (FLD-N-1). */ unit?: string; /** A date field: a day, or a day and a time. */ kind?: "date" | "datetime"; /** A lookup or a rollup: the key of the relation Field on THIS table it travels along. */ via?: string; /** A lookup: the key of the value it reads on the far side. */ pick?: string; /** A rollup: what it works out, and of which far-side field (`count` needs none). */ agg?: "sum" | "count" | "min" | "max" | "avg" | "countall" | "counta" | "and" | "or" | "xor" | "arrayjoin" | "concatenate" | "arrayunique" | "arraycompact"; of?: string; /** * A rollup: WHICH linked records it adds up (CHAIR-MATH b, 2026-10-03). The same shape a saved * view's `filters` carries, written against the far table β€” a flat map of far-side column keys * (`{ status: "open" }`, choice columns by their words, windows as a view writes them) or a Rule * expression over far-side field IDs (`{ op: "eq", args: [{ field: "" }, { const: false }] }`). * The store validates it against the far table on declare and update; absent or `null` means * every linked record. Kept in `config.filter`. */ filter?: Record | null; /** A formula: the expression, in the same shape a Rule uses (REC-15). */ expr?: Record; /** * A formula IN THE LANGUAGE A PERSON TYPES β€” `{Visit fee} - {Deposit taken}` (GRID-PRIMITIVES * G3). The store parses it (`custom.formula_parse`), keeps the text in `config.formula_text` * and the expression it parses to in `config.expr`; send this OR `expr`, never both. */ formula_text?: string; /** * LANE SC-R β€” an `entity_reference` column's kinds: the platform entity tokens it may point at * (`note`, `site`, `agent` …), kept by the store in `config.allowed_types`. */ allowed_types?: string[]; compute_on?: ComputeOn; relation_max?: number; /** * REL / FLD-11. The Table this column points at, when it is neither a Person * (`member`) nor a File (`attachment`). The store has taken it since * 2026-09-20 and no client type named it, so a person with an Invoice table * and a Line table could not declare the link through this package at all. */ relation_target?: Uuid; /** REL-2. What happens to this record when the thing it points at goes. */ on_target_delete?: OnDelete; /** * REL-DISP. Which of the target's columns this one shows, and what joins them. * A bare column name and a plain list are both accepted by the door and * normalized; leaving it out keeps the target Table's own reference column. */ display?: RelationDisplay | string[] | string; /** * Presentation and behavior settings the store carries verbatim β€” a long * text's `multiline`, a list's existing options table. The door has always * read it (`p_spec -> 'config'`) and this interface never offered it, so a * package-owned table could not declare its own multi-line column. */ config?: Record; /** A text column that holds a signature (`custom.doc_sign` accepts no other). */ format?: string; /** * STORE-RULE-GAPS (the older grid's `allowOther`). A choice list only: a value * that is none of the choices is ADDED to the list as typed instead of * refused. Stored as `config.allow_other`; absent means off. */ allow_other?: boolean; /** LANE 10 P4 β€” a rating's top star, 1 to 10 (5 when absent); the store keeps it as a max Rule. */ max?: number; /** LANE 10 P4 β€” a status's groups: choice words β†’ `todo`, `in_progress` or `done`. */ status_groups?: Record; /** LANE 10 P4 β€” a barcode's symbology, one of `BARCODE_SYMBOLOGIES`; absent takes any code. */ symbology?: string; } /** * Every word `custom._field_document_for` resolves for `type`: any parity type * in `PARITY_FIELD_TYPES`, any behavior those types are made of, or one of the * plain synonyms a person or an agent actually writes. No count is written down * here on purpose β€” the parity floor is regenerated from * `custom.parity_field_types()` and grew from thirteen to fourteen the day * `checkbox` landed, which is how the store's own refusals came to name a list * that no longer matched it. */ type FieldTypeWord = DeclarableFieldKind | FieldBehavior | "number" | "integer" | "decimal" | "float" | "long_text" | "string" | "autonumber" | "created_time" | "modified_time" | "entity_reference" | NewerFieldKind; /** * What `custom.field_update` accepts. * * THE SETTINGS **AND** THE BEHAVIOR. The door has had a behavior arm since * T12 β€” say `parity_type`, `plain` or `type` and every value of the column is * converted (or kept in `_retired` with its reason) by * `custom._field_type_converts_values`. This interface named only the settings, * so the one client that needed to move a field back onto the behavior it is * written against had no door and reached for `custom.record_update` instead, * which writes the document under the guards rather than through them. */ /** One existing choice of a list, re-worded in place (`FieldPatch.options`). */ interface FieldOptionEdit { /** The option record's id, as `fieldOptions` answers it. */ id: string; /** What the choice reads as from now on. */ words: string; } interface FieldPatch { label?: string; /** T8 / CD-CORE-15. The record types this column applies to; `[]` makes it apply to every record. */ applies_to_types?: string[]; required?: boolean; dated?: boolean; sort?: number; sensitivity?: FieldSensitivity; context_policy?: ContextPolicy; unit?: string; rules?: FieldRule[]; /** * The choices of a choice list, rewritten, IN ORDER (lane CHOICE-COLUMN-EDIT, 2026-09-27). * * Each entry is either `{ id, words }` β€” `id` is the option record's id as `fieldOptions` * answers it β€” which RE-WORDS that choice in place (its key never changes, so every cell * holding it now reads the new words), or plain words: the live choice with the same words * (any case), else a retired one brought back, else a new choice. Every choice takes the * position it has in this array. A live choice the array does not name is RETIRED (never * deleted): cells holding it keep it and read as its words. All in one transaction. */ options?: Array; /** * Words ADDED to a choice list's choices without touching the others: a live same-words * choice adds nothing, a retired one is brought back, the rest are appended. Refused by name * beside `type` / `parity_type` / `plain`. */ options_add?: string[]; /** One of the thirteen β€” a change of what this column HOLDS, not of a setting. */ parity_type?: ParityFieldType; /** …or one of the three plain behaviors, …*/ plain?: "text" | "long_text" | "number"; /** …or the one word every other caller reaches for (see `NewFieldDeclaration`). */ type?: FieldTypeWord; /** * FLD-2. The shape of the value β€” one, or several. * * This comment used to say that a patch moving `multi` ALONE changed nothing * and that the behavior word had to be sent with it. That was true, and it * was a description of a silent failure rather than a contract: the door * answered "saved" and the column still held one. `custom.field_update` has * had a real settings arm for it since 2026-09-20 (matrx-frontend * `migrations/campaign/fix7b_field_multi_arm.sql`), which also moves a * relation's `relation_max` beside it β€” `custom.validate_values` counts links * against that number, so moving `multi` without it would tick a box and * still refuse the second record. * * A CHOICE LIST IS THE ONE EXCEPTION, and it is refused by name rather than * ignored: for `select` / `multi_select`, "one answer or several" IS the * parity type, so send the type instead. */ multi?: boolean; /** REL / FLD-11: re-point the column. The old edges are withdrawn by the door. */ relation_target?: Uuid; on_target_delete?: OnDelete; /** * REL-DISP. Which of the target's columns this one shows. `null` REMOVES the * spec and the column goes back to whatever the table it points at is titled by; * the door refuses it by name on a column that points at nothing. */ display?: RelationDisplay | string[] | string | null; /** SEAT-SUITES: whether the store builds an index for this column, and a unique one. */ promoted?: boolean; unique?: boolean; /** * STORE-RULE-GAPS. A choice list's "other values allowed" setting β€” see * `NewFieldDeclaration.allow_other`. Refused by name on any other column; a * retype that stays a list keeps it. */ allow_other?: boolean; /** LANE 10 P4 β€” a status's groups, changed in place: choice words or keys β†’ `todo` | `in_progress` | `done`. */ status_groups?: Record; /** LANE 10 P4 β€” a barcode's symbology; null takes any code again. */ symbology?: string | null; /** LANE 10 P4 round 3 β€” a rating's new top star, 1 to 10; scores above it are set aside in `_retired`. */ max?: number; /** NOTION-PROPS-3 β€” a button column's settings, whole (`config.button`); refused on any other column. */ button?: ButtonSettings; /** NOTION-PROPS-3 β€” a link to its own table says one record waits on another (a timeline draws it). */ dependency?: boolean; /** W2-A 4 β€” a Last changed time column watches only these field keys (`config.only_fields`); null = the whole record. */ only_fields?: string[] | null; /** NOTION-PROPS-3 β€” a one-record link to its own table names each record's parent: the table nests sub-items. */ sub_items?: boolean; /** CLOSE-LINK (CD-LINK-03) β€” a link's picker offers only this saved view's records (`config.limit_to_view`); null takes it off. */ limit_to_view?: string | null; /** CLOSE-LINK (CD-LINK-05) β€” a link follows the linked record (`live`, the default) or keeps a copy of it from when it was linked (`snapshot`, `config.binding`); turning snapshot on takes a copy for every link already there. */ binding?: "live" | "snapshot"; /** CLOSE-LINK (CD-LINK-08) β€” a link carries access: whoever can see a linked record can see this one (`config.carries`), at most at `carries_max`. */ carries?: boolean; /** CLOSE-LINK (CD-LINK-08) β€” the most a carrying link conveys: `viewer` (the default) or `editor`. */ carries_max?: "viewer" | "editor"; /** CLOSE-LINK (CD-LINK-09) β€” two records may point at each other along this link (`config.loops`). */ loops?: boolean; /** CLOSE-LINK (CD-LINK-04) β€” the other tables a link also points at (`config.target_tables`, its own table always first); null or [] = only its own table. */ target_tables?: readonly string[] | null; /** Β§D / T7: which fields a worked-out column reads. */ depends_on?: Uuid[]; /** * A rollup: WHICH linked records it adds up β€” the same shape as `NewFieldDeclaration.filter` * (a saved view's filter written against the far table). `null` removes it, and the rollup adds * up every linked record again. `custom.field_update` validates it against the far table. */ filter?: Record | null; /** * FLD-9. WHEN a worked-out column works its answer out: when somebody reads the record, * or when somebody saves it. * * This door accepted the word, answered success and threw it away until 2026-09-21 * (matrx-frontend `migrations/campaign/tails2_a_formula_says_when_it_works_itself_out.sql`) * β€” its spec builder used a fixed key list that did not carry it, so a person declaring a * write-stamped formula through this door silently got a read-time one. It is applied now, * in both arms, and the records already saved are brought with it. * * TWO THINGS IT REFUSES BY NAME rather than forcing quietly: a column that is not worked * out by the store has no moment to be worked out at, and a ROLL-UP adds up OTHER records, * so an answer stamped when this record was last saved would be wrong the moment one of * them changed β€” a roll-up is always `read`. */ compute_on?: ComputeOn; /** * FLD-11 / REC-15. RE-AIMING A WORKED-OUT COLUMN: the expression, in the same * shape a Rule uses and read by the same evaluator β€” `{"op":"concat","args": * [{"field":"note"}]}`. Every `{"field": …}` in it names a live Field of this * organization, or `custom._field_document_for` refuses the whole patch by * name and writes nothing. * * `custom.field_update` carries this in its BEHAVIOR arm β€” the arm a type * word opens β€” so send it WITH the word for what the column is * (`type: "formula"`, or `parity_type` / `plain`). The door's spec builder * reads `coalesce(p_patch -> 'expr', v_old -> 'config' -> 'expr')`, so a * re-type that does not carry one deliberately keeps the expression the * column already had; the settings arm has no `expr` key at all, so `expr` * alone is not a change this door makes. `STORE-TAILS-2` asked for the field * because the one client that needed to re-aim an existing formula had no * typed way to say it and reached for `custom.record_update`, which writes the * Field document under the guards rather than through them. */ expr?: Record; /** * RE-AIMING A FORMULA IN THE LANGUAGE A PERSON TYPES (GRID-PRIMITIVES G3). Same arm as * `expr` β€” send it with `type: "formula"`; the store parses it and keeps both the text and * the expression. Measured on the dev clone 2026-09-23: `{"type":"formula","formula_text": * "{Visit fee} * 2"}` re-aims the column and keeps its other settings. */ formula_text?: string; } /** * Whether a choice column takes values that are not one of its choices * (`config.allow_other`, STORE-RULE-GAPS). Off unless the column says so. */ declare function allowsOtherValues(field: Pick): boolean; /** * What `custom.field_duplicate` answers (DUPLICATE PROPERTY, Notion N5): the copy's id, key and name, and * how many records had a value copied into it. `values` is `not_a_value` for a link, formula, lookup or * roll-up column, whose copy holds no document values of its own. */ interface FieldDuplicated { field_id: string; key: string; label: string; values_copied: number; values: "copied" | "not_a_value"; } export { isButtonField as $, ADDRESS_PARTS as A, BARCODE_SYMBOLOGIES as B, STATUS_GROUP_WORDS as D, SYSTEM_WRITER_WORDS as E, addressFromText as H, addressMapUrl as I, addressValueText as J, allowsOtherValues as L, buttonNeeds as M, NEWER_FIELD_KINDS as N, buttonOf as O, buttonUrlPlaceholders as Q, STATUS_GROUPS as S, coerceTypedAnswer as U, coerceTypedAnswers as V, coercionLabel as W, durationText as X, entityReferenceKindsOf as Y, fieldKindFor as Z, isAddressValue as _, isEntityReferenceConfig as a0, isPersonStamp as a1, newerKindExportWords as a2, newerKindOf as a3, newerKindWords as a4, personStampText as a5, ratingMaxOf as a6, readTypedDuration as a7, relationNameIndexOf as a8, relationNameKey as a9, relationNamesIn as aa, richTextPlain as ab, safeLinkHref as ac, splitListCell as ad, statusGroupOf as ae, AGENT_WRITER_WORDS as c, BUTTON_DOES as f, FIELD_KINDS as n, NOT_A_MEMBER_WORDS as t }; export type { ChoiceOptions as C, Field as F, StatusGroup as G, KernelTargets as K, ParityFieldTypeDescriptor as P, RelationDisplay as R, TableProposal as T, FieldTypeWord as a, FieldRule as b, AddressPart as d, AddressValue as e, ButtonDoes as g, ButtonPressed as h, ButtonSettings as i, CoerceOptions as j, Coerced as k, CoercedAnswers as l, CoercibleField as m, FieldDuplicated as o, FieldKind as p, FieldOptionEdit as q, FieldPatch as r, FieldProposal as s, NewFieldDeclaration as u, NewerFieldKind as v, PersonStamp as w, RelationNameIndex as x, RelationNameMatch as y, RelationNamesAnswer as z };