{"version":3,"sources":["../../src/forms/index.ts","../../src/portal.ts","../../src/forms/languages.ts","../../src/forms/forms.ts","../../src/forms/publicBinding.ts","../../src/button.ts","../../src/newerKinds.ts","../../src/typedDate.ts","../../src/typedNumber.ts","../../src/coerce.ts","../../src/forms/typedAnswers.ts"],"sourcesContent":["// @ai-matrx/records/forms — forms, portals and public addresses without any screen: the form document\n// (flows, looks, endings, jumps, embeds), the store's form doors' arguments, and which read a public\n// address does. React-free; `@ai-matrx/records-ui` draws them.\nexport * from \"./forms\";\nexport * from \"./publicBinding\";\nexport * from \"./typedAnswers\";\nexport * from \"./languages\";\n","// src/portal.ts — @ai-matrx/records\n//\n// THE CLIENT PORTAL, FROM THE OWNER'S SIDE.\n//\n// A portal is the one way somebody with no membership of an organization is\n// let in to see the records that name them — their jobs, their invoices —\n// and nothing else. The store holds it in three tables (`custom.portal`,\n// `custom.portal_table`, `custom.portal_principal`) and answers about it\n// through six doors. These are the shapes those doors answer with, written\n// from the doors' own bodies and never from a screen's convenience.\n//\n// TWO FACTS THIS FILE EXISTS TO KEEP STRAIGHT, because a screen that gets\n// either of them wrong tells somebody a comforting lie:\n//\n//   · AN INVITATION CONFERS NOTHING. `custom.portal_invite` writes a row and\n//     says so in its own `say` sentence: the person gets access the moment\n//     they follow the sign-in link and the platform gives them an identity,\n//     and until then the row holds nothing at all. That is why\n//     `PortalPrincipal` carries `signed_in` separately from `is_active` —\n//     \"invited\" and \"can see their records\" are two different states and one\n//     number for both is how an owner comes to believe a client is looking at\n//     something they cannot reach.\n//\n//   · \"VIEW AS THIS CLIENT\" IS NOT A PORTAL-SHAPED QUERY.\n//     `custom.portal_preview` asks `custom.visible_set` for HER user id — the\n//     exact call `custom.read_records` makes when she opens the page — so the\n//     preview cannot disagree with what she sees. When she has not signed in\n//     yet it REFUSES with `02000` and a sentence, rather than answering an\n//     empty list that would read as \"she sees nothing\".\n\nimport type { Timestamp, Uuid } from \"./record\";\n\n/** One portal as `custom.portals` reports it to its organization. */\nexport interface PortalSummary {\n  portal_id: Uuid;\n  title: string;\n  slug: string;\n  /** The Table whose records ARE the clients. A principal is one of its rows. */\n  client_table_id: Uuid;\n  /** That Table's name, so a list never has to read a second door to draw a row. */\n  client_table: string;\n  is_active: boolean;\n  /** How many Tables this portal exposes. */\n  tables: number;\n  /** Everybody invited and not revoked — NOT everybody who can see anything. */\n  invited: number;\n  /** Of those, how many have followed the link and hold an identity. */\n  signed_in: number;\n  /** `magic_link` today, and the door refuses every other word by name. */\n  sign_in_method: string;\n  opened_at: Timestamp | null;\n}\n\n/**\n * ONE (portal, Table) PAIR, as `custom.portal_tables` reports it.\n *\n * 🚨 WHY THIS EXISTS (TAILS-3, 2026-09-21). The Portals rail sits on a Table's\n * page beside Forms, Bookings and Dashboards — all table-scoped — while\n * `custom.portals` takes no table at all. On Rincon Plumbing Co's **Parts**\n * table the rail read \"Portals 10\" and listed ten cards titled \"Your jobs and\n * invoices\", which belong to **Jobs**; the empty state never rendered, so the\n * \"Build it / Ask an agent\" offer was unreachable on EVERY table of any\n * organization that already had one portal.\n *\n * This is the whole of what a table-scoped rail needs: is the table I am\n * standing on in any portal, and if not, which portals could take it.\n */\nexport interface PortalTableRef {\n  portal_id: Uuid;\n  table_id: Uuid;\n  /** That Table's name, so a rail never reads a second door to draw a row. */\n  name: string;\n}\n\n/** One Table a portal exposes, as `custom.portal_card` reports it. */\nexport interface PortalTableExposure {\n  table_id: Uuid;\n  /** The Table's own name. */\n  name: string;\n  /**\n   * The key of the relation Field that says WHOSE a record is. This is the\n   * whole of \"only theirs\": the association that Field writes is what carries\n   * the record to the client, so a portal cannot exist without one.\n   */\n  names_via: string;\n  /** The field keys a client sees. Empty is impossible — the door fills it. */\n  visible_fields: string[];\n  /** The subset a client may change. Always a subset of `visible_fields`. */\n  editable_fields: string[];\n  comments: boolean;\n  /**\n   * The most a client is ever conveyed on this Table — `viewer`, `commenter`\n   * or `editor` — worked out by the door from the two lists above, never by a\n   * screen.\n   */\n  conveys: string;\n}\n\n/** One person a portal was opened to, as `custom.portal_card` reports them. */\nexport interface PortalPrincipal {\n  principal_id: Uuid;\n  email: string;\n  /** The record of the client Table this person IS, for the portal's purposes. */\n  client_record_id: Uuid;\n  /** That record's title — what an owner calls this client. */\n  client: string;\n  /** They followed the link and hold an identity. Until then this is false. */\n  signed_in: boolean;\n  /** Not revoked. An invitation that was never followed is still active. */\n  is_active: boolean;\n  invited_at: Timestamp;\n  revoked_at: Timestamp | null;\n  /**\n   * THE LINK THE OFFICE COPIES FOR THIS PERSON — `/invitations/portal/accept/<token>`, the\n   * path `custom.portal_invite` handed back when they were asked, read off the same\n   * `iam.invitations` row and never re-templated.\n   *\n   * 🚨 `null` IS AN ANSWER, NOT AN ABSENCE, and a screen must say which. It is null when they\n   * have already followed the link, when the office took it back (a revoke withdraws the\n   * invitation in the same statement), when it has expired, or when they were let in through\n   * the server's own magic-link route, which binds without an invitation at all. In every one\n   * of those cases there is genuinely no link to copy — so draw a sentence, never a dead\n   * control. (TAILS-5, 2026-09-21; the gap PORTAL-BIND left named.)\n   */\n  accept_path: string | null;\n}\n\n/** Everything about ONE portal: what it shows, and who it was opened to. */\nexport interface PortalCard {\n  portal_id: Uuid;\n  title: string;\n  slug: string;\n  is_active: boolean;\n  sign_in_method: string;\n  client_table_id: Uuid;\n  tables: PortalTableExposure[];\n  principals: PortalPrincipal[];\n  /**\n   * Whether this organization's outside lane is open\n   * (`custom/external_principal_enabled`). Declaring a portal opens it; while\n   * it is false every door refuses an outsider BY NAME, and a screen that drew\n   * the people as reachable anyway would be lying about the one thing that\n   * decides whether they can sign in at all.\n   */\n  external_lane_open: boolean;\n  /** S6 — the look and forms exactly as the owner stored them (`{}` when none). */\n  config: PortalConfig;\n  /** S6 — the look as a client sees it: the portal's own values over the organization's brand. */\n  style: PortalStyle;\n  /** S6 — every form on the portal, in its order, with its title, Table and state. */\n  forms: PortalFormRef[];\n  /** S6 — the accent names a portal may carry (the design system's `STYLE_COLORS`). */\n  accents: PortalAccent[];\n  /**\n   * S6 — the pictures that could be this portal's logo: the organization's own PUBLIC pictures in\n   * Files, newest first. A private file is never offered, because the store would refuse it.\n   */\n  logo_candidates: PublicPicture[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// S6 — A PORTAL CARRIES ITS OWN LOOK AND ITS FORMS (UI-CHAMPIONS-PLAN rows 45, 46, 55)\n//\n// The look and the forms live on the portal's own record (`custom.portal.config`), written only\n// by `custom.portal_declare(… p_config)` and judged there: a logo must be a PUBLIC picture of this\n// organization's Files (a portal's logo is shown on its sign-in page, before anybody has signed\n// in), an accent is one of the design system's color names, and a form is refused unless its\n// answers land in a Table the portal shows. The organization's brand is the default, so a portal\n// with no look of its own is never blank — `from_organization` says which values came from there.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The design system's color names (`STYLE_COLORS`), which is all an accent may be. */\nexport const PORTAL_ACCENTS = [\"slate\", \"green\", \"amber\", \"red\", \"blue\", \"violet\", \"teal\"] as const;\nexport type PortalAccent = (typeof PORTAL_ACCENTS)[number];\n\nexport function isPortalAccent(word: unknown): word is PortalAccent {\n  return typeof word === \"string\" && (PORTAL_ACCENTS as readonly string[]).includes(word);\n}\n\n/** One link in a portal's footer. The store takes only `https://`, `mailto:` and `tel:`. */\nexport interface PortalFooterLink {\n  label: string;\n  url: string;\n}\n\n/** A portal's look as the owner sets it. Every key is optional; absent means \"the organization's\". */\nexport interface PortalStyleInput {\n  display_name?: string | null;\n  welcome?: string | null;\n  logo_file_id?: Uuid | null;\n  accent?: PortalAccent | null;\n  footer_links?: PortalFooterLink[] | null;\n}\n\n/** One form on a portal, as the owner sets it. `order` is the position; `label` overrides the title. */\nexport interface PortalFormInput {\n  form_id: Uuid;\n  label?: string | null;\n  order?: number | null;\n}\n\n/**\n * What `portalDeclare({ config })` sends. A key given REPLACES that key; a key not given is KEPT;\n * `style: null` puts the organization's brand back and `forms: []` takes every form off.\n */\nexport interface PortalConfigInput {\n  style?: PortalStyleInput | null;\n  forms?: PortalFormInput[] | null;\n}\n\n/** The look and forms as the store holds them (normalized by the store). */\nexport interface PortalConfig {\n  style?: PortalStyleInput;\n  forms?: Array<{ form_id: Uuid; label?: string; order: number }>;\n}\n\n/** The look a client is shown — resolved by the store, never computed by a screen. */\nexport interface PortalStyle {\n  display_name: string;\n  welcome: string | null;\n  logo_file_id: Uuid | null;\n  /** The logo's PUBLIC address, or null. Readable by anybody the portal is shown to. */\n  logo_url: string | null;\n  /** Null means the app's own color. */\n  accent: PortalAccent | null;\n  footer_links: PortalFooterLink[];\n  /** MAKE-HOME W5 — a cover picture's PUBLIC address (a form's look carries one), or null. */\n  cover_file_id?: Uuid | null;\n  cover_url?: string | null;\n  /** Which of the values above came from the organization rather than the portal. */\n  from_organization: Array<\"display_name\" | \"logo\">;\n}\n\n/**\n * One of the organization's PUBLIC pictures (`custom._public_pictures`): the only pictures a\n * portal's logo or a form's logo and cover may be, because a stranger sees them before signing in.\n */\nexport interface PublicPicture {\n  file_id: Uuid;\n  name: string;\n  url: string | null;\n}\n\n/**\n * What a form's look editor needs (`custom.form_look_options`, MAKE-HOME W5b): the pictures the\n * store accepts, the colors, and the look a stranger would see for the draft presentation.\n */\nexport interface FormLookOptions {\n  pictures: PublicPicture[];\n  accents: PortalAccent[];\n  look: PortalStyle;\n}\n\n/** One form on a portal, with what the owner needs to see about it. */\nexport interface PortalFormRef {\n  form_id: Uuid;\n  /** What the portal calls it (the owner's label, else the form's title). */\n  label: string;\n  order: number;\n  title: string;\n  table_id: Uuid | null;\n  table: string;\n  /** `gone` is a form that was removed; the outsider's list only ever carries `open`. */\n  state: \"open\" | \"closed\" | \"full\" | \"gone\";\n}\n\n/** A Table's stages as a portal client may be told them (`custom.portal_me` → tables[].stage). */\nexport interface PortalStage {\n  /** The stage Field's key on the record. */\n  field: string;\n  label: string;\n  /** In the order they were declared. A record stores the stage's `key`. */\n  stages: Array<{ key: string; label: string; retired: boolean }>;\n}\n\n/** What `custom.portal_form` answers: one form on the caller's own portal. */\nexport interface PortalFormSpec {\n  form_id: Uuid;\n  portal_id: Uuid;\n  table_id: Uuid;\n  title: string;\n  label: string;\n  presentation: Record<string, unknown>;\n  /** The form's exposed Fields as the store holds them — never the one naming the client. */\n  fields: Array<Record<string, unknown> & { id: Uuid; key: string }>;\n  required: string[];\n  state: \"open\" | \"closed\" | \"full\";\n  message: string | null;\n}\n\n/** What `custom.portal_form_submit` answers. `held` waits for the office; `message` says so. */\nexport interface PortalFormAnswer {\n  submission_id: Uuid | null;\n  record_id: Uuid | null;\n  state: \"accepted\" | \"held\" | \"rejected\" | \"closed\" | \"full\" | \"too_many\";\n  message: string | null;\n}\n\n/** What `custom.portal_invite` answers. `say` is the door's own sentence. */\nexport interface PortalInvitation {\n  invited: boolean;\n  principal_id: Uuid;\n  portal_id: Uuid;\n  slug: string;\n  email: string;\n  client_record_id: Uuid;\n  client: string;\n  /** They already hold an identity. From a screen this is false: the door is passed no user. */\n  bound: boolean;\n  /**\n   * The door's own words, and the ONLY correct summary of what just happened —\n   * it says outright that an invitation confers nothing until the link is\n   * followed. Show it; never paraphrase it.\n   */\n  say: string;\n}\n\n/** What `custom.portal_revoke` answers. `say` names the consequence, past tense. */\nexport interface PortalRevocation {\n  revoked: boolean;\n  principal_id: Uuid;\n  email: string;\n  /** The door's own sentence: they can no longer sign in, and their records stop reaching them. */\n  say: string;\n}\n\n/** One row of \"what this client sees\", from `custom.portal_preview`. */\nexport interface PortalPreviewRow {\n  record_id: Uuid;\n  title: string;\n}\n\n/**\n * THE PUBLIC ADDRESS OF A PORTAL, and the one place its shape is written down.\n *\n * Unlike a form's link, a portal's slug is NOT the capability: the page it\n * serves asks who you are and emails a one-time link, so the slug is a name and\n * nothing more. That is why it is readable and why copying it is safe.\n */\nexport function portalPath(slug: string): string {\n  return `/portal/c/${slug}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// A PORTAL IS ARCHIVED, NEVER DELETED (ORG-CLEANUP, 2026-09-22)\n//\n// The store grew `custom.portal_archive`, `custom.portal_restore` and the\n// archive-aware `custom.list_portals` because Rincon Plumbing Co carried TEN\n// portals all titled \"Your jobs and invoices\" — nine of them the residue of one\n// walk repeated nine times — and there was no door to put any of them away.\n// `is_active = false` is not it: CLOSED is a state an organization chooses about\n// a portal it still has, and its clients are told so by name. ARCHIVED means it\n// is out of the way, and nothing is deleted: every exposed Table and every\n// invited person is handed back exactly as it was.\n//\n// These are the words a screen says about that, written once here so the rail,\n// the guard and any other client say the same thing.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The platform archive filter's own three values, and no fourth. `custom.list_portals`\n * refuses anything else with `22023` rather than quietly treating it as one of them.\n */\nexport const PORTAL_ARCHIVE_FILTERS = [\"active\", \"archived\", \"all\"] as const;\nexport type PortalArchiveFilter = (typeof PORTAL_ARCHIVE_FILTERS)[number];\n\n/** THE LAW'S DEFAULT IS TO HIDE. A list nobody narrowed is the live things. */\nexport const DEFAULT_PORTAL_ARCHIVE_FILTER: PortalArchiveFilter = \"active\";\n\nexport function isPortalArchiveFilter(word: unknown): word is PortalArchiveFilter {\n  return typeof word === \"string\" && (PORTAL_ARCHIVE_FILTERS as readonly string[]).includes(word);\n}\n\n/** What `custom.portal_archive` and `custom.portal_restore` answer with. */\nexport interface PortalArchiveAnswer {\n  /** Where the portal stands NOW — true after an archive, false after a restore. */\n  archived: boolean;\n  /** False when it was already in that state; the sentence says so rather than lying about an act. */\n  changed: boolean;\n  portal_id: Uuid;\n  archived_at?: Timestamp | null;\n  /** The store's own sentence, in plain English. Show it verbatim; never a constraint name. */\n  sentence: string;\n}\n\n/**\n * THE CONSEQUENCE, SAID BEFORE THE CLICK — never a generic \"are you sure?\".\n *\n * What actually changes is two things: nobody can sign in to it any more, and it\n * leaves the organization's portal list. What does NOT change is everything\n * else, and saying so is the difference between an archive somebody dares to\n * press and one they avoid.\n */\nexport function portalArchiveConsequence(title: string, signedIn: number, invited: number): string {\n  const people =\n    invited === 0\n      ? \"Nobody is invited to it\"\n      : signedIn === 0\n        ? `The ${invited} ${invited === 1 ? \"person\" : \"people\"} invited to it will not be able to sign in`\n        : `The ${signedIn} ${signedIn === 1 ? \"person\" : \"people\"} signed in to it, and the ${invited - signedIn} still invited, will not be able to sign in`;\n  return (\n    `“${title}” will leave your portal list. ${people}, and their invitation links will say this ` +\n    `portal was archived and can be restored. Nothing is deleted — every Table it shows and ` +\n    `everyone invited to it is kept exactly as it is, and you can restore it at any time.`\n  );\n}\n\n/**\n * WHY THE TITLE HAS TO BE TYPED BACK, in the store's own reasoning. Ten portals\n * can carry one title, so the title is not what PICKS this one — the portal you\n * opened is. Typing it back is how you say you meant this one.\n */\nexport function portalConfirmLine(title: string): string {\n  return `Type the portal’s title — ${title} — to confirm you mean this one.`;\n}\n\n/** True when what the person typed is what the door will accept. The door decides; this only spares them a round trip. */\nexport function portalTitleTypedBack(typed: string, title: string): boolean {\n  return typed.trim() === (title ?? \"\").trim();\n}\n\n/**\n * WHAT AN EMPTY ARCHIVE LANE SAYS. An empty list is an answer: it says nothing\n * has been archived, so \"none here\" can never be read as \"the archive is broken\"\n * or as \"this organization has no portals at all\".\n */\nexport function emptyPortalArchiveLine(): string {\n  return \"No portal of this organization has been archived. A portal you put away waits here until somebody restores it, and nothing is ever destroyed.\";\n}\n","// src/forms/languages.ts — @ai-matrx/records/forms · CD-PAGE-08: ONE FORM, SEVERAL LANGUAGES.\n//\n// Typeform's multi-language forms: the owner writes the form once and adds a translation of its\n// words for each language; a stranger meets the form in theirs — the link's `?lang=` first, then\n// the languages their browser asks for — and the answers land in the SAME table, under the same\n// keys, whatever language they were asked in. A choice's KEY is the answer; only its label is\n// translated, so a Spanish \"Sí\" and an English \"Yes\" are one value in the grid.\n//\n// Words only. Nothing here decides which questions are asked, what counts as required or where a\n// send goes — the store judges those from the base form, so a translation can never change what a\n// form accepts. `presentation.languages` holds the translations; this file is the ONLY translation\n// between that document and a screen, in both directions.\n\n/** The runner's own words (its buttons and its few sentences), so a translated form is translated all the way. */\nexport interface FormRunnerWords {\n  next: string;\n  back: string;\n  submit: string;\n  sending: string;\n  start: string;\n  thankYou: string;\n  continue: string;\n  /** \"press\" before the Enter key's name. */\n  press: string;\n  /** \"{n} of {total}\" — the progress count. */\n  of: string;\n  required: string;\n  invalidEmail: string;\n  invalidPhone: string;\n  invalidLink: string;\n  takingYouThere: string;\n  /** The answer-again link's words. */\n  changeAnswers: string;\n  saveChanges: string;\n  /** Said under the ending once her answers landed. */\n  received: string;\n  /** The answer-again and finish-later links' button, before and after the copy. */\n  copyMyLink: string;\n  linkCopied: string;\n  /** When the browser will not copy: the link follows these words to copy by hand. */\n  copyByHand: string;\n  /** The finish-later lane: saving, saved (then \"to finish on another device…\"), picked up, start over. */\n  saving: string;\n  savedFinishElsewhere: string;\n  pickedUp: string;\n  startOver: string;\n  /** Under a payment notice: the answers were kept whatever the payment did. */\n  answersKept: string;\n}\n\nexport const FORM_RUNNER_WORDS_EN: FormRunnerWords = {\n  next: \"Next\",\n  back: \"Back\",\n  submit: \"Submit\",\n  sending: \"Sending…\",\n  start: \"Start\",\n  thankYou: \"Thank you\",\n  continue: \"Continue\",\n  press: \"press\",\n  of: \"of\",\n  required: \"This field is required\",\n  invalidEmail: \"Enter a valid email\",\n  invalidPhone: \"Enter a valid phone number\",\n  invalidLink: \"Enter a valid link\",\n  takingYouThere: \"Taking you there…\",\n  changeAnswers: \"Change your answers\",\n  saveChanges: \"Save changes\",\n  received: \"Answers received\",\n  copyMyLink: \"Copy my link\",\n  linkCopied: \"Link copied\",\n  copyByHand: \"This browser would not copy for you, so here is your link to copy by hand:\",\n  saving: \"Saving your answers…\",\n  savedFinishElsewhere: \"Your answers are saved. To finish on another device, use your own link.\",\n  pickedUp: \"Picked up where you left off.\",\n  startOver: \"Start over\",\n  answersKept: \"Your answers were saved.\",\n};\n\n/**\n * THE RUNNER'S WORDS IN THE LANGUAGES A FORM IS MOST OFTEN TRANSLATED INTO. A language not listed\n * here keeps English for the runner's own buttons until the owner writes them (`FormLanguage.words`),\n * and the builder says which ones it has.\n */\nexport const FORM_RUNNER_WORDS: Readonly<Record<string, FormRunnerWords>> = {\n  en: FORM_RUNNER_WORDS_EN,\n  es: {\n    next: \"Siguiente\", back: \"Atrás\", submit: \"Enviar\", sending: \"Enviando…\", start: \"Empezar\", thankYou: \"Gracias\",\n    continue: \"Continuar\", press: \"pulsa\", of: \"de\", required: \"Esta pregunta es obligatoria\",\n    invalidEmail: \"Escribe un correo válido\", invalidPhone: \"Escribe un teléfono válido\", invalidLink: \"Escribe un enlace válido\",\n    takingYouThere: \"Te llevamos allí…\", changeAnswers: \"Cambiar tus respuestas\", saveChanges: \"Guardar cambios\",\n    received: \"Respuestas recibidas\", copyMyLink: \"Copiar mi enlace\", linkCopied: \"Enlace copiado\", copyByHand: \"Este navegador no pudo copiarlo; aquí tienes tu enlace para copiarlo a mano:\", saving: \"Guardando tus respuestas…\", savedFinishElsewhere: \"Tus respuestas están guardadas. Para terminar en otro dispositivo, usa tu propio enlace.\", pickedUp: \"Seguimos donde lo dejaste.\", startOver: \"Empezar de nuevo\", answersKept: \"Tus respuestas se guardaron.\",\n  },\n  fr: {\n    next: \"Suivant\", back: \"Retour\", submit: \"Envoyer\", sending: \"Envoi…\", start: \"Commencer\", thankYou: \"Merci\",\n    continue: \"Continuer\", press: \"appuyez sur\", of: \"sur\", required: \"Cette question est obligatoire\",\n    invalidEmail: \"Saisissez une adresse e-mail valide\", invalidPhone: \"Saisissez un numéro de téléphone valide\", invalidLink: \"Saisissez un lien valide\",\n    takingYouThere: \"Redirection…\", changeAnswers: \"Modifier vos réponses\", saveChanges: \"Enregistrer\",\n    received: \"Réponses reçues\", copyMyLink: \"Copier mon lien\", linkCopied: \"Lien copié\", copyByHand: \"Ce navigateur n'a pas pu copier ; voici votre lien à copier à la main :\", saving: \"Enregistrement de vos réponses…\", savedFinishElsewhere: \"Vos réponses sont enregistrées. Pour terminer sur un autre appareil, utilisez votre propre lien.\", pickedUp: \"Reprise là où vous vous étiez arrêté.\", startOver: \"Recommencer\", answersKept: \"Vos réponses ont été enregistrées.\",\n  },\n  de: {\n    next: \"Weiter\", back: \"Zurück\", submit: \"Absenden\", sending: \"Wird gesendet…\", start: \"Starten\", thankYou: \"Vielen Dank\",\n    continue: \"Weiter\", press: \"drücke\", of: \"von\", required: \"Diese Frage ist erforderlich\",\n    invalidEmail: \"Gib eine gültige E-Mail-Adresse ein\", invalidPhone: \"Gib eine gültige Telefonnummer ein\", invalidLink: \"Gib einen gültigen Link ein\",\n    takingYouThere: \"Du wirst weitergeleitet…\", changeAnswers: \"Antworten ändern\", saveChanges: \"Änderungen speichern\",\n    received: \"Antworten erhalten\", copyMyLink: \"Meinen Link kopieren\", linkCopied: \"Link kopiert\", copyByHand: \"Dieser Browser konnte nicht kopieren, hier ist dein Link zum Kopieren von Hand:\", saving: \"Deine Antworten werden gespeichert…\", savedFinishElsewhere: \"Deine Antworten sind gespeichert. Um auf einem anderen Gerät fertig zu werden, nutze deinen eigenen Link.\", pickedUp: \"Weiter, wo du aufgehört hast.\", startOver: \"Neu beginnen\", answersKept: \"Deine Antworten wurden gespeichert.\",\n  },\n  pt: {\n    next: \"Próximo\", back: \"Voltar\", submit: \"Enviar\", sending: \"Enviando…\", start: \"Começar\", thankYou: \"Obrigado\",\n    continue: \"Continuar\", press: \"pressione\", of: \"de\", required: \"Esta pergunta é obrigatória\",\n    invalidEmail: \"Digite um e-mail válido\", invalidPhone: \"Digite um telefone válido\", invalidLink: \"Digite um link válido\",\n    takingYouThere: \"Levando você até lá…\", changeAnswers: \"Alterar suas respostas\", saveChanges: \"Salvar alterações\",\n    received: \"Respostas recebidas\", copyMyLink: \"Copiar meu link\", linkCopied: \"Link copiado\", copyByHand: \"Este navegador não conseguiu copiar; aqui está seu link para copiar à mão:\", saving: \"Salvando suas respostas…\", savedFinishElsewhere: \"Suas respostas estão salvas. Para terminar em outro dispositivo, use seu próprio link.\", pickedUp: \"Continuando de onde você parou.\", startOver: \"Começar de novo\", answersKept: \"Suas respostas foram salvas.\",\n  },\n  it: {\n    next: \"Avanti\", back: \"Indietro\", submit: \"Invia\", sending: \"Invio…\", start: \"Inizia\", thankYou: \"Grazie\",\n    continue: \"Continua\", press: \"premi\", of: \"di\", required: \"Questa domanda è obbligatoria\",\n    invalidEmail: \"Inserisci un'email valida\", invalidPhone: \"Inserisci un numero di telefono valido\", invalidLink: \"Inserisci un link valido\",\n    takingYouThere: \"Ti stiamo portando lì…\", changeAnswers: \"Modifica le tue risposte\", saveChanges: \"Salva modifiche\",\n    received: \"Risposte ricevute\", copyMyLink: \"Copia il mio link\", linkCopied: \"Link copiato\", copyByHand: \"Questo browser non ha potuto copiare; ecco il tuo link da copiare a mano:\", saving: \"Salvataggio delle risposte…\", savedFinishElsewhere: \"Le tue risposte sono salvate. Per finire su un altro dispositivo, usa il tuo link.\", pickedUp: \"Riprendi da dove avevi lasciato.\", startOver: \"Ricomincia\", answersKept: \"Le tue risposte sono state salvate.\",\n  },\n};\n\n/** The languages the builder offers by name. Any BCP 47 code is accepted; these are the ones it lists. */\nexport const FORM_LANGUAGE_NAMES: Readonly<Record<string, string>> = {\n  en: \"English\", es: \"Español\", fr: \"Français\", de: \"Deutsch\", pt: \"Português\", it: \"Italiano\",\n  nl: \"Nederlands\", pl: \"Polski\", tr: \"Türkçe\", ar: \"العربية\", he: \"עברית\", hi: \"हिन्दी\",\n  ja: \"日本語\", ko: \"한국어\", zh: \"中文\", vi: \"Tiếng Việt\", tl: \"Tagalog\", ru: \"Русский\",\n};\n\n/** One question's words in one language. */\nexport interface FormQuestionWords {\n  ask?: string | null;\n  help?: string | null;\n  /** A choice's label by its KEY — the key stays the answer. */\n  choices?: Record<string, string> | null;\n}\n\n/** ONE TRANSLATION of a form's words. */\nexport interface FormLanguage {\n  /** A BCP 47 language code, lower-case: `es`, `pt-br`. */\n  code: string;\n  /** How the language names itself on the page's switcher. Left out, the builder's name for it. */\n  label?: string | null;\n  title?: string | null;\n  intro?: string | null;\n  submitLabel?: string | null;\n  thankYou?: { title?: string | null; body?: string | null } | null;\n  welcome?: { title?: string | null; body?: string | null; buttonLabel?: string | null } | null;\n  /** By the question's field key. */\n  questions?: Record<string, FormQuestionWords> | null;\n  /** By the ending's id. */\n  endings?: Record<string, { title?: string | null; body?: string | null; buttonLabel?: string | null }> | null;\n  /** The runner's own words, over the built-in ones for this language. */\n  words?: Partial<FormRunnerWords> | null;\n}\n\nconst str = (v: unknown): string | null => (typeof v === \"string\" && v.trim() !== \"\" ? v : null);\nconst obj = (v: unknown): Record<string, unknown> | null =>\n  v && typeof v === \"object\" && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\nconst clean = (d: Record<string, unknown>): Record<string, unknown> =>\n  Object.fromEntries(Object.entries(d).filter(([, v]) => v !== null && v !== undefined && !(typeof v === \"object\" && !Array.isArray(v) && Object.keys(v as object).length === 0)));\n\n/** A language code as the store keeps it: lower-case, `_` read as `-`, or null when it cannot be one. */\nexport function formLanguageCode(raw: unknown): string | null {\n  const code = typeof raw === \"string\" ? raw.trim().toLowerCase().replace(/_/g, \"-\") : \"\";\n  return /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/.test(code) ? code : null;\n}\n\n/** The name a language goes by on the page: its own label, the builder's name for it, or its code. */\nexport function formLanguageName(lang: Pick<FormLanguage, \"code\" | \"label\">): string {\n  return str(lang.label) ?? FORM_LANGUAGE_NAMES[lang.code] ?? FORM_LANGUAGE_NAMES[lang.code.split(\"-\")[0] ?? \"\"] ?? lang.code;\n}\n\n/** `presentation.languages` from the screen's words. A translation with no code is dropped. */\nexport function formLanguagesDocument(languages: ReadonlyArray<FormLanguage> | null | undefined): Array<Record<string, unknown>> {\n  const seen = new Set<string>();\n  const out: Array<Record<string, unknown>> = [];\n  for (const lang of languages ?? []) {\n    const code = formLanguageCode(lang.code);\n    if (!code || seen.has(code)) continue;\n    seen.add(code);\n    const questions: Record<string, unknown> = {};\n    for (const [key, q] of Object.entries(lang.questions ?? {})) {\n      const choices = Object.fromEntries(Object.entries(q?.choices ?? {}).filter(([, v]) => str(v)));\n      const doc = clean({ ask: str(q?.ask), help: str(q?.help), choices: Object.keys(choices).length ? choices : null });\n      if (Object.keys(doc).length) questions[key] = doc;\n    }\n    const endings: Record<string, unknown> = {};\n    for (const [id, e] of Object.entries(lang.endings ?? {})) {\n      const doc = clean({ title: str(e?.title), body: str(e?.body), button_label: str(e?.buttonLabel) });\n      if (Object.keys(doc).length) endings[id] = doc;\n    }\n    const words = Object.fromEntries(Object.entries(lang.words ?? {}).filter(([, v]) => str(v)));\n    out.push(clean({\n      code,\n      label: str(lang.label),\n      title: str(lang.title),\n      intro: str(lang.intro),\n      submit_label: str(lang.submitLabel),\n      thank_you: clean({ title: str(lang.thankYou?.title), body: str(lang.thankYou?.body) }),\n      welcome: clean({ title: str(lang.welcome?.title), body: str(lang.welcome?.body), button_label: str(lang.welcome?.buttonLabel) }),\n      questions,\n      endings,\n      words,\n    }));\n  }\n  return out;\n}\n\n/** `presentation.languages` back into the screen's words. */\nexport function formLanguagesFromDocument(raw: unknown): FormLanguage[] {\n  if (!Array.isArray(raw)) return [];\n  const out: FormLanguage[] = [];\n  for (const entry of raw) {\n    const d = obj(entry);\n    const code = formLanguageCode(d?.[\"code\"]);\n    if (!d || !code) continue;\n    const questions: Record<string, FormQuestionWords> = {};\n    for (const [key, q] of Object.entries(obj(d[\"questions\"]) ?? {})) {\n      const qd = obj(q);\n      if (!qd) continue;\n      const choices = Object.fromEntries(Object.entries(obj(qd[\"choices\"]) ?? {}).filter((e): e is [string, string] => typeof e[1] === \"string\"));\n      questions[key] = { ask: str(qd[\"ask\"]), help: str(qd[\"help\"]), choices: Object.keys(choices).length ? choices : null };\n    }\n    const endings: NonNullable<FormLanguage[\"endings\"]> = {};\n    for (const [id, e] of Object.entries(obj(d[\"endings\"]) ?? {})) {\n      const ed = obj(e);\n      if (ed) endings[id] = { title: str(ed[\"title\"]), body: str(ed[\"body\"]), buttonLabel: str(ed[\"button_label\"]) };\n    }\n    const ty = obj(d[\"thank_you\"]);\n    const w = obj(d[\"welcome\"]);\n    const words = Object.fromEntries(Object.entries(obj(d[\"words\"]) ?? {}).filter((e): e is [string, string] => typeof e[1] === \"string\"));\n    out.push({\n      code,\n      label: str(d[\"label\"]),\n      title: str(d[\"title\"]),\n      intro: str(d[\"intro\"]),\n      submitLabel: str(d[\"submit_label\"]),\n      thankYou: ty ? { title: str(ty[\"title\"]), body: str(ty[\"body\"]) } : null,\n      welcome: w ? { title: str(w[\"title\"]), body: str(w[\"body\"]), buttonLabel: str(w[\"button_label\"]) } : null,\n      questions,\n      endings,\n      words: Object.keys(words).length ? (words as Partial<FormRunnerWords>) : null,\n    });\n  }\n  return out;\n}\n\n/**\n * WHICH TRANSLATION A VISITOR MEETS. The link's `?lang=` first (exactly, then by its first part),\n * then each language the browser asks for in its own order (`Accept-Language`). `null` is the form\n * as written — also the answer when the visitor asks for the base language itself.\n */\nexport function pickFormLanguage(\n  presentation: Record<string, unknown> | null | undefined,\n  requested: string | null | undefined,\n  acceptLanguage?: string | null,\n): string | null {\n  const codes = formLanguagesFromDocument(presentation?.[\"languages\"]).map((l) => l.code);\n  if (codes.length === 0) return null;\n  const base = formLanguageCode(obj(presentation?.[\"language\"])?.[\"code\"]) ?? \"en\";\n  const match = (want: string | null): string | null | undefined => {\n    if (!want) return undefined;\n    if (want === base || want.split(\"-\")[0] === base) return null;\n    return codes.find((c) => c === want) ?? codes.find((c) => c.split(\"-\")[0] === want.split(\"-\")[0]);\n  };\n  const asked = match(formLanguageCode(requested));\n  if (asked !== undefined) return asked;\n  const wanted = (acceptLanguage ?? \"\")\n    .split(\",\")\n    .map((part) => {\n      const [tag, ...params] = part.trim().split(\";\");\n      const q = params.map((p) => p.trim()).find((p) => p.startsWith(\"q=\"));\n      return { code: formLanguageCode(tag), q: q ? Number(q.slice(2)) : 1 };\n    })\n    .filter((x) => x.code && x.q > 0)\n    .sort((a, b) => b.q - a.q);\n  for (const { code } of wanted) {\n    const hit = match(code);\n    if (hit !== undefined) return hit;\n  }\n  return null;\n}\n\n/** The base language's code and name (`presentation.language`), English when the owner never said. */\nexport function formBaseLanguage(presentation: Record<string, unknown> | null | undefined): { code: string; label: string } {\n  const d = obj(presentation?.[\"language\"]);\n  const code = formLanguageCode(d?.[\"code\"]) ?? \"en\";\n  return { code, label: str(d?.[\"label\"]) ?? FORM_LANGUAGE_NAMES[code] ?? code };\n}\n\n/** The runner's words for a language: the built-in ones (English when there are none) under the owner's own. */\nexport function formRunnerWords(code: string | null | undefined, own?: Partial<FormRunnerWords> | null): FormRunnerWords {\n  const c = formLanguageCode(code);\n  const builtIn = (c && (FORM_RUNNER_WORDS[c] ?? FORM_RUNNER_WORDS[c.split(\"-\")[0] ?? \"\"])) || FORM_RUNNER_WORDS_EN;\n  return { ...builtIn, ...Object.fromEntries(Object.entries(own ?? {}).filter(([, v]) => str(v))) };\n}\n\n/** What a public form is, as `custom.form_public` answers it — only the parts translation touches. */\nexport interface TranslatableForm {\n  title: string;\n  presentation: Record<string, unknown>;\n  fields: ReadonlyArray<Record<string, unknown>>;\n}\n\n/**\n * THE FORM IN ONE LANGUAGE: the same document with every word the translation has put over the base\n * form's. A word the translation leaves out keeps the base form's. The keys, the conditions, the\n * jumps, the points and the choices' keys are never touched, so the store judges the same form.\n * `code` null (or a language the form does not have) answers the form unchanged.\n */\nexport function formInLanguage<T extends TranslatableForm>(form: T, code: string | null | undefined): T & { language: string | null; words: FormRunnerWords } {\n  const base = formBaseLanguage(form.presentation);\n  const lang = code ? formLanguagesFromDocument(form.presentation[\"languages\"]).find((l) => l.code === code) : undefined;\n  if (!lang) return { ...form, language: null, words: formRunnerWords(base.code) };\n  const p = { ...form.presentation };\n  if (lang.intro) p[\"intro\"] = lang.intro;\n  if (lang.submitLabel) p[\"submit_label\"] = lang.submitLabel;\n  const ty = obj(p[\"thank_you\"]);\n  if (lang.thankYou?.title || lang.thankYou?.body) {\n    p[\"thank_you\"] = { ...(ty ?? {}), ...(lang.thankYou.title ? { title: lang.thankYou.title } : {}), ...(lang.thankYou.body ? { body: lang.thankYou.body } : {}) };\n  }\n  const w = obj(p[\"welcome\"]);\n  if (w && lang.welcome) {\n    p[\"welcome\"] = {\n      ...w,\n      ...(lang.welcome.title ? { title: lang.welcome.title } : {}),\n      ...(lang.welcome.body ? { body: lang.welcome.body } : {}),\n      ...(lang.welcome.buttonLabel ? { button_label: lang.welcome.buttonLabel } : {}),\n    };\n  }\n  if (Array.isArray(p[\"questions\"])) {\n    p[\"questions\"] = (p[\"questions\"] as unknown[]).map((q) => {\n      const qd = obj(q);\n      const key = String(qd?.[\"field\"] ?? qd?.[\"key\"] ?? \"\");\n      const words = lang.questions?.[key];\n      if (!qd || !words) return q;\n      return { ...qd, ...(words.ask ? { ask: words.ask } : {}), ...(words.help ? { help: words.help } : {}) };\n    });\n  }\n  if (Array.isArray(p[\"endings\"])) {\n    p[\"endings\"] = (p[\"endings\"] as unknown[]).map((e) => {\n      const ed = obj(e);\n      const words = ed ? lang.endings?.[String(ed[\"id\"] ?? \"\")] : undefined;\n      if (!ed || !words) return e;\n      return {\n        ...ed,\n        ...(words.title ? { title: words.title } : {}),\n        ...(words.body ? { body: words.body } : {}),\n        ...(words.buttonLabel ? { button_label: words.buttonLabel } : {}),\n      };\n    });\n  }\n  const fields = form.fields.map((f) => {\n    const words = lang.questions?.[String(f[\"key\"] ?? \"\")];\n    if (!words) return f;\n    const out: Record<string, unknown> = { ...f };\n    // A question asked in the Field's own label (no `ask`) is asked in the translation's words.\n    if (words.ask) out[\"label\"] = words.ask;\n    if (words.choices && Array.isArray(f[\"public_choices\"])) {\n      out[\"public_choices\"] = (f[\"public_choices\"] as unknown[]).map((c) => {\n        const cd = obj(c);\n        const label = cd && typeof cd[\"key\"] === \"string\" ? words.choices?.[cd[\"key\"]] : undefined;\n        return cd && label ? { ...cd, label } : c;\n      });\n    }\n    return out;\n  });\n  return { ...form, title: lang.title ?? form.title, presentation: p, fields, language: lang.code, words: formRunnerWords(lang.code, lang.words) };\n}\n","\"use client\";\n\n// src/forms/forms.ts — @ai-matrx/records/forms · SCR-13, SCR-14 (moved from records-ui, 2026-10-08)\n//\n// A FORM IS A VIEW ON A REAL TABLE.\n//\n// It is not a second data model with its own \"responses\" store that somebody\n// later has to reconcile with the records people actually work on. A form names\n// a Table, each of its questions names one of that Table's own Fields, and a\n// submission is an ORDINARY RECORD in that Table — so the moment it is sent it\n// is in the grid, in the views, in the exports, in History and in the agent's\n// context, with no plumbing in between.\n//\n// THE AI-FIRST LAW. We build a Typeform duplicate not because people will click\n// a form together one question at a time, but because an agent writes the WHOLE\n// thing in one call (`form_propose`) and the person only tweaks it. `FormSpec`\n// below is that complete declarative object: name, questions, conditional logic,\n// theme, thank-you screen. `FormBuilder` takes one as `seed`, exactly as\n// `ViewBar` and `ActionInbox` do.\n//\n// CONDITIONAL LOGIC IS A RULE, NOT AN `if`. `showIf` is a Rule expression the\n// STORE evaluates (`custom.rule_eval`), referencing Fields BY ID. So the same\n// form means the same thing to the runner, to the server and to an agent, and\n// nobody re-implements the predicate in JavaScript where it can drift.\n//\n// ONE FORM OBJECT, AND THIS IS WHERE IT IS WRITTEN DOWN (2026-09-20).\n// --------------------------------------------------------------------------\n// Until this file changed there were TWO. `form_propose` — the sentence a person\n// actually says — wrote `custom.anon_form`: the thing that has a public link, an\n// accept Rule, a notify Rule and a published state. `FormBuilder` wrote form\n// RECORDS into a package-owned `records_ui_form` Table that had none of those,\n// so the form a person tweaked and the form a stranger answered were different\n// objects with the same name. Nothing reconciled them and nothing ever could.\n//\n// There is now one: `custom.anon_form`, reached through `custom.form_declare`\n// and `custom.forms`, which is exactly what `form_propose` calls. `FormSpec`\n// stays the screen's shape — it is what an agent writes and what `FormRunner`\n// renders — and the two functions below are the ONLY translation between it and\n// the store, in both directions. A second mapper anywhere is the drift coming\n// back.\n\nimport { isPortalAccent, type PortalAccent } from \"../portal\";\nimport { FORM_RUNNER_WORDS_EN, formLanguagesDocument, formLanguagesFromDocument, type FormLanguage, type FormRunnerWords } from \"./languages\";\nimport type { FormSummary, RuleExpression } from \"../rule\";\nimport type { Uuid } from \"../record\";\n\n/**\n * THE WORDS FOR EACH STATE, SAID ONCE, FOR EVERY SCREEN THAT SHOWS A FORM.\n *\n * `FormsPanel` and `FormBuilder` both put a form in front of a person and both\n * offer the one act that changes this (`custom.anon_publish`). Two copies of\n * these four sentences is two places for them to drift, and a person who reads\n * \"Not published\" on one screen and something else on the other has been lied\n * to by one of them. The store owns the state; this owns the sentence.\n */\nexport const FORM_STATE_WORDS: Record<FormSummary[\"state\"], string> = {\n  draft: \"Not published — the link does not answer yet.\",\n  open: \"Open — anyone with the link can answer it.\",\n  closed: \"Closed — it keeps its answers and takes no more.\",\n  full: \"Full — it has all the answers it was set up to take.\",\n};\n\n/** How a form is put in front of a person. */\nexport const FORM_FLOWS = [\"one-at-a-time\", \"single-page\"] as const;\nexport type FormFlow = (typeof FORM_FLOWS)[number];\n\n/** One question. It ASKS FOR one Field of the subject Table — never its own column. */\nexport interface FormQuestionSpec {\n  /** The Field's key on the subject Table. */\n  field: string;\n  /** Ask it in the form's own words. Left out, the Field's own label is the question. */\n  ask?: string | null;\n  /** One line under the question. */\n  help?: string | null;\n  /** Override the Field's own `required`. Left out, the Field decides. */\n  required?: boolean | null;\n  /**\n   * CONDITIONAL LOGIC, AS A RULE. `{\"op\":\"eq\",\"args\":[{\"field\":\"<field id>\"},{\"const\":\"Yes\"}]}`\n   * — the store answers it, this package never does. An UNDECIDED answer (the\n   * store's `null`, which is what an unanswered question gives) SHOWS the\n   * question: a person is never silently skipped past something.\n   */\n  showIf?: RuleExpression | null;\n  /**\n   * LOGIC JUMPS (lane TYPEFORM-DUP). After this question, the FIRST jump whose `when` the store\n   * answers true is taken — to a later question or to one of the form's endings. A jump with no\n   * `when` is \"always\". Undecided falls through to the next question. The store judges the route\n   * (`custom._form_route`); this package never does.\n   */\n  jumps?: FormJump[] | null;\n  /** SCORING: what each answer is worth (`{\"Yes\": 5}`); a list answer adds each of its parts. */\n  points?: Record<string, number> | null;\n}\n\n/** One logic jump: when the Rule is true, go to a later question or to an ending. */\nexport interface FormJump {\n  when?: RuleExpression | null;\n  to: { question: string } | { ending: string };\n}\n\n/**\n * ONE ENDING (lane TYPEFORM-DUP). A form may have several; a jump picks one, or the first whose\n * condition holds (`when`, `scoreMin`, `scoreMax`), or else the first with no condition.\n */\nexport interface FormEnding {\n  /** A short name, lower-case letters, digits, - and _. Jumps point at it. */\n  id: string;\n  title?: string | null;\n  body?: string | null;\n  /** One of the organization's public pictures (the builder's side). */\n  pictureFileId?: Uuid | null;\n  /** The picture's public address (the store resolves it for the public page). */\n  pictureUrl?: string | null;\n  buttonLabel?: string | null;\n  /** A secure page on the organization's own sites — the thank-you's own rule. */\n  redirectUrl?: string | null;\n  when?: RuleExpression | null;\n  scoreMin?: number | null;\n  scoreMax?: number | null;\n}\n\n/** THE WELCOME SCREEN, before question one: a title, a message, a picture and a Start button. */\nexport interface FormWelcome {\n  title?: string | null;\n  body?: string | null;\n  pictureFileId?: Uuid | null;\n  pictureUrl?: string | null;\n  buttonLabel?: string | null;\n}\n\n/** The look. Tokens, so light and dark both come out right without a second theme. */\nexport interface FormTheme {\n  /** A design-system accent class or a CSS color. Left out, the app's own accent. */\n  accent?: string | null;\n  align?: \"left\" | \"center\" | null;\n  /** One of design-system `FORM_FONTS`. */\n  font?: string | null;\n  /** One of design-system `FORM_BUTTON_STYLES`. */\n  button?: string | null;\n  /** One of design-system `FORM_BACKGROUNDS`. */\n  background?: string | null;\n  /** The picture for the `picture` background: a public picture of the organization. */\n  backgroundFileId?: Uuid | null;\n  /** Its public address, as the store resolved it for the public page. */\n  backgroundUrl?: string | null;\n}\n\n/**\n * THE FORM'S OWN LOOK, in the portal's words (`custom._portal_style` resolves both): a color of\n * the app's own palette and two of the organization's PUBLIC pictures. Each part left out is the\n * organization's look — its newest open portal's, then its own name and logo. `custom.form_declare`\n * refuses a color the app does not have or a picture a stranger could not see, by name.\n */\nexport interface FormLook {\n  accent?: PortalAccent | null;\n  logoFileId?: Uuid | null;\n  coverFileId?: Uuid | null;\n}\n\n/** The screen after sending: the form's words, and optionally where to go next. */\nexport interface FormThankYou {\n  title?: string | null;\n  body?: string | null;\n  /** A secure page on one of the organization's own sites. The store judges it. */\n  redirectUrl?: string | null;\n}\n\n/** THE COMPLETE DECLARATIVE SPEC. One `record_write` and the form exists. */\nexport interface FormSpec {\n  name: string;\n  /** The Table a submission lands in. */\n  subject: Uuid;\n  intro?: string | null;\n  questions: FormQuestionSpec[];\n  flow?: FormFlow | null;\n  theme?: FormTheme | null;\n  /** Color, logo and cover a stranger sees. Left out, the organization's look. */\n  look?: FormLook | null;\n  /**\n   * WHAT THE PERSON SEES AFTER SENDING. `title` and `body` are the form's own\n   * words for the next step (\"You are all set\" / \"Next, book your first visit\").\n   * `redirectUrl` optionally sends them on to a page instead — and only ever to\n   * one of the organization's own sites: `custom.form_declare` refuses anything\n   * else by name, and `custom.form_public` stops handing it out the moment its\n   * site leaves the organization's list (lane S7-PRIME).\n   */\n  thankYou?: FormThankYou | null;\n  submitLabel?: string | null;\n  /** A separate first screen with a Start button (lane TYPEFORM-DUP). */\n  welcome?: FormWelcome | null;\n  /** Several endings, each picked by the answers (lane TYPEFORM-DUP). */\n  endings?: FormEnding[] | null;\n  /**\n   * HIDDEN FIELDS: names read from the link (`?utm_source=…&ref=…`), kept on the submission and\n   * never shown. Not questions — a question with the same key is prefilled instead.\n   */\n  hiddenFields?: string[] | null;\n  /**\n   * Is this form meant to be answerable by someone who is not signed in? The\n   * runner does NOT make that true by itself — it needs the anonymous write\n   * door, and until that door exists it says so BY NAME rather than writing as\n   * whoever happens to be signed in and calling it public.\n   */\n  isPublic?: boolean;\n  /**\n   * THE OWNER'S LOGO AND NAME ABOVE THE FORM (lane TYPEFORM-2). Left out, the organization's knob\n   * `forms/show_owner_header` (default on); false hides the agency's brand on a client's form.\n   */\n  showOwnerHeader?: boolean | null;\n  /** A single choice goes on to the next question when picked. Left out, knob `forms/choice_auto_advance`. */\n  autoAdvance?: boolean | null;\n  /** NOTIFY ON ANSWER, the builder's words (lane TYPEFORM-2): a webhook and the automation that sends it. */\n  notify?: FormNotify | null;\n  /**\n   * CD-PAGE-08 — TRANSLATIONS of the form's words (Typeform's multi-language forms). The page picks one by the\n   * link's `?lang=` or the visitor's browser; the answers land under the same keys whatever language was asked.\n   */\n  languages?: FormLanguage[] | null;\n  /** The language the form is written in. Left out, English. */\n  language?: { code: string; label?: string | null } | null;\n  /**\n   * CD-PAGE-08 — ANSWER AGAIN. On, the person who sent an answer is given a private link that opens their own\n   * answers and changes the record they became (`custom.form_response_update`), while the form is open.\n   */\n  allowEdit?: boolean | null;\n  /**\n   * CD-PAGE-08 — A CALCULATED SCORE ON THE ROW. One of the table's own (number) columns; the score the answers'\n   * points add up to is written there by the store on every send and every change. `{{score}}` recalls it.\n   */\n  scoreField?: string | null;\n}\n\n/** Where an answer is sent besides the owner's own subscription (lane TYPEFORM-2). */\nexport interface FormNotify {\n  webhookUrl?: string | null;\n  /** The `custom.automation_declare` row that posts to `webhookUrl` (trigger form_answered). */\n  webhookAutomationId?: Uuid | null;\n  /**\n   * EMAIL THE RESPONDENT (TYPEFORM-2), off unless set: one email to the address the person typed\n   * in `respondentField` (an email question), worded from the ending reached — sent by\n   * `custom.form_respondent_copy` through the platform's notification sender.\n   */\n  emailRespondent?: boolean | null;\n  respondentField?: string | null;\n}\n\n/**\n * A form as `custom.forms` reports it — the spec a screen renders, plus the\n * things only the STORE knows about it.\n *\n * The four store-side ids are carried on purpose. `custom.form_declare` is a\n * RE-STATEMENT of the whole form, so a save that left `notifyRuleId` out would\n * set it to null and the owner would quietly stop being told when somebody\n * answers — the exact silent failure the notify Rule exists to prevent. They are\n * read here and handed straight back by `formDeclareArgs`.\n */\nexport interface SavedForm extends FormSpec {\n  id: Uuid;\n  /** draft · open · closed · full — the store's own word, never derived on a screen. */\n  state: FormSummary[\"state\"];\n  /** The form's readable name in the store. `form_declare` keeps it across saves. */\n  slug: string;\n  /** Stops taking answers at this many. Null is no ceiling. */\n  submissionCap: number | null;\n  /** The accept Rule: what counts as a complete answer. Null holds every answer for a person. */\n  quarantineRuleId: Uuid | null;\n  /** The subscription Rule that tells somebody an answer arrived. */\n  notifyRuleId: Uuid | null;\n}\n\n/**\n * The spec as `custom.anon_form.presentation` holds it.\n *\n * `questions` is deliberately NOT here: the door writes it itself from the\n * argument it validated (`presentation || {\"questions\": p_questions}`), so the\n * words a form is asked in and the keys it is allowed to accept can never be two\n * different lists.\n */\nexport function formPresentation(spec: FormSpec): Record<string, unknown> {\n  return {\n    intro: spec.intro ?? null,\n    flow: spec.flow ?? \"one-at-a-time\",\n    thank_you: thankYouDocument(spec.thankYou ?? null),\n    submit_label: spec.submitLabel ?? null,\n    look: formLookDocument(spec.look ?? null),\n    welcome: welcomeDocument(spec.welcome ?? null),\n    endings: (spec.endings ?? []).map(endingDocument),\n    hidden_fields: spec.hiddenFields ?? [],\n    theme: themeDocument(spec.theme ?? null),\n    ...(typeof spec.showOwnerHeader === \"boolean\" ? { show_owner_header: spec.showOwnerHeader } : {}),\n    ...(typeof spec.autoAdvance === \"boolean\" ? { auto_advance: spec.autoAdvance } : {}),\n    ...((spec.languages ?? []).length > 0 ? { languages: formLanguagesDocument(spec.languages) } : {}),\n    ...(spec.language && spec.language.code ? { language: strip({ code: spec.language.code, label: text(spec.language.label) }) } : {}),\n    ...(spec.allowEdit ? { allow_edit: true } : {}),\n    ...(text(spec.scoreField) ? { score_field: text(spec.scoreField) } : {}),\n    ...(spec.notify && (spec.notify.webhookUrl || spec.notify.webhookAutomationId || spec.notify.emailRespondent)\n      ? {\n          notify: strip({\n            webhook_url: text(spec.notify.webhookUrl),\n            webhook_automation_id: text(spec.notify.webhookAutomationId),\n            email_respondent: spec.notify.emailRespondent ? true : null,\n            respondent_field: spec.notify.emailRespondent ? text(spec.notify.respondentField) : null,\n          }),\n        }\n      : {}),\n  };\n}\n\n/** `presentation.notify` back into the screen's words. */\nexport function notifyFromDocument(raw: unknown): FormNotify | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const d = raw as Record<string, unknown>;\n  const n: FormNotify = {\n    webhookUrl: text(d[\"webhook_url\"]),\n    webhookAutomationId: text(d[\"webhook_automation_id\"]) as Uuid | null,\n    emailRespondent: d[\"email_respondent\"] === true ? true : null,\n    respondentField: text(d[\"respondent_field\"]),\n  };\n  return n.webhookUrl || n.webhookAutomationId || n.emailRespondent ? n : null;\n}\n\nconst bool = (v: unknown): boolean | null => (typeof v === \"boolean\" ? v : null);\n\n/** `presentation.language` (the language the form is written in) back into the screen's words. */\nfunction languageFromDocument(raw: unknown): { code: string; label: string | null } | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const d = raw as Record<string, unknown>;\n  const code = text(d[\"code\"]);\n  return code ? { code, label: text(d[\"label\"]) } : null;\n}\n\n/**\n * WHAT A QUESTION REFUSES BEFORE IT GOES ON (lane TYPEFORM-2) — Typeform's words, one per case.\n * `null` is \"may go on\". A required question with no answer is refused; an answer of the email,\n * phone or link kind is refused when it cannot be one. The store judges again on send.\n */\nexport function answerRefusal(kind: string, required: boolean, value: unknown, words: Pick<FormRunnerWords, \"required\" | \"invalidEmail\" | \"invalidPhone\" | \"invalidLink\"> = FORM_RUNNER_WORDS_EN): string | null {\n  const empty = value === undefined || value === null || value === \"\" || (Array.isArray(value) && value.length === 0);\n  if (empty) return required ? words.required : null;\n  if (typeof value !== \"string\") return null;\n  const v = value.trim();\n  if (kind === \"email\" && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(v)) return words.invalidEmail;\n  if (kind === \"phone\" && (v.replace(/\\D/g, \"\").length < 7 || /[^\\d\\s()+.\\-x]/i.test(v))) return words.invalidPhone;\n  if (kind === \"url\" && !/^(https?:\\/\\/)?[^\\s.\\/]+(\\.[^\\s.\\/]+)+(\\/\\S*)?$/i.test(v)) return words.invalidLink;\n  return null;\n}\n\n/**\n * ANSWER PIPING — Typeform's recall (lane TYPEFORM-2). `{{key}}` or `{{Question words}}` in a later\n * question, a help line or an ending becomes that question's answer as the person gave it; a\n * choice reads as its label. Unanswered, it becomes nothing.\n */\nexport function pipeAnswers(\n  text: string,\n  answers: Readonly<Record<string, unknown>>,\n  questions: ReadonlyArray<{ key: string; ask?: string | null }>,\n  labelOf?: (key: string, value: unknown) => string | null,\n  /** CD-PAGE-08: values the form works out (`score`), recalled by name when no question has that key. */\n  computed?: Readonly<Record<string, string | number | null | undefined>>,\n): string {\n  if (!text.includes(\"{{\")) return text;\n  return text.replace(/\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g, (_all, name: string) => {\n    const wanted = name.trim().toLowerCase();\n    const q = questions.find((x) => x.key.toLowerCase() === wanted) ?? questions.find((x) => (x.ask ?? \"\").trim().toLowerCase() === wanted);\n    if (!q) {\n      const worked = computed ? Object.entries(computed).find(([k]) => k.toLowerCase() === wanted)?.[1] : undefined;\n      return worked === undefined || worked === null ? \"\" : String(worked);\n    }\n    const value = answers[q.key];\n    if (value === undefined || value === null) return \"\";\n    const one = (v: unknown) => labelOf?.(q.key, v) ?? String(v);\n    return Array.isArray(value) ? value.map(one).join(\", \") : one(value);\n  });\n}\n\nconst text = (v: unknown): string | null => (typeof v === \"string\" && v.trim() !== \"\" ? v.trim() : null);\nconst num = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? v : null);\nconst strip = (doc: Record<string, unknown>): Record<string, unknown> =>\n  Object.fromEntries(Object.entries(doc).filter(([, v]) => v !== null && v !== undefined));\n\n/** `presentation.theme` from the screen's words: camelCase out, the store's snake_case in. */\nexport function themeDocument(theme: FormTheme | null): Record<string, unknown> {\n  if (!theme) return {};\n  return strip({\n    accent: theme.accent ?? null,\n    align: theme.align ?? null,\n    font: text(theme.font),\n    button: text(theme.button),\n    background: text(theme.background),\n    background_file_id: text(theme.backgroundFileId),\n  });\n}\n\nexport function themeFromDocument(raw: unknown): FormTheme | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const d = raw as Record<string, unknown>;\n  const align = d[\"align\"] === \"center\" ? \"center\" : d[\"align\"] === \"left\" ? \"left\" : null;\n  return {\n    accent: text(d[\"accent\"]),\n    align,\n    font: text(d[\"font\"]),\n    button: text(d[\"button\"]),\n    background: text(d[\"background\"]),\n    backgroundFileId: text(d[\"background_file_id\"]) as Uuid | null,\n    backgroundUrl: text(d[\"background_url\"]),\n  };\n}\n\nexport function welcomeDocument(welcome: FormWelcome | null): Record<string, unknown> | null {\n  if (!welcome) return null;\n  const doc = strip({\n    title: text(welcome.title),\n    body: text(welcome.body),\n    picture_file_id: text(welcome.pictureFileId),\n    button_label: text(welcome.buttonLabel),\n  });\n  return Object.keys(doc).length > 0 ? doc : null;\n}\n\nexport function welcomeFromDocument(raw: unknown): FormWelcome | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const d = raw as Record<string, unknown>;\n  const w: FormWelcome = {\n    title: text(d[\"title\"]),\n    body: text(d[\"body\"]),\n    pictureFileId: text(d[\"picture_file_id\"]) as Uuid | null,\n    pictureUrl: text(d[\"picture_url\"]),\n    buttonLabel: text(d[\"button_label\"]),\n  };\n  return w.title || w.body || w.pictureFileId || w.pictureUrl || w.buttonLabel ? w : null;\n}\n\nexport function endingDocument(ending: FormEnding): Record<string, unknown> {\n  return strip({\n    id: ending.id,\n    title: text(ending.title),\n    body: text(ending.body),\n    picture_file_id: text(ending.pictureFileId),\n    button_label: text(ending.buttonLabel),\n    redirect_url: text(ending.redirectUrl),\n    when: ending.when ?? null,\n    score_min: num(ending.scoreMin),\n    score_max: num(ending.scoreMax),\n  });\n}\n\nexport function endingsFromDocument(raw: unknown): FormEnding[] {\n  if (!Array.isArray(raw)) return [];\n  return raw\n    .filter((e): e is Record<string, unknown> => !!e && typeof e === \"object\" && typeof (e as Record<string, unknown>)[\"id\"] === \"string\")\n    .map((d) => ({\n      id: String(d[\"id\"]),\n      title: text(d[\"title\"]),\n      body: text(d[\"body\"]),\n      pictureFileId: text(d[\"picture_file_id\"]) as Uuid | null,\n      pictureUrl: text(d[\"picture_url\"]),\n      buttonLabel: text(d[\"button_label\"]),\n      redirectUrl: text(d[\"redirect_url\"]),\n      when: (d[\"when\"] as RuleExpression | null | undefined) ?? null,\n      scoreMin: num(d[\"score_min\"]),\n      scoreMax: num(d[\"score_max\"]),\n    }));\n}\n\n/** The hidden fields' values out of a link's query, narrowed to the names the form declares. */\nexport function hiddenFromLink(\n  hiddenFields: readonly string[] | null | undefined,\n  params: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n  const out: Record<string, string> = {};\n  for (const key of hiddenFields ?? []) {\n    const raw = params[key];\n    const one = Array.isArray(raw) ? raw[raw.length - 1] : raw;\n    if (typeof one === \"string\" && one.trim() !== \"\") out[key] = one.trim().slice(0, 500);\n  }\n  return out;\n}\n\n/** Does this form route at all — conditions, jumps, endings or points the store must judge? */\nexport function formRoutes(form: Pick<FormSpec, \"questions\" | \"endings\">): boolean {\n  return (\n    (form.questions ?? []).some((q) => q.showIf || (q.jumps && q.jumps.length > 0) || q.points) ||\n    (form.endings ?? []).length > 0\n  );\n}\n\n/** HOW A FORM GOES INTO SOMEBODY ELSE'S PAGE. */\nexport const FORM_EMBEDS = [\"inline\", \"popup\", \"slider\"] as const;\nexport type FormEmbed = (typeof FORM_EMBEDS)[number];\n\n/**\n * THE SNIPPET THE OWNER COPIES (lane TYPEFORM-DUP). `inline` is a plain iframe of the public link\n * in embed mode; `popup` and `slider` are one button plus the platform's own embed script\n * (`<origin>/embed/form.js`), which opens the same iframe in a centered window or a side panel.\n * Nothing in either needs a cookie: the form keeps its place in its own storage and posts to its\n * own origin, so it keeps working with third-party cookies blocked.\n */\nexport function formEmbedSnippet(kind: FormEmbed, formUrl: string, options?: { label?: string | null; height?: number | null }): string {\n  const url = new URL(formUrl);\n  url.searchParams.set(\"embed\", \"1\");\n  const src = url.toString();\n  const label = (options?.label ?? \"\").trim() || \"Open the form\";\n  const esc = (v: string) => v.replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n  if (kind === \"inline\") {\n    const height = options?.height && options.height > 0 ? Math.round(options.height) : 640;\n    return `<iframe src=\"${esc(src)}\" aria-label=\"${esc(label)}\" style=\"width:100%;height:${height}px;border:0\" loading=\"lazy\" allow=\"clipboard-write\"></iframe>`;\n  }\n  const script = `${url.origin}/embed/form.js`;\n  return `<button type=\"button\" data-matrx-form=\"${esc(src)}\" data-matrx-form-mode=\"${kind}\">${esc(label)}</button>\\n<script src=\"${esc(script)}\" async></script>`;\n}\n\n/** `presentation.look` both directions. Nothing set is `null`, one spelling on both sides. */\nexport function formLookDocument(look: FormLook | null): Record<string, unknown> | null {\n  if (!look) return null;\n  const out: Record<string, unknown> = {};\n  if (look.accent) out[\"accent\"] = look.accent;\n  if (look.logoFileId) out[\"logo_file_id\"] = look.logoFileId;\n  if (look.coverFileId) out[\"cover_file_id\"] = look.coverFileId;\n  return Object.keys(out).length > 0 ? out : null;\n}\n\nexport function formLookFromDocument(raw: unknown): FormLook | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const doc = raw as Record<string, unknown>;\n  const id = (v: unknown) => (typeof v === \"string\" && v.trim() !== \"\" ? (v as Uuid) : null);\n  const look: FormLook = {\n    accent: isPortalAccent(doc[\"accent\"]) ? doc[\"accent\"] : null,\n    logoFileId: id(doc[\"logo_file_id\"]),\n    coverFileId: id(doc[\"cover_file_id\"]),\n  };\n  return look.accent || look.logoFileId || look.coverFileId ? look : null;\n}\n\n/**\n * THE THANK-YOU SCREEN, BOTH DIRECTIONS, IN ONE PLACE. The store holds it as\n * `presentation.thank_you = {title, body, redirect_url}`; the screen reads it as\n * `FormThankYou`. A thank-you with nothing in it is `null`, so \"nothing set\"\n * has one spelling on both sides.\n */\nexport function thankYouDocument(thankYou: FormThankYou | null): Record<string, unknown> | null {\n  if (!thankYou) return null;\n  const title = thankYou.title?.trim() || null;\n  const body = thankYou.body?.trim() || null;\n  const redirect = thankYou.redirectUrl?.trim() || null;\n  if (!title && !body && !redirect) return null;\n  return { title, body, ...(redirect ? { redirect_url: redirect } : {}) };\n}\n\n/** `presentation.thank_you` as the store (or `custom.form_public`) answers it, for a screen. */\nexport function thankYouFromDocument(raw: unknown): FormThankYou | null {\n  if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n  const doc = raw as Record<string, unknown>;\n  const text = (v: unknown) => (typeof v === \"string\" && v.trim() !== \"\" ? v : null);\n  const thankYou: FormThankYou = {\n    title: text(doc[\"title\"]),\n    body: text(doc[\"body\"]),\n    redirectUrl: text(doc[\"redirect_url\"] ?? doc[\"redirectUrl\"]),\n  };\n  return thankYou.title || thankYou.body || thankYou.redirectUrl ? thankYou : null;\n}\n\n/** The exact arguments `custom.form_declare` is called with, for a new form or a re-statement. */\nexport function formDeclareArgs(form: FormSpec | SavedForm): {\n  table_id: Uuid;\n  title: string;\n  questions: FormQuestionSpec[];\n  presentation: Record<string, unknown>;\n  submission_cap: number | null;\n  quarantine_rule_id: Uuid | null;\n  notify_rule_id: Uuid | null;\n  form_id: Uuid | null;\n  slug: string | null;\n} {\n  const saved = form as Partial<SavedForm>;\n  return {\n    table_id: form.subject,\n    title: form.name,\n    questions: form.questions ?? [],\n    presentation: formPresentation(form),\n    submission_cap: saved.submissionCap ?? null,\n    quarantine_rule_id: saved.quarantineRuleId ?? null,\n    notify_rule_id: saved.notifyRuleId ?? null,\n    form_id: saved.id ?? null,\n    slug: saved.slug ?? null,\n  };\n}\n\n/** One row of `custom.forms` back into the spec a screen and an agent both read. */\nexport function formFromSummary(summary: FormSummary): SavedForm {\n  const presentation = (summary.presentation ?? {}) as Record<string, unknown>;\n  const flow = String(presentation[\"flow\"] ?? \"one-at-a-time\");\n  return {\n    id: summary.form_id as Uuid,\n    name: summary.title ?? summary.slug,\n    subject: summary.table_id as Uuid,\n    intro: (presentation[\"intro\"] as string | null) ?? null,\n    questions: readQuestions(presentation[\"questions\"]),\n    flow: (FORM_FLOWS as readonly string[]).includes(flow) ? (flow as FormFlow) : \"one-at-a-time\",\n    theme: themeFromDocument(presentation[\"theme\"]),\n    welcome: welcomeFromDocument(presentation[\"welcome\"]),\n    endings: endingsFromDocument(presentation[\"endings\"]),\n    hiddenFields: Array.isArray(presentation[\"hidden_fields\"])\n      ? (presentation[\"hidden_fields\"] as unknown[]).filter((k): k is string => typeof k === \"string\")\n      : [],\n    thankYou: thankYouFromDocument(presentation[\"thank_you\"]),\n    look: formLookFromDocument(presentation[\"look\"]),\n    submitLabel: (presentation[\"submit_label\"] as string | null) ?? null,\n    showOwnerHeader: bool(presentation[\"show_owner_header\"]),\n    autoAdvance: bool(presentation[\"auto_advance\"]),\n    notify: notifyFromDocument(presentation[\"notify\"]),\n    languages: formLanguagesFromDocument(presentation[\"languages\"]),\n    language: languageFromDocument(presentation[\"language\"]),\n    allowEdit: presentation[\"allow_edit\"] === true ? true : null,\n    scoreField: text(presentation[\"score_field\"]),\n    // A form is public when the store says it is OPEN. A screen never decides\n    // this from a flag of its own: the link answers or it does not.\n    isPublic: summary.state === \"open\",\n    state: summary.state,\n    slug: summary.slug,\n    submissionCap: summary.submission_cap,\n    quarantineRuleId: (summary.quarantine_rule_id as Uuid | null) ?? null,\n    notifyRuleId: (summary.notify_rule_id as Uuid | null) ?? null,\n  };\n}\n\n/**\n * The questions out of a stored presentation. `custom.form_declare` takes either\n * `field` or `key` as the address, so both come back out — a form written by an\n * agent through the door reads the same as one saved from the builder.\n */\nfunction readQuestions(raw: unknown): FormQuestionSpec[] {\n  if (!Array.isArray(raw)) return [];\n  const out: FormQuestionSpec[] = [];\n  for (const entry of raw) {\n    if (!entry || typeof entry !== \"object\") continue;\n    const q = entry as Record<string, unknown>;\n    const field = String(q[\"field\"] ?? q[\"key\"] ?? \"\").trim();\n    if (field === \"\") continue;\n    out.push({\n      field,\n      ask: (q[\"ask\"] as string | null) ?? null,\n      help: (q[\"help\"] as string | null) ?? null,\n      required: (q[\"required\"] as boolean | null) ?? null,\n      showIf: (q[\"showIf\"] as RuleExpression | null) ?? (q[\"show_if\"] as RuleExpression | null) ?? null,\n      jumps: Array.isArray(q[\"jumps\"]) ? (q[\"jumps\"] as FormJump[]) : null,\n      points:\n        q[\"points\"] && typeof q[\"points\"] === \"object\" && !Array.isArray(q[\"points\"])\n          ? (q[\"points\"] as Record<string, number>)\n          : null,\n    });\n  }\n  return out;\n}\n\n/**\n * THE SOURCE STAMP every submission carries. A record that arrived through a\n * form is not indistinguishable from one somebody typed into the grid: it says\n * which form, which version of it, when, and who was acting. `_source` is a\n * stamp on the document beside the answers, so it travels with the record\n * through export, History and the agent's context without a parallel table.\n */\nexport function submissionStamp(args: {\n  formId: Uuid | null;\n  formName: string;\n  actor: string;\n  onBehalfOf?: string | null;\n}): Record<string, unknown> {\n  return {\n    _source: {\n      via: \"form\",\n      form_id: args.formId,\n      form_name: args.formName,\n      at: new Date().toISOString(),\n      actor: args.actor,\n      ...(args.onBehalfOf ? { on_behalf_of: args.onBehalfOf } : {}),\n    },\n  };\n}\n","// src/forms/publicBinding.ts — @ai-matrx/records/forms (moved from records-ui, 2026-10-08)\n//\n// WHICH READ A PUBLIC ADDRESS DOES, decided by WHAT IT IS BOUND TO — never by its render mode.\n//\n// `iam.publish_binding.render_mode` is page | embed | feed | raw: HOW it renders, not what it\n// holds. Until v6 lane 11 (2026-10-02) the page tested `render_mode === \"list\"`, a value the store\n// refuses, so every binding fell through to the record read with whatever id it held — a table's\n// id included.\n//\n// Tables, records and dashboards all carry the entity token `record`, so the token cannot say\n// which. `iam.resolve_publish_binding_kind` (the chair's door; anon may call it) can: it names the\n// kind — table | record | dashboard | page | view — and the table a list reads. A table lists its\n// rows; a view lists its table's rows THROUGH THE VIEW'S OWN FILTER (never the whole table — that\n// would publish rows the view leaves out); a record is read; a dashboard or a page reads nothing\n// until the page renderer exists, and the page says so. A signed-out visitor has no read door yet\n// (a later chair wave): the read is refused by the store, and the page shows that refusal.\n\nimport type { Uuid } from \"../record\";\n\nexport interface PublicBindingShape {\n  render_mode: string;\n  resource_id: Uuid;\n  /** What the door says the address holds. */\n  kind: string | null | undefined;\n  /** The table a list reads: the table itself, a view's subject, a record's table. */\n  table_id: Uuid | null | undefined;\n}\n\nexport type PublicRead =\n  | { read: \"table\"; tableId: Uuid }\n  | { read: \"view\"; tableId: Uuid; viewId: Uuid }\n  | { read: \"record\"; recordId: Uuid }\n  | { read: \"unknown\" };\n\nexport function publicReadOf(binding: PublicBindingShape): PublicRead {\n  if (binding.kind === \"table\" && binding.table_id) return { read: \"table\", tableId: binding.table_id };\n  if (binding.kind === \"view\" && binding.table_id) {\n    return { read: \"view\", tableId: binding.table_id, viewId: binding.resource_id };\n  }\n  if (binding.kind === \"record\") return { read: \"record\", recordId: binding.resource_id };\n  return { read: \"unknown\" };\n}\n","// src/button.ts — @ai-matrx/records\n//\n// A BUTTON COLUMN (lanes NOTION-PROPS-2/3; champions: Notion's and Airtable's Button). The store keeps it as\n// behavior text wearing the format `button`; it holds no value, and `config.button` says what a press does\n// through `custom.button_press`:\n//\n//   open_url        answers `url` with every {{field id}} filled from the row; the screen opens it\n//   run_action      one of the table's row actions on this row (which is how a button sets fields)\n//   run_automation  one of the table's automations on this row, for an editor of the row\n//\n// A button with no `do` (what Notion's importer brings — Notion's API hides a button's steps) is a label\n// only, and a press is refused by name. Its settings change through `custom.field_update`'s `button` key.\n\nimport type { CoercibleField } from \"./coerce\";\nimport type { Uuid } from \"./record\";\n\nexport const BUTTON_DOES = [\"open_url\", \"run_action\", \"run_automation\", \"add_row\"] as const;\nexport type ButtonDoes = (typeof BUTTON_DOES)[number];\n\n/** What `config.button` holds. */\nexport interface ButtonSettings {\n  label: string;\n  /** One of the table's color names (blue, green, …); absent, the button wears the neutral look. */\n  color?: string | undefined;\n  /** Absent: nothing is set up yet, and a press says so. */\n  do?: ButtonDoes | undefined;\n  url?: string | undefined;\n  action_id?: Uuid | undefined;\n  automation_id?: Uuid | undefined;\n  /** W2-A `add_row`: the table the new row goes in; absent = this table. */\n  table_id?: Uuid | undefined;\n  /** 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). */\n  prefill?: Record<string, string> | undefined;\n  /** TEMPLATE-GAPS R3 `add_row`: the target table's row template the new row starts from (its values under prefill). */\n  template_id?: Uuid | undefined;\n}\n\n/** What `custom.button_press` answers. */\nexport type ButtonPressed =\n  | { do: \"open_url\"; label: string; url: string }\n  /** W2-A: the row the press added (through custom.record_write), for the screen to open. */\n  | { do: \"add_row\"; label: string; table_id: Uuid; record_id: Uuid; template_id?: Uuid }\n  | { do: \"run_action\"; label: string; result: { ran?: number; records?: Array<{ record_id: Uuid; version: number }> } & Record<string, unknown> }\n  | {\n      do: \"run_automation\";\n      label: string;\n      /** null when no automation this button starts met its conditions (status \"skipped\"). */\n      run_id: Uuid | null;\n      automation: string | null;\n      status: string | null;\n      says: string | null;\n      /** AUTOMATIONS-AIRTABLE: every automation whose trigger is this button, as each ran on the press. */\n      runs?: Array<{ run_id: Uuid; automation: string | null; status: string | null; says: string | null }>;\n    };\n\n/** Whether a Field is a button column — `custom.field_kind_of`'s mirror for this one kind. */\nexport function isButtonField(field: CoercibleField | null | undefined): boolean {\n  return field?.type === \"text\" && String(field.format ?? \"\") === \"button\";\n}\n\n/** A button column's settings, read off its declaration; null for any other column. */\nexport function buttonOf(field: (CoercibleField & { label?: string | null }) | null | undefined): ButtonSettings | null {\n  if (!field || !isButtonField(field)) return null;\n  const raw = ((field.config ?? {}) as Record<string, unknown>)[\"button\"];\n  const b = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n  const text = (v: unknown) => (typeof v === \"string\" && v.trim() !== \"\" ? v.trim() : undefined);\n  const does = text(b[\"do\"]);\n  return {\n    label: text(b[\"label\"]) ?? text(field.label) ?? \"Button\",\n    color: text(b[\"color\"]),\n    do: (BUTTON_DOES as readonly string[]).includes(does ?? \"\") ? (does as ButtonDoes) : undefined,\n    url: text(b[\"url\"]),\n    action_id: text(b[\"action_id\"]) as Uuid | undefined,\n    automation_id: text(b[\"automation_id\"]) as Uuid | undefined,\n    table_id: text(b[\"table_id\"]) as Uuid | undefined,\n    prefill: b[\"prefill\"] && typeof b[\"prefill\"] === \"object\" && !Array.isArray(b[\"prefill\"]) ? (b[\"prefill\"] as Record<string, string>) : undefined,\n    template_id: text(b[\"template_id\"]) as Uuid | undefined,\n  };\n}\n\n/** The `{{field id}}` placeholders a link names, in order — so a settings screen can show them as names. */\nexport function buttonUrlPlaceholders(url: string): string[] {\n  return Array.from(url.matchAll(/\\{\\{([^{}]+)\\}\\}/g), (m) => m[1]!.trim());\n}\n\n/**\n * What a button's settings are missing before a press can work, in words; null when it is set up.\n * The store refuses the same cases (`custom._field_kind_shape_ok`, `custom.button_press`).\n */\nexport function buttonNeeds(b: ButtonSettings): string | null {\n  if (!b.do) return `${b.label} has nothing set up to do yet.`;\n  if (b.do === \"open_url\" && !/^(https?:\\/\\/|mailto:|tel:|\\{\\{)/i.test(b.url ?? \"\")) return `${b.label} needs a link that starts with https://.`;\n  if (b.do === \"run_action\" && !b.action_id) return `${b.label} needs a row action to run.`;\n  if (b.do === \"run_automation\" && !b.automation_id) return `${b.label} needs an automation to run.`;\n  return null;\n}\n","// src/newerKinds.ts — @ai-matrx/records\n//\n// THE NINE NEWER KINDS OF COLUMN (lane 10 VIEWS-AND-FIELDS, sublane P4; champions: Airtable's and\n// SmartSuite's field types). The store keeps each as an EXISTING behavior wearing a format —\n// FLD-1's six behaviors stay six — and `custom.field_kind_of` reads each back. This file is the\n// client's mirror of that one reader, plus the few words each kind needs wherever only words fit\n// (a card, an export cell, a typed answer):\n//\n//   kind         behavior  format     value\n//   rating       range      rating     whole number 0..max (max Rule 1–10, default 5)\n//   duration     range      duration   seconds (unit \"seconds\"), never below zero\n//   created_by   formula    —          {id, name}; config.system created_by, worked out on read\n//   modified_by  formula    —          {id, name}; config.system modified_by, worked out on read\n//   status       list       status     the choice's key; config.status_groups key → todo | in_progress | done\n//   address      text       address    {street, city, region, postal_code, country}\n//   barcode      text       barcode    one printable line; config.symbology optional\n//   rich_text    text       rich_text  Markdown, no raw HTML\n//   count        formula    count      how many linked records (a roll-up whose agg is count)\n//\n// `fieldKindFor` (coerce.ts) still answers the BASE kind — a rating is a number, a status a\n// select, a count a roll-up — because every editor, sort and aggregate keyed on those keeps\n// working for them. What is particular to each newer kind asks `newerKindOf` here.\n\nimport { formatDurationSeconds } from \"@ai-matrx/kit/format\";\nimport type { CoercibleField } from \"./coerce\";\n\nexport const NEWER_FIELD_KINDS = [\n  \"rating\",\n  \"duration\",\n  \"created_by\",\n  \"modified_by\",\n  \"status\",\n  \"address\",\n  \"barcode\",\n  \"rich_text\",\n  \"count\",\n] as const;\n\nexport type NewerFieldKind = (typeof NEWER_FIELD_KINDS)[number];\n\ntype WithRules = CoercibleField & { rules?: ReadonlyArray<{ kind?: string; value?: unknown }> | null };\n\n/** Which of the nine newer kinds a Field is, read off its declaration — `custom.field_kind_of`'s mirror. */\nexport function newerKindOf(field: CoercibleField): NewerFieldKind | null {\n  const format = String(field.format ?? \"\");\n  const config = (field.config ?? {}) as Record<string, unknown>;\n  switch (field.type) {\n    case \"range\":\n      if (format === \"rating\") return \"rating\";\n      if (format === \"duration\") return \"duration\";\n      return null;\n    case \"text\":\n      if (format === \"address\") return \"address\";\n      if (format === \"barcode\") return \"barcode\";\n      if (format === \"rich_text\") return \"rich_text\";\n      return null;\n    case \"list\":\n      return format === \"status\" ? \"status\" : null;\n    case \"formula\":\n      if (format === \"count\" && config[\"agg\"] === \"count\") return \"count\";\n      if (config[\"system\"] === \"created_by\") return \"created_by\";\n      if (config[\"system\"] === \"modified_by\") return \"modified_by\";\n      return null;\n    default:\n      return null;\n  }\n}\n\n// ── RATING ─────────────────────────────────────────────────────────────────────────────────\n\n/** A rating's top star: its max Rule (1–10), else 5 — the store's own default. */\nexport function ratingMaxOf(field: CoercibleField): number {\n  const rules = (field as WithRules).rules ?? [];\n  const max = rules.find((r) => r?.kind === \"max\")?.value;\n  const n = typeof max === \"number\" ? max : typeof max === \"string\" ? Number(max) : NaN;\n  return Number.isInteger(n) && n >= 1 && n <= 10 ? n : 5;\n}\n\n// ── DURATION ───────────────────────────────────────────────────────────────────────────────\n\n/** Seconds as h:mm — and h:mm:ss only when there are seconds to show. 2700 → \"0:45\". */\nexport function durationText(seconds: unknown): string | null {\n  const n = typeof seconds === \"number\" ? seconds : typeof seconds === \"string\" && seconds.trim() !== \"\" ? Number(seconds) : NaN;\n  if (!Number.isFinite(n)) return null;\n  return formatDurationSeconds(Math.round(n), { granularity: \"minute\", signed: true });\n}\n\n/**\n * A length of time as a person types it, in seconds. \"45\" and \"45 min\" are 45 minutes (the column\n * reads in h:mm, so a bare number is minutes, as in Airtable); \"1:30\" is an hour and a half;\n * \"1:30:15\" has seconds; \"1h 30m\", \"2 hours\", \"90s\" read as written. Null: not a length of time.\n */\nexport function readTypedDuration(text: string): number | null {\n  const t = text.trim().toLowerCase();\n  if (t === \"\") return null;\n  const clock = /^(\\d+):([0-5]?\\d)(?::([0-5]?\\d))?$/.exec(t);\n  if (clock) return Number(clock[1]) * 3600 + Number(clock[2]) * 60 + Number(clock[3] ?? 0);\n  if (/^\\d+(\\.\\d+)?$/.test(t)) return Math.round(Number(t) * 60);\n  const unitRe = /(\\d+(?:\\.\\d+)?)\\s*(h|hr|hrs|hour|hours|m|min|mins|minute|minutes|s|sec|secs|second|seconds)\\b/g;\n  let total = 0;\n  let matched = \"\";\n  for (const m of t.matchAll(unitRe)) {\n    const n = Number(m[1]);\n    const u = m[2]!;\n    total += u.startsWith(\"h\") ? n * 3600 : u.startsWith(\"m\") ? n * 60 : n;\n    matched += m[0];\n  }\n  if (matched === \"\" || t.replace(unitRe, \"\").replace(/[\\s,and]+/g, \"\") !== \"\") return null;\n  return Math.round(total);\n}\n\n// ── ADDRESS ────────────────────────────────────────────────────────────────────────────────\n\nexport const ADDRESS_PARTS = [\"street\", \"city\", \"region\", \"postal_code\", \"country\"] as const;\nexport type AddressPart = (typeof ADDRESS_PARTS)[number];\nexport type AddressValue = Partial<Record<AddressPart, string | null>>;\n\nexport function isAddressValue(value: unknown): value is AddressValue {\n  return value !== null && typeof value === \"object\" && !Array.isArray(value)\n    && Object.keys(value as object).every((k) => (ADDRESS_PARTS as readonly string[]).includes(k));\n}\n\n/** An address on one line, the way an envelope reads: \"1420 Cedar Ridge Pkwy, Boulder, CO 80301, US\". */\nexport function addressValueText(value: unknown): string | null {\n  if (typeof value === \"string\") return value.trim() === \"\" ? null : value.trim();\n  if (!isAddressValue(value)) return null;\n  const part = (k: AddressPart) => (typeof value[k] === \"string\" ? (value[k] as string).trim() : \"\");\n  const regionLine = [part(\"region\"), part(\"postal_code\")].filter(Boolean).join(\" \");\n  const words = [part(\"street\"), part(\"city\"), regionLine, part(\"country\")].filter(Boolean).join(\", \");\n  return words === \"\" ? null : words;\n}\n\n/**\n * One typed line as an address in parts. \"street, city, region postal, country\" is read into its\n * parts when it has that shape; anything else is kept whole as the street, so nothing typed is lost.\n */\nexport function addressFromText(text: string): AddressValue | null {\n  const t = text.trim();\n  if (t === \"\") return null;\n  const bits = t.split(\",\").map((b) => b.trim()).filter(Boolean);\n  if (bits.length >= 3) {\n    const [street, city, regionPostal, ...rest] = bits;\n    const rp = /^(.*?)\\s*([A-Za-z0-9-]*\\d[A-Za-z0-9 -]*)?$/.exec(regionPostal ?? \"\");\n    const out: AddressValue = { street: street!, city: city! };\n    if (rp?.[1]) out.region = rp[1];\n    if (rp?.[2]) out.postal_code = rp[2].trim();\n    if (rest.length > 0) out.country = rest.join(\", \");\n    return out;\n  }\n  return { street: t };\n}\n\n/** A maps search for an address, opened in a new tab. */\nexport function addressMapUrl(value: unknown): string | null {\n  const words = addressValueText(value);\n  return words ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(words)}` : null;\n}\n\n// ── CREATED BY / LAST MODIFIED BY ──────────────────────────────────────────────────────────\n\n/**\n * `{id, name, actor}` as the store works it out (`custom.formula_value`). `actor` is the kind of\n * writer — a person (`user`), an agent (`agent`, with `id`/`name` the person it wrote for) or the\n * platform itself (`system`, with no id and no name). `name` is null for somebody outside the\n * organization. A value written before the store said `actor` is a person's.\n */\nexport type PersonStamp = { id: string | null; name: string | null; actor?: \"user\" | \"agent\" | \"system\" };\n\nexport function isPersonStamp(value: unknown): value is PersonStamp {\n  if (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n  const v = value as { id?: unknown; actor?: unknown };\n  return typeof v.id === \"string\" || v.actor === \"system\" || v.actor === \"agent\";\n}\n\n/** The words for who made or changed a record. */\nexport const NOT_A_MEMBER_WORDS = \"Not in this organization\";\nexport const SYSTEM_WRITER_WORDS = \"System\";\nexport const AGENT_WRITER_WORDS = \"Agent\";\nexport function personStampText(value: unknown): string | null {\n  if (!isPersonStamp(value)) return null;\n  const name = value.name && value.name.trim() !== \"\" ? value.name : null;\n  // V11 #6: a system write names the person who set it going, when the store knows who.\n  if (value.actor === \"system\") return name ? `${SYSTEM_WRITER_WORDS} for ${name}` : SYSTEM_WRITER_WORDS;\n  if (value.actor === \"agent\") return name ? `${AGENT_WRITER_WORDS} for ${name}` : AGENT_WRITER_WORDS;\n  return name ?? NOT_A_MEMBER_WORDS;\n}\n\n// ── STATUS ─────────────────────────────────────────────────────────────────────────────────\n\nexport const STATUS_GROUPS = [\"todo\", \"in_progress\", \"done\"] as const;\nexport type StatusGroup = (typeof STATUS_GROUPS)[number];\nexport const STATUS_GROUP_WORDS: Record<StatusGroup, string> = { todo: \"To do\", in_progress: \"In progress\", done: \"Done\" };\n\n/** Which group a status choice sits in; a choice the column names no group for is To do. */\nexport function statusGroupOf(field: CoercibleField, choiceKey: string): StatusGroup {\n  const groups = ((field.config ?? {}) as Record<string, unknown>)[\"status_groups\"];\n  const g = groups && typeof groups === \"object\" ? (groups as Record<string, unknown>)[choiceKey] : undefined;\n  return g === \"in_progress\" || g === \"done\" ? g : \"todo\";\n}\n\n// ── BARCODE ────────────────────────────────────────────────────────────────────────────────\n\nexport const BARCODE_SYMBOLOGIES = [\"code128\", \"code39\", \"ean13\", \"ean8\", \"upc_a\", \"upc_e\", \"itf\", \"qr\", \"data_matrix\", \"pdf417\"] as const;\n\n// ── RICH TEXT ──────────────────────────────────────────────────────────────────────────────\n\n/** Markdown read as plain words on one line — what a card title or a sentence shows. */\nexport function richTextPlain(text: string): string {\n  return text\n    .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, \"$1\")\n    .replace(/(\\*\\*|__|`)/g, \"\")\n    .replace(/(^|\\s)[*_]([^*_\\s][^*_]*)[*_](?=\\s|$|[.,;:!?])/g, \"$1$2\")\n    .replace(/^\\s*(#{1,3}|[-*+]|\\d+[.)])\\s+/gm, \"\")\n    .replace(/\\s*\\n+\\s*/g, \" · \")\n    .trim();\n}\n\n/**\n * A link destination as a browser would follow it: HTML entities and backslash escapes decoded,\n * spaces and control characters removed (`custom._entity_decoded`'s rules). Then only a web page,\n * an email address or a phone number is a link here; everything else is drawn as words.\n */\nexport function safeLinkHref(raw: string): string | null {\n  let t = raw\n    .replace(/&#[xX]([0-9a-fA-F]{1,8});?/g, (_, h: string) => codePoint(parseInt(h, 16)))\n    .replace(/&#([0-9]{1,8});?/g, (_, d: string) => codePoint(parseInt(d, 10)))\n    .replace(/&colon;/g, \":\").replace(/&Tab;/g, \"\\t\").replace(/&NewLine;/g, \"\\n\").replace(/&sol;/g, \"/\")\n    .replace(/&lpar;/g, \"(\").replace(/&rpar;/g, \")\").replace(/&period;/g, \".\").replace(/&nbsp;/g, \" \")\n    .replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&quot;/g, '\"').replace(/&apos;/g, \"'\").replace(/&amp;/g, \"&\")\n    .replace(/\\\\([!-/:-@[-`{-~])/g, \"$1\");\n  t = t.replace(/[\\s\\u0000-\\u001f\\u007f]/g, \"\");\n  return /^(https?:\\/\\/|mailto:|tel:)/i.test(t) ? t : null;\n}\n\nfunction codePoint(n: number): string {\n  return Number.isFinite(n) && n >= 1 && n <= 0x10ffff && !(n >= 0xd800 && n <= 0xdfff) ? String.fromCodePoint(n) : \"\\ufffd\";\n}\n\n// ── ONE VALUE IN WORDS ─────────────────────────────────────────────────────────────────────\n\n/**\n * The words one value of a newer kind reads as wherever only words fit — an export cell, a card,\n * a sentence. Null: not a newer kind, or nothing particular to say (the caller's own words apply).\n */\nexport function newerKindWords(field: CoercibleField, value: unknown): string | null {\n  if (value === null || value === undefined || value === \"\") return null;\n  switch (newerKindOf(field)) {\n    case \"duration\":\n      return durationText(value);\n    case \"address\":\n      return addressValueText(value);\n    case \"created_by\":\n    case \"modified_by\":\n      return personStampText(value);\n    case \"rich_text\":\n      return typeof value === \"string\" ? richTextPlain(value) : null;\n    case \"rating\": {\n      const n = typeof value === \"number\" ? value : Number(value);\n      return Number.isFinite(n) ? `${n} / ${ratingMaxOf(field)}` : null;\n    }\n    default:\n      return null;\n  }\n}\n\n/**\n * The words one value of a newer kind is EXPORTED or COPIED as: an address on one line, a length of\n * time as h:mm, who by name. A rating, a count and a barcode stay what they are, and rich text\n * stays its Markdown. Null: the value is written as it is stored.\n */\nexport function newerKindExportWords(field: CoercibleField, value: unknown): string | null {\n  const kind = newerKindOf(field);\n  if (kind === \"address\" || kind === \"duration\" || kind === \"created_by\" || kind === \"modified_by\") {\n    return newerKindWords(field, value);\n  }\n  return null;\n}\n","// src/typedDate.ts — @ai-matrx/records\n//\n// A DATE A PERSON TYPED, READ THE WAY THEIR SPREADSHEET READS IT — OR REFUSED.\n//\n// 🚨 WHY THIS EXISTS (merged-grid cold review, 2026-09-26). A person selected a Visit Date cell\n// and typed `10/03/2026`; the store kept **Jan 31, 0026**. `2026-10-03` became **Feb 6, 1003**.\n// The keystrokes after the first landed in the browser's segmented date control one segment at a\n// time and every half-typed segment was a \"valid\" date. The reading that replaces that is this\n// one, and it is the ONE reading every typed-date door uses (`coerceTypedAnswer` — the grid's\n// type-to-edit, a paste, a public form, an import).\n//\n// THE RULES (Google Sheets and Airtable are the bar):\n//   · `2026-10-03` (and `2026/10/03`) is year-month-day, everywhere.\n//   · `10/03/2026` is read in the reader's own LOCALE order — Oct 3 in en-US, 10 March in en-GB —\n//     exactly as their spreadsheet would. The locale is the caller's to pass; the default is the\n//     runtime's.\n//   · A month may be a word: `Oct 3, 2026`, `3 October 2026`, `Saturday, October 3, 2026`.\n//   · A TWO-DIGIT YEAR IS READ THE WAY GOOGLE SHEETS READS IT (grids review 3, 2026-09-30):\n//     `10/03/26` is Oct 3, 2026 — 00–29 is 2000–2029 and 30–99 is 1930–1999, Sheets' and Excel's\n//     own window, so a birthday typed `5/12/85` is 1985 and a visit typed `10/03/26` is this year.\n//     Airtable and Sheets both take it; refusing it was the one place this reader was stricter\n//     than every spreadsheet a person has used.\n//   · NOTHING ELSE IS GUESSED. A missing year (which year?), a one- or three-digit year, a\n//     month 0 or 13, a 31 September, a year before 1000 (that is a half-typed year, not a date\n//     anybody means) — each is REFUSED with a sentence saying how to write it.\n//   · A time of day may follow (`10/03/2026 2:30 PM`, `2026-10-03T14:30`, an ISO instant with its\n//     zone). A day-only column keeps the DAY that was written; a date-and-time column keeps the\n//     instant, read in the reader's own zone unless the text names one.\n\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\n// THE PERSON'S ZONE, read from the ONE slot design-system's `setDisplayTimeZone` writes (the host sets it\n// once at boot through records-ui `setPersonTimeZone`). Same key, same shape: this file only READS it, so a\n// typed `10/03/2026 9:30 AM` is placed at 9:30 in the zone the cell, the panel and the calendar show it in.\n// Without a saved zone the device's zone applies, exactly as before (AT-FIX-J2, 2026-10-20).\nconst zoneSlot = globalSlot<{ zone: string | null }>(\"ai-matrx.design-system.display-time-zone\", () => ({ zone: null }));\n\nexport type WallParts = { year: number; month: number; day: number; hour: number; minute: number; second: number };\n\n/** The wall clock `at` shows in the person's zone (the device's when none is saved). */\nexport function wallPartsInZone(at: Date): WallParts {\n  const zone = zoneSlot.zone;\n  if (!zone) {\n    return { year: at.getFullYear(), month: at.getMonth() + 1, day: at.getDate(), hour: at.getHours(), minute: at.getMinutes(), second: at.getSeconds() };\n  }\n  const parts = new Intl.DateTimeFormat(\"en-US\", {\n    timeZone: zone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\", hourCycle: \"h23\",\n  }).formatToParts(at);\n  const n = (t: string) => Number(parts.find((p) => p.type === t)?.value ?? 0);\n  return { year: n(\"year\"), month: n(\"month\"), day: n(\"day\"), hour: n(\"hour\"), minute: n(\"minute\"), second: n(\"second\") };\n}\n\n/** The instant at which the person's wall clock reads these fields (the device's clock when no zone is saved). */\nexport function instantFromWallInZone(year: number, month: number, day: number, hour = 0, minute = 0, second = 0): Date {\n  const zone = zoneSlot.zone;\n  if (!zone) return new Date(year, month - 1, day, hour, minute, second);\n  const target = Date.UTC(year, month - 1, day, hour, minute, second);\n  let guess = target;\n  for (let i = 0; i < 3; i += 1) {\n    const w = wallPartsInZone(new Date(guess));\n    guess -= Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second) - target;\n  }\n  return new Date(guess);\n}\n\nexport type TypedDate =\n  | { ok: true; day: string; /** ISO instant, only when a time of day was read or asked for. */ instant: string | null }\n  | { ok: false; why: string };\n\nconst MONTHS: Record<string, number> = {\n  jan: 1, january: 1, feb: 2, february: 2, mar: 3, march: 3, apr: 4, april: 4, may: 5, jun: 6, june: 6,\n  jul: 7, july: 7, aug: 8, august: 8, sep: 9, sept: 9, september: 9, oct: 10, october: 10,\n  nov: 11, november: 11, dec: 12, december: 12,\n};\nconst WEEKDAYS = /\\b(mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)(day|nesday|sday|urday)?\\b\\.?,?/gi;\n\n/** The order a locale writes a numeric date in: \"mdy\", \"dmy\" or \"ymd\". */\nexport function localeDateOrder(locale?: string): \"mdy\" | \"dmy\" | \"ymd\" {\n  try {\n    const parts = new Intl.DateTimeFormat(locale, { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).formatToParts(\n      new Date(2026, 10, 22),\n    );\n    const order = parts\n      .filter((p) => p.type === \"year\" || p.type === \"month\" || p.type === \"day\")\n      .map((p) => p.type.charAt(0))\n      .join(\"\");\n    if (order === \"mdy\" || order === \"dmy\" || order === \"ymd\") return order;\n  } catch {\n    // An unknown locale reads like the runtime's own.\n  }\n  return \"mdy\";\n}\n\nfunction pad(n: number, width = 2): string {\n  return String(n).padStart(width, \"0\");\n}\n\nfunction daysIn(year: number, month: number): number {\n  return new Date(Date.UTC(year, month, 0)).getUTCDate();\n}\n\nconst HOW = \"Write it as 2026-10-03, or with the year in full the way your spreadsheet writes dates.\";\n\n/** Sheets' and Excel's two-digit-year window: 00–29 → 2000–2029, 30–99 → 1930–1999. */\nexport function twoDigitYear(yy: number): number {\n  return yy < 30 ? 2000 + yy : 1900 + yy;\n}\n\nfunction checkDay(year: number, month: number, day: number, raw: string): string | null {\n  if (year < 1000 || year > 9999) return `“${raw}” has no four-digit year. ${HOW}`;\n  if (month < 1 || month > 12) return `“${raw}” names no month (month ${month}). ${HOW}`;\n  if (day < 1 || day > daysIn(year, month)) return `“${raw}” is not a day on the calendar. ${HOW}`;\n  return null;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// A TIME OF DAY A PERSON TYPED (Arman, 2026-09-28: \"if I type 1200PM, I get 2:00PM because the\n// one isn't recorded\"). The grid opens a plain text box holding every key typed; this reads it\n// ONCE, on commit — for a time column and for the time after a date-and-time column's day.\n//\n//   · 12-hour with a meridiem, any spelling: `1200PM`, `12pm`, `12:00 PM`, `9:30a`, `9:30 a.m.`,\n//     `930am`, `2 PM`.\n//   · 24-hour with minutes: `14:30`, `14:30:05`, and the four-digit clock `1200`, `0930`.\n//   · NOTHING IS GUESSED. A bare hour (`9` — morning or evening?), three bare digits (`200`),\n//     `13pm`, `0am`, `25:00`, `12:60`, `2400` — each is refused with the way to write it.\n// ───────────────────────────────────────────────────────────────────────────\n\nexport type TypedTime = { ok: true; /** 24-hour `HH:MM`, or `HH:MM:SS` when seconds were typed. */ value: string } | { ok: false; why: string };\n\nconst TIME_HOW = \"Write it like 2:30 PM or 14:30.\";\nconst MERIDIEM = String.raw`([ap])\\.?\\s*(?:m\\.?)?`;\nconst CLOCK = new RegExp(String.raw`^(\\d{1,2})(?::(\\d{2})(?::(\\d{2}))?)?\\s*(?:${MERIDIEM})?$`, \"i\");\nconst COMPACT = new RegExp(String.raw`^(\\d{3,4})\\s*(?:${MERIDIEM})?$`, \"i\");\n\n/** Read one typed time of day. Never throws; never guesses. */\nexport function readTypedTime(raw: string): TypedTime {\n  const text = raw.trim();\n  if (text === \"\") return { ok: false, why: \"Nothing was typed.\" };\n  let h: number;\n  let m: number;\n  let s: number | null = null;\n  let meridiem: string | undefined;\n  const clock = CLOCK.exec(text);\n  const compact = clock ? null : COMPACT.exec(text);\n  if (clock) {\n    h = Number(clock[1]);\n    meridiem = clock[4]?.toLowerCase();\n    if (clock[2] === undefined && !meridiem) {\n      return { ok: false, why: `“${raw}” could be in the morning or the evening. ${TIME_HOW}` };\n    }\n    m = clock[2] === undefined ? 0 : Number(clock[2]);\n    if (clock[3] !== undefined) s = Number(clock[3]);\n  } else if (compact) {\n    const digits = compact[1]!;\n    meridiem = compact[2]?.toLowerCase();\n    if (digits.length === 3 && !meridiem) {\n      return { ok: false, why: `“${raw}” could be in the morning or the evening. ${TIME_HOW}` };\n    }\n    h = Number(digits.slice(0, digits.length - 2));\n    m = Number(digits.slice(-2));\n  } else {\n    return { ok: false, why: `“${raw}” is not a time of day. ${TIME_HOW}` };\n  }\n  if (meridiem) {\n    if (h < 1 || h > 12) return { ok: false, why: `“${raw}” is not a time of day. ${TIME_HOW}` };\n    if (meridiem === \"p\" && h !== 12) h += 12;\n    if (meridiem === \"a\" && h === 12) h = 0;\n  }\n  if (h > 23 || m > 59 || (s !== null && s > 59)) return { ok: false, why: `“${raw}” is not a time of day. ${TIME_HOW}` };\n  return { ok: true, value: [h, m, ...(s === null ? [] : [s])].map((n) => pad(n)).join(\":\") };\n}\n\n/**\n * A time of day at the END of the text: `14:30`, `2:30 pm`, `2 PM`, `1200PM`, `14:30:05`. A bare\n * number (`2026` after `Oct 3`) is never a time — only one with a colon or a meridiem is.\n */\nfunction splitTime(text: string): { date: string; time: { h: number; m: number; s: number } | null; bad: boolean } {\n  const match = /(?:^|[\\sT,]+)(?:at\\s+)?(\\d{1,4}(?::\\d{2}){0,2}\\s*(?:[ap]\\.?\\s*(?:m\\.?)?)?)$/i.exec(text);\n  if (!match) return { date: text, time: null, bad: false };\n  const token = match[1]!;\n  if (!/:|[ap]\\.?\\s*(?:m\\.?)?$/i.test(token)) return { date: text, time: null, bad: false };\n  const read = readTypedTime(token);\n  if (!read.ok) return { date: text, time: null, bad: true };\n  const [h, m, s] = read.value.split(\":\").map(Number);\n  return { date: text.slice(0, match.index).replace(/[\\s,]+$/, \"\"), time: { h: h!, m: m!, s: s ?? 0 }, bad: false };\n}\n\n/**\n * Read one typed date. `withTime` is true for a date-and-time column. Never throws; never guesses.\n */\nexport function readTypedDate(raw: string, opts: { withTime?: boolean; locale?: string } = {}): TypedDate {\n  const text = raw.trim();\n  if (text === \"\") return { ok: false, why: \"Nothing was typed.\" };\n\n  // AN ISO INSTANT WITH ITS ZONE (`…Z`, `…+02:00`) is a moment, placed exactly.\n  const iso = /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})$/i.exec(text);\n  if (iso) {\n    const bad = checkDay(Number(iso[1]), Number(iso[2]), Number(iso[3]), raw);\n    if (bad) return { ok: false, why: bad };\n    const at = new Date(Date.parse(text));\n    if (Number.isNaN(at.getTime())) return { ok: false, why: `“${raw}” is not a moment this can read. ${HOW}` };\n    const w = wallPartsInZone(at);\n    const day = `${pad(w.year, 4)}-${pad(w.month)}-${pad(w.day)}`;\n    return { ok: true, day, instant: opts.withTime ? at.toISOString() : null };\n  }\n\n  const split = splitTime(text.replace(/(\\d)T(\\d)/, \"$1 $2\"));\n  if (split.bad) return { ok: false, why: `“${raw}” has a time of day that is not one. Write it as 14:30 or 2:30 PM.` };\n  const dateText = split.date.replace(WEEKDAYS, \" \").replace(/\\s+/g, \" \").trim();\n\n  let year: number | null = null;\n  let month: number | null = null;\n  let day: number | null = null;\n\n  const numeric = /^(\\d{1,4})[/.\\-](\\d{1,2})[/.\\-](\\d{1,4})$/.exec(dateText);\n  // DIGITS WITH NO SEPARATORS (Arman 2026-10-08: typing `10142026` did nothing): MMDDYYYY / DDMMYYYY by\n  // the locale's order, MMDDYY (the two-digit-year window), and YYYYMMDD when that is the only reading.\n  const compact = /^(\\d{8}|\\d{6})$/.exec(dateText);\n  if (compact) {\n    const digits = compact[1]!;\n    const order = localeDateOrder(opts.locale);\n    const first2 = Number(digits.slice(0, 2));\n    const mid2 = Number(digits.slice(2, 4));\n    if (digits.length === 6) {\n      year = twoDigitYear(Number(digits.slice(4)));\n      if (order === \"dmy\") {\n        day = first2;\n        month = mid2;\n      } else {\n        month = first2;\n        day = mid2;\n      }\n    } else {\n      const yearLast = Number(digits.slice(4));\n      const yearFirst = Number(digits.slice(0, 4));\n      const localeReading = (): [number, number, number] =>\n        order === \"dmy\" ? [yearLast, mid2, first2] : [yearLast, first2, mid2];\n      const [ly, lm, ld] = localeReading();\n      const isoReading: [number, number, number] = [yearFirst, Number(digits.slice(4, 6)), Number(digits.slice(6))];\n      const fits = (y: number, m: number, d: number) => checkDay(y, m, d, raw) === null;\n      const useIso = order === \"ymd\" || (!fits(ly, lm, ld) && fits(...isoReading));\n      [year, month, day] = useIso ? isoReading : [ly, lm, ld];\n    }\n  } else if (numeric) {\n    const [a, b, c] = [numeric[1]!, numeric[2]!, numeric[3]!];\n    if (a.length === 4) {\n      year = Number(a);\n      month = Number(b);\n      day = Number(c);\n    } else {\n      if (c.length !== 4 && c.length !== 2) {\n        return { ok: false, why: `“${raw}” has no four-digit year. ${HOW}` };\n      }\n      const order = localeDateOrder(opts.locale);\n      year = c.length === 2 ? twoDigitYear(Number(c)) : Number(c);\n      if (order === \"dmy\") {\n        day = Number(a);\n        month = Number(b);\n      } else {\n        month = Number(a);\n        day = Number(b);\n      }\n    }\n  } else {\n    // A MONTH WRITTEN AS A WORD, in any order with a day and a four-digit year.\n    const tokens = dateText.toLowerCase().replace(/,/g, \" \").split(/[\\s/.\\-]+/).filter(Boolean);\n    const numbers: string[] = [];\n    for (const token of tokens) {\n      const word = token.replace(/\\.$/, \"\");\n      if (/^\\d+(st|nd|rd|th)?$/.test(word)) numbers.push(word.replace(/\\D/g, \"\"));\n      else if (word in MONTHS) {\n        if (month !== null) return { ok: false, why: `“${raw}” names two months. ${HOW}` };\n        month = MONTHS[word]!;\n      } else if (word !== \"of\" && word !== \"the\") {\n        return { ok: false, why: `“${raw}” is not a date this can read. ${HOW}` };\n      }\n    }\n    if (month === null) return { ok: false, why: `“${raw}” is not a date this can read. ${HOW}` };\n    const years = numbers.filter((n) => n.length === 4);\n    const days = numbers.filter((n) => n.length <= 2);\n    if (years.length === 0) {\n      return {\n        ok: false,\n        why: numbers.some((n) => n.length === 3)\n          ? `“${raw}” has no four-digit year. ${HOW}`\n          : `“${raw}” has no year, and which year it means is a guess. Write the year too (${\n              days[0] ?? \"3\"\n            } ${Object.keys(MONTHS).find((k) => MONTHS[k] === month && k.length > 3) ?? \"October\"} 2026).`,\n      };\n    }\n    if (years.length > 1 || days.length !== 1 || numbers.length !== 2) {\n      return { ok: false, why: `“${raw}” is not a date this can read. ${HOW}` };\n    }\n    year = Number(years[0]);\n    day = Number(days[0]);\n  }\n\n  const bad = checkDay(year!, month!, day!, raw);\n  if (bad) return { ok: false, why: bad };\n  const dayText = `${pad(year!, 4)}-${pad(month!)}-${pad(day!)}`;\n  if (!opts.withTime) return { ok: true, day: dayText, instant: null };\n  const t = split.time ?? { h: 0, m: 0, s: 0 };\n  const local = instantFromWallInZone(year!, month!, day!, t.h, t.m, t.s);\n  return { ok: true, day: dayText, instant: local.toISOString() };\n}\n\n/**\n * THE TEXT AN EDITOR OPENS WITH for a stored day or instant — the inverse of `readTypedDate`, so\n * a cell opened by Enter or a double-click shows words this reader reads back to the SAME value\n * (grids review 3, 2026-09-30: those paths used to open the browser's own date picker, which lost\n * the time and saved \"now\"). The day is numeric in the locale's own order with a four-digit year;\n * a time of day follows as `2:30 PM` (a 12-hour locale) or `14:30` (a 24-hour one). An unreadable\n * stored value opens as the stored characters, untouched.\n */\nexport function typedDateText(value: unknown, opts: { withTime?: boolean; locale?: string } = {}): string {\n  if (value === null || value === undefined || value === \"\") return \"\";\n  const text = String(value).trim();\n  const bare = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(text);\n  let y: number;\n  let mo: number;\n  let d: number;\n  let at: Date | null = null;\n  let wall: WallParts | null = null;\n  if (bare) {\n    y = Number(bare[1]);\n    mo = Number(bare[2]);\n    d = Number(bare[3]);\n  } else {\n    const parsed = value instanceof Date ? value : new Date(Date.parse(text));\n    if (Number.isNaN(parsed.getTime())) return text;\n    at = parsed;\n    wall = wallPartsInZone(parsed);\n    y = wall.year;\n    mo = wall.month;\n    d = wall.day;\n  }\n  const order = localeDateOrder(opts.locale);\n  const day =\n    order === \"ymd\" ? `${pad(y, 4)}-${pad(mo)}-${pad(d)}` : order === \"dmy\" ? `${pad(d)}/${pad(mo)}/${pad(y, 4)}` : `${pad(mo)}/${pad(d)}/${pad(y, 4)}`;\n  if (!opts.withTime || !at || !wall) return day;\n  return `${day} ${typedTimeText(wall.hour, wall.minute, opts.locale)}`;\n}\n\n/** A time of day in the words `readTypedTime` reads back: `2:30 PM` or `14:30`, by the locale's clock. */\nexport function typedTimeText(hours: number, minutes: number, locale?: string): string {\n  let twelve = true;\n  try {\n    twelve = new Intl.DateTimeFormat(locale, { hour: \"numeric\" }).resolvedOptions().hour12 !== false;\n  } catch {\n    // An unknown locale reads like the runtime's own.\n  }\n  if (!twelve) return `${pad(hours)}:${pad(minutes)}`;\n  const h = hours % 12 === 0 ? 12 : hours % 12;\n  return `${h}:${pad(minutes)} ${hours < 12 ? \"AM\" : \"PM\"}`;\n}\n","// src/typedNumber.ts — @ai-matrx/records\n//\n// A NUMBER A PERSON TYPED, READ THE WAY THEY WROTE IT — OR REFUSED.\n//\n// PEOPLE WRITE NUMBERS THE WAY PEOPLE WRITE NUMBERS. \"$1,250.50\", \"(300)\" for a negative, \"12%\",\n// \"2019\" — all of them are the number that was meant, and refusing them makes the column people use\n// most the one the product cannot take. A MINUS IS A MINUS WHEREVER THE SYMBOL SITS (merged-grid\n// review 2, A9): \"-$150\", \"$-150\" and \"(150)\" are all −150 — Sheets and Airtable read every one of\n// them. Everything before the first digit is symbols and signs; an odd number of minus signs there\n// is a negative.\n//\n// This is the ONE reading: `coerceTypedAnswer` (every record-store door) and the older `/data` grid's\n// typed cells both call it, so a number typed into any grid is read the same way.\n\nexport type TypedNumber = { ok: true; value: number } | { ok: false; why: string };\n\nexport function readTypedNumber(raw: string): TypedNumber {\n  const text = raw.trim();\n  if (text === \"\") return { ok: false, why: \"Nothing was typed.\" };\n  const bracketed = /^\\(.*\\)$/.test(text);\n  const inner = (bracketed ? text.slice(1, -1) : text).replace(/[\\s,]/g, \"\");\n  const head = /^[^\\d.]*/.exec(inner)?.[0] ?? \"\";\n  const bare = inner.slice(head.length).replace(/[^\\d.eE+-]+$/, \"\");\n  const minus = (head.match(/[-−]/g) ?? []).length % 2 === 1;\n  const n = Number(bare);\n  if (bare === \"\" || Number.isNaN(n) || /[\\d]/.test(head)) {\n    return {\n      ok: false,\n      why: `“${raw}” is not a number. Write digits, like 1,250.50 — a currency symbol, a percent sign, commas and a minus sign or brackets for a negative are all fine.`,\n    };\n  }\n  return { ok: true, value: bracketed !== minus ? -n : n };\n}\n","// src/coerce.ts — @ai-matrx/records\n//\n// WHAT A TYPED-IN ANSWER MEANS TO A FIELD, WORKED OUT IN ONE PLACE.\n//\n// Every door into this store that a person types into hands over STRINGS. A\n// browser input is a string. A clipboard cell is a string. A CSV column is a\n// string. A public form's answer is a string. A Field does not take strings —\n// it takes a number, a tick, a day, the id of an option — and until the string\n// has been turned into one of those, the store refuses it.\n//\n// 🚨 WHY THIS FILE EXISTS (AGENT-BUILDS-2's closing walk, 2026-09-21). A real\n// customer picked a time on Ironclad Mobile Mechanic's public booking page,\n// typed their name, phone, email and vehicle, pressed \"Book it\", and the page\n// answered:\n//\n//     \"Vehicle Year takes a number, and it was given a string\"\n//\n// The appointment was not made. `BookingPicker` kept its answers as\n// `Record<string, string>` and the confirm route passed them through\n// uncoerced, so a booking page over ANY table with a number field was\n// unbookable by anybody. The store was right and said so plainly; nothing\n// between the person and the store had done the one job that stood between\n// them.\n//\n// AND IT WAS ALREADY SOLVED, TWICE, IN THE WRONG PLACE. The grid's paste\n// (`@ai-matrx/records-ui` `valueFromPastedText`) and the import wizard each\n// carried their own coercion — good ones — while the two PUBLIC doors, the\n// ones a stranger uses, carried none. That is the shape the platform-primitive\n// rule exists to stop: a capability scoped to the feature that surfaced it.\n// So the coercion lives HERE, headless, in the package both the screens and\n// the servers already depend on, and every one of those five doors calls it.\n//\n// THREE RULES IT KEEPS:\n//\n//   1. A VALUE IS COERCED, NEVER GUESSED. Writing 0 for \"about twelve\" or\n//      today's date for \"soon\" is how a table quietly becomes untrue. A string\n//      that is not the thing is REFUSED, by name, in words the person can act\n//      on — \"Vehicle Year holds a number, and “twenty nineteen” is not one\".\n//   2. THE REFUSAL NAMES THE FIELD IN PLAIN WORDS, never its key, because the\n//      person reading it is looking at a label on a screen.\n//   3. IT DECIDES NOTHING ELSE. Required, the Rules, the cap, the rate window\n//      and every wall are the store's. This turns a string into a value and\n//      stops.\n\nimport { isButtonField } from \"./button\";\nimport { addressFromText, newerKindOf, ratingMaxOf, readTypedDuration } from \"./newerKinds\";\nimport { readTypedDate } from \"./typedDate\";\nimport { readTypedNumber } from \"./typedNumber\";\nimport type { Field } from \"./field\";\nimport { isUuidShape } from \"@ai-matrx/kit/uuid\";\n\n/**\n * EVERYTHING THIS FILE NEEDS TO KNOW ABOUT A FIELD, AND NOTHING ELSE.\n *\n * A full `Field` satisfies it, and so does every narrower shape the store\n * hands to a screen — `CaptureField` on a phone with no network, the Field\n * documents `custom.form_public` and `custom.booking_public` publish to a\n * stranger. Asking for the whole `Field` would have meant the capture sheet\n * keeping its own `Number(raw)`, which is exactly the duplication this file\n * exists to end.\n */\nexport interface CoercibleField {\n  /** Named so the doc above is checkable: a full {@link Field} satisfies this. */\n  key: string;\n  label?: string | null;\n  name?: string | null;\n  /** FLD-1. One of five behaviors; an unknown word is treated as text. */\n  type?: string | null;\n  multi?: boolean | null;\n  unit?: string | null;\n  format?: string | null;\n  config?: Record<string, unknown> | null | undefined;\n  relation_target?: string | null;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// WHICH KIND OF THING A FIELD HOLDS\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * The kind of value one Field holds, resolved from its DECLARATION only — the\n * behavior, the modifiers, the format, the relation target and the config,\n * because those are what the store stores. A screen that guessed from a value\n * would answer differently for an empty record.\n *\n * `plain_text`, `long_text`, `number` and `boolean` are behaviors that carry\n * no parity type of their own; every other word here is a parity type.\n */\nexport const FIELD_KINDS = [\n  \"attachment\",\n  \"checkbox\",\n  \"currency\",\n  \"datetime\",\n  \"email\",\n  \"entity_reference\",\n  \"formula\",\n  \"json\",\n  \"long_text\",\n  \"lookup\",\n  \"member\",\n  \"multi_select\",\n  \"number\",\n  \"percent\",\n  \"phone\",\n  \"relation\",\n  \"rollup\",\n  \"select\",\n  \"text\",\n  \"url\",\n] as const;\n\n/**\n * 🚨 THE UNION IS DERIVED FROM THE LIST ABOVE, and the list is what the\n * coverage test walks. Add a kind here and the test drives it; give it no arm\n * in `coerceTypedAnswer` and the test says so, instead of the new kind falling\n * quietly through `default:` and a value of the wrong shape reaching the store\n * — which is the whole defect this file closes.\n */\nexport type FieldKind = (typeof FIELD_KINDS)[number];\n\n/** The kernel Table ids REC-31 pins the two special relations to. */\nexport interface KernelTargets {\n  /** The kernel `File` Table — a relation pointing here is an attachment. */\n  file?: string | undefined;\n  /** The kernel `Person` Table — a relation pointing here names a person. */\n  person?: string | undefined;\n}\n\n/**\n * Which kind of value this Field holds.\n *\n * This is the ONE classifier. `@ai-matrx/records-ui`'s `editorKindFor` — which\n * decides which editor to DRAW — delegates to it, so the editor a person types\n * into and the coercion applied to what they typed can never disagree about\n * the same column. They did disagree before this moved here; that is the whole\n * class of grid defect September was spent closing.\n */\nexport function fieldKindFor(field: CoercibleField, kernel?: KernelTargets): FieldKind {\n  const format = (field.format ?? \"\").toLowerCase();\n  const config = field.config ?? {};\n  switch (field.type) {\n    case \"boolean\":\n      return \"checkbox\";\n    case \"text\":\n      if (format === \"email\") return \"email\";\n      if (format === \"phone\") return \"phone\";\n      if (format === \"url\") return \"url\";\n      if (format === \"json\" || config[\"json\"] === true) return \"json\";\n      if (format === \"long\" || config[\"multiline\"] === true) return \"long_text\";\n      return \"text\";\n    case \"range\": {\n      const kind = String(config[\"kind\"] ?? \"number\");\n      if (kind === \"date\" || kind === \"datetime\") return \"datetime\";\n      // THE FORMAT, AND ONLY THE FORMAT. A unit is not a currency: a quantity\n      // in `units` drawn as money is how three of something rendered \"units3\".\n      if (format === \"currency\") return \"currency\";\n      if (format === \"percent\" || field.unit === \"%\") return \"percent\";\n      return \"number\";\n    }\n    case \"list\":\n      return field.multi ? \"multi_select\" : \"select\";\n    case \"relation\": {\n      // SC-R / P12: a relation whose target mode is `any`, restricted by\n      // `config.allowed_types` to the platform kinds it may name (notes, web\n      // sites, agents …). Its value is `{token, id}`, never a record id.\n      if (isEntityReferenceConfig(config)) return \"entity_reference\";\n      const parity = String(config[\"parity\"] ?? \"\");\n      if (parity === \"attachment\" || parity === \"member\") return parity;\n      if (kernel?.file && field.relation_target === kernel.file) return \"attachment\";\n      if (kernel?.person && field.relation_target === kernel.person) return \"member\";\n      return \"relation\";\n    }\n    case \"formula\": {\n      const via = config[\"via\"];\n      if (via && config[\"agg\"]) return \"rollup\";\n      if (via) return \"lookup\";\n      return \"formula\";\n    }\n    default:\n      return \"text\";\n  }\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// THE COERCION\n// ───────────────────────────────────────────────────────────────────────────\n\n/** One Field's options, indexed the way a typed word has to be looked up. */\nexport interface ChoiceOptions {\n  /**\n   * Lower-cased label, option key and option id, each pointing at the OPTION\n   * RECORD'S ID — which is what a list Field stores (FLD-5/FLD-6).\n   */\n  byWord: Map<string, string>;\n  /** The labels, in order, so a refusal can say what is on offer. */\n  labels: string[];\n}\n\n/** What one answer became, or the sentence saying why it did not. */\nexport type Coerced =\n  | { value: unknown; words?: string }\n  | { refusal: string; unmatched?: string[]; unmatchedNames?: string[]; ambiguousNames?: string[] };\n\n// ───────────────────────────────────────────────────────────────────────────\n// A RELATION FILLED BY NAME (VISION-REACH G1, 2026-10-02)\n// ───────────────────────────────────────────────────────────────────────────\n//\n// A spreadsheet names the record a row points at — \"Dr. Marisol Gutierrez\" —\n// and Airtable fills a linked-record cell from exactly that: the primary\n// field's text. Until this lane every door here refused it (\"chosen rather than\n// typed\"), and the sitting's clinic file needed 264 hand picks. The store now\n// matches a name through ONE matcher (`custom._relation_names_resolve`); a\n// screen asks it in one call per column (`relationNamesMatch`) and hands the\n// answer to the coercion below as a `RelationNameIndex`, exactly as a choice\n// column hands its `ChoiceOptions`. One match is that record; none, or two\n// records of one name, is REFUSED and named — never guessed.\n\n/** One record a name matched. */\nexport interface RelationNameMatch {\n  id: string;\n  /** What its name column holds. */\n  words: string;\n}\n\n/** A relation's target Table, indexed by the names a column holds. */\nexport interface RelationNameIndex {\n  /** `relationNameKey(name)` → the records of that name the reader may see. */\n  byName: ReadonlyMap<string, readonly RelationNameMatch[]>;\n  /** The target Table's name, for the sentence. */\n  tableName: string;\n  /** The target Table's name column, so a screen can make a record a name matched nothing. */\n  titleField: string | null;\n}\n\n/** The answer of `custom.relation_names_match`. */\nexport interface RelationNamesAnswer {\n  table_id: string;\n  table_name: string | null;\n  title_field?: string | null;\n  names: Array<{ name: string; matches: RelationNameMatch[] }>;\n}\n\n/**\n * How two spellings of one name are told apart: case and runs of spacing do not\n * count. The store's `custom.relation_name_key` is the same rule.\n */\nexport function relationNameKey(name: string): string {\n  return name.trim().replace(/\\s+/g, \" \").toLowerCase();\n}\n\n/** How an import splits a cell that holds several values: `;`, `,` or `|` — the store's own rule. */\nexport function splitListCell(text: string): string[] {\n  return text\n    .split(/\\s*[;,|]\\s*/)\n    .map((w) => w.trim())\n    .filter((w) => w.length > 0);\n}\n\n/** Index the store's answer the way the coercion reads it. */\nexport function relationNameIndexOf(answer: RelationNamesAnswer): RelationNameIndex {\n  const byName = new Map<string, RelationNameMatch[]>();\n  for (const entry of answer.names ?? []) {\n    const key = relationNameKey(entry.name ?? \"\");\n    if (key !== \"\" && !byName.has(key)) byName.set(key, entry.matches ?? []);\n  }\n  return { byName, tableName: answer.table_name ?? \"that table\", titleField: answer.title_field ?? null };\n}\n\n/** Every name a relation cell carries, split the way the column reads it. */\nexport function relationNamesIn(field: Pick<CoercibleField, \"multi\">, text: string): string[] {\n  const t = text.trim();\n  if (t === \"\") return [];\n  const parts = field.multi ? splitListCell(t) : [t];\n  return parts.filter((p) => !isUuidShape(p));\n}\n\nexport interface CoerceOptions {\n  /** This Field's choices, when the caller has loaded them. */\n  options?: ChoiceOptions | undefined;\n  /** The kernel File/Person Table ids, for classifying relations. */\n  kernel?: KernelTargets | undefined;\n  /**\n   * The Field's name as this caller writes it. Defaults to the Field's own\n   * label. A screen passes the same words it printed above the box.\n   */\n  label?: string | undefined;\n  /**\n   * A relation column's target, indexed by name (`relationNameIndexOf`). Without it a name in a\n   * relation column is refused as before, and only ids pass.\n   */\n  relationNames?: RelationNameIndex | undefined;\n  /** The reader's locale, for the order of a numeric date (10/03/2026). Defaults to the runtime's. */\n  locale?: string | undefined;\n  /**\n   * The column SHOWS AS a percent (its \"Shows as\" format, which this package does not read), so a\n   * typed `45%` is a percent here even on a number column. The caller scales it to the column's\n   * own percent scale.\n   */\n  showsAsPercent?: boolean | undefined;\n}\n\n/** The money symbols people type, and the currency each one names. `¥` names two, so it names none. */\nconst MONEY_SYMBOLS: Record<string, string> = { $: \"USD\", \"€\": \"EUR\", \"£\": \"GBP\", \"₹\": \"INR\", \"₩\": \"KRW\", \"₪\": \"ILS\", \"₺\": \"TRY\", \"₽\": \"RUB\" };\n\n/** The currency a money column keeps (`unit` holds a symbol or an ISO code), or null when it names none. */\nfunction columnCurrency(unit: string | null | undefined): string | null {\n  const u = (unit ?? \"\").trim();\n  if (u === \"\") return null;\n  if (MONEY_SYMBOLS[u]) return MONEY_SYMBOLS[u]!;\n  return /^[A-Za-z]{3}$/.test(u) ? u.toUpperCase() : null;\n}\n\n/**\n * A UNIT THAT DOES NOT BELONG TO THE COLUMN IS REFUSED, NEVER DROPPED (grids review 3,\n * 2026-09-30): `45%` typed into a Number column was kept as 45 and into a Money column as $45.00 —\n * the percent sign silently thrown away, a value off by a hundred times. Sheets reads `45%` as the\n * percent it is; a column that does not keep percents says so, with the way to write its own kind.\n */\nfunction foreignUnit(kind: \"number\" | \"currency\" | \"percent\", text: string, label: string, raw: string, unit: string | null | undefined, showsAsPercent: boolean): string | null {\n  const hasPercent = /%/.test(text);\n  const symbols = [...text].filter((ch) => ch in MONEY_SYMBOLS);\n  if (kind === \"percent\" || showsAsPercent) {\n    if (symbols.length > 0) return `${label} holds a percent, and “${raw}” is an amount of money. Write it like 45%.`;\n    return null;\n  }\n  if (hasPercent) {\n    return kind === \"currency\"\n      ? `${label} holds an amount of money, and “${raw}” is a percent. Write the amount, like $1,250.50.`\n      : `${label} holds a number, and “${raw}” is a percent, which this column does not show. Write 45 or 0.45 — or make the column show as a percent.`;\n  }\n  if (kind === \"currency\") {\n    const own = columnCurrency(unit);\n    const typed = symbols.map((ch) => MONEY_SYMBOLS[ch]!);\n    if (own && typed.some((code) => code !== own)) {\n      return `${label} holds an amount in ${own}, and “${raw}” is written in ${typed.find((code) => code !== own)}. Write the amount in ${own}.`;\n    }\n  }\n  return null;\n}\n\n/** One id, written the way the store writes one (REC-51's shape test). */\n\n/**\n * SC-R / P12. Whether a relation's config makes it an ENTITY REFERENCE: target\n * mode `any` (REL-8) restricted by a non-empty `allowed_types`.\n */\nexport function isEntityReferenceConfig(config: Record<string, unknown> | null | undefined): boolean {\n  const allowed = config?.[\"allowed_types\"];\n  return Array.isArray(allowed) && allowed.length > 0;\n}\n\n/** The platform kinds an entity-reference Field may name, lower-cased, in its own order. */\nexport function entityReferenceKindsOf(field: Pick<CoercibleField, \"config\">): string[] {\n  const allowed = field.config?.[\"allowed_types\"];\n  return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];\n}\n\nconst TRUE_WORDS = new Set([\"true\", \"yes\", \"y\", \"1\", \"✓\", \"x\", \"checked\", \"on\"]);\nconst FALSE_WORDS = new Set([\"false\", \"no\", \"n\", \"0\", \"\", \"unchecked\", \"off\"]);\n\n/** The Field's name in plain words — never its key on a screen. */\nexport function coercionLabel(field: CoercibleField): string {\n  const label = (field.label ?? \"\").trim();\n  if (label !== \"\") return label;\n  const name = (field.name ?? \"\").trim();\n  if (name !== \"\") return name;\n  return field.key\n    .replace(/[_-]+/g, \" \")\n    .replace(/\\b\\w/g, (c) => c.toUpperCase())\n    .trim();\n}\n\n/**\n * Turn ONE typed-in string into the value this Field holds — or say, in a\n * sentence a person can act on, why it is not one.\n *\n * An empty answer is `null` for every kind: a blank means \"no answer here\",\n * and clearing a value is a thing a person means to do. Whether the Field may\n * be blank is the store's question, asked with the Field's `required` and its\n * Rules, not this one's.\n */\n/**\n * The choices a public question was handed (`public_choices`: `{key, label}` in order), as the\n * options a typed answer is matched against — by its key or by its label, either case — and each\n * answering with its KEY, which is what the public picker sends. Null when the question carries none.\n */\nfunction optionsFromPublicChoices(field: CoercibleField): ChoiceOptions | null {\n  const offered = (field as { public_choices?: unknown }).public_choices;\n  if (!Array.isArray(offered) || offered.length === 0) return null;\n  const byWord = new Map<string, string>();\n  const labels: string[] = [];\n  for (const c of offered) {\n    const key = typeof (c as { key?: unknown })?.key === \"string\" ? (c as { key: string }).key : null;\n    if (!key) continue;\n    const words = (c as { label?: unknown }).label;\n    const choiceLabel = typeof words === \"string\" && words.trim() !== \"\" ? words.trim() : key;\n    byWord.set(key.toLowerCase(), key);\n    byWord.set(choiceLabel.toLowerCase(), key);\n    labels.push(choiceLabel);\n  }\n  return labels.length > 0 ? { byWord, labels } : null;\n}\n\nexport function coerceTypedAnswer(\n  field: CoercibleField,\n  raw: string,\n  opts: CoerceOptions = {},\n): Coerced {\n  const label = opts.label ?? coercionLabel(field);\n  const text = raw.trim();\n  const kind = fieldKindFor(field, opts.kernel);\n\n  if (text === \"\" && kind !== \"checkbox\") return { value: null };\n\n  // LANE 10 P4: the newer kinds a format makes more exact (`newerKinds.ts`) read their own way;\n  // everything else about them (a status's choices, a count's refusal) is their base kind's.\n  // NOTION-PROPS-3: a button holds no value — it is pressed, never typed or pasted into.\n  if (isButtonField(field)) return { refusal: `${label} is a button: it is pressed, and holds nothing to type.` };\n  const newer = newerKindOf(field);\n  if (newer === \"rating\") {\n    const max = ratingMaxOf(field);\n    const n = Number(text);\n    if (Number.isInteger(n) && n >= 0 && n <= max) return { value: n };\n    return { refusal: `${label} is a whole number of stars from 0 to ${max}, and “${raw}” is not one.` };\n  }\n  if (newer === \"duration\") {\n    const seconds = readTypedDuration(text);\n    if (seconds === null || seconds < 0) {\n      return { refusal: `${label} is a length of time, and “${raw}” is not one. Write it like 45 min, 1:30 or 1h 30m.` };\n    }\n    // The store's own ceiling (custom._kind_value_ok): ten years of 365.25 days.\n    if (seconds > 315_576_000) return { refusal: `${label} is longer than ten years, and a length of time here is at most ten years.` };\n    return { value: seconds };\n  }\n  if (newer === \"address\") return { value: addressFromText(text) };\n  if (newer === \"barcode\") return { value: text };\n\n  switch (kind) {\n    case \"checkbox\": {\n      const word = text.toLowerCase();\n      if (TRUE_WORDS.has(word)) return { value: true };\n      if (FALSE_WORDS.has(word)) return { value: word === \"\" ? null : false };\n      return {\n        refusal: `${label} is a tick box, so it takes yes or no — “${raw}” is neither. Write TRUE/FALSE, yes/no or 1/0.`,\n      };\n    }\n    case \"number\":\n    case \"currency\":\n    case \"percent\": {\n      // The ONE number reading (`typedNumber.ts`): \"$1,250.50\", \"(300)\", \"-$150\", \"12%\".\n      const read = readTypedNumber(text);\n      if (!read.ok) {\n        return {\n          refusal: `${label} holds a number, and “${raw}” is not one. Write digits, like 1,250.50 — a currency symbol, a percent sign, commas and a minus sign or brackets for a negative are all fine.`,\n        };\n      }\n      const foreign = foreignUnit(kind, text, label, raw, field.unit, opts.showsAsPercent === true);\n      if (foreign) return { refusal: foreign };\n      return { value: read.value };\n    }\n    case \"datetime\": {\n      // A DAY IS NOT AN INSTANT, AND TREATING IT AS ONE MOVES IT. `2026-09-01`\n      // parsed as an instant is midnight UTC, and a screen west of Greenwich\n      // then draws it as 31 August — measured on fifty pasted invoice dates,\n      // all one day early, systematically. A date Field holds a DAY.\n      const wantsTime = String(field.config?.[\"kind\"] ?? \"date\") === \"datetime\";\n      // 🚨 READ, NEVER GUESSED (merged-grid review 2026-09-26): `Date.parse` read \"10/3\" as\n      // 2001 and a half-typed year as a real one. `readTypedDate` reads the person's locale\n      // order and refuses a two-digit or missing year, a month 13, a 31 September.\n      const read = readTypedDate(text, { withTime: wantsTime, ...(opts.locale ? { locale: opts.locale } : {}) });\n      if (!read.ok) return { refusal: `${label} holds a date, and ${read.why}` };\n      return { value: wantsTime ? read.instant : read.day };\n    }\n    case \"json\": {\n      try {\n        return { value: JSON.parse(text) };\n      } catch {\n        return {\n          refusal: `${label} holds a document, and “${raw}” is not valid JSON. Write the whole object, braces and all.`,\n        };\n      }\n    }\n    case \"select\":\n    case \"multi_select\": {\n      // A LIST IS AN IDENTITY, AND A WORD IS HOW A PERSON NAMES ONE. The store\n      // keeps the option's id; the person types its label. Matching the two is\n      // the whole job, and it is case-insensitive because \"COMPLETED\" and\n      // \"Completed\" are one option.\n      // A PUBLIC QUESTION CARRIES ITS OWN CHOICES (lane HANDOVER, 2026-09-29). A stranger's page\n      // never reads the column's options, and the form and booking doors hand each choice question\n      // its choices as `public_choices` instead. Cedar Ridge's booking page sent the Visit type's\n      // key (\"follow_up\") and this answered \"its options have not been read yet\", so every booking\n      // that answered a choice was refused, on the page and again at the confirm route.\n      const options = opts.options ?? optionsFromPublicChoices(field);\n      if (!options) {\n        return {\n          refusal: `${label} is a list and its options have not been read yet, so nothing can be matched to “${raw}”. Try again once the column's choices have loaded.`,\n        };\n      }\n      const wanted = kind === \"multi_select\"\n        ? text.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0)\n        : [text];\n      if (wanted.length === 0) return { value: kind === \"multi_select\" ? [] : null };\n      const matched: string[] = [];\n      const unmatched: string[] = [];\n      for (const word of wanted) {\n        const hit = options.byWord.get(word.toLowerCase());\n        if (hit === undefined) unmatched.push(word);\n        else matched.push(hit);\n      }\n      if (unmatched.length > 0) {\n        const offered = options.labels.length > 0\n          ? options.labels.join(\", \")\n          : \"it has none yet\";\n        return {\n          refusal:\n            `${label} does not have ${unmatched.map((w) => `“${w}”`).join(\" or \")} among its options. ` +\n            `It offers: ${offered}.`,\n          unmatched,\n        };\n      }\n      return { value: kind === \"multi_select\" ? matched : matched[0] };\n    }\n    // AN IDENTITY IS NOT A WORD. Writing the word would put a string where an\n    // id belongs, and every filter, color and rollup built on that column\n    // would stop agreeing with it. So it is refused and the person is told\n    // which control does hold it.\n    //\n    // BUT AN ID IS AN IDENTITY (RELATION-TARGETS, 2026-09-23). A Person or File\n    // column holds the kernel Person / File record, and the store itself now\n    // turns the id a writer actually holds — a member's user id, an uploaded\n    // file's id, or the record's own id — into that record at every write door\n    // (`custom.relation_kernel_record`), refusing anything else in its own\n    // words (\"… names someone who is not a member of …\"). So an import column\n    // of member ids, or an agent handing over the file it just uploaded, is a\n    // value and not a refusal; the store, not this mirror, decides whether the\n    // id is somebody here.\n    case \"member\":\n      if (isUuidShape(text)) return { value: text.toLowerCase() };\n      return {\n        refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open it and pick them.`,\n      };\n    case \"attachment\":\n      if (isUuidShape(text)) return { value: text.toLowerCase() };\n      return {\n        refusal: `${label} holds a file, which cannot arrive as typed-in text. Open it and attach the file.`,\n      };\n    case \"relation\": {\n      // VISION-REACH G1: a record's id, or its NAME read through the index the caller asked the\n      // store for. A several-records column splits the cell the way an import splits any list.\n      const parts = field.multi ? splitListCell(text) : [text];\n      const ids: string[] = [];\n      const words: string[] = [];\n      const none: string[] = [];\n      const many: string[] = [];\n      for (const part of parts) {\n        if (isUuidShape(part)) {\n          if (!ids.includes(part.toLowerCase())) ids.push(part.toLowerCase());\n          continue;\n        }\n        if (!opts.relationNames) {\n          return {\n            refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`,\n          };\n        }\n        const hits = opts.relationNames.byName.get(relationNameKey(part)) ?? [];\n        if (hits.length === 0) none.push(part);\n        else if (hits.length > 1) many.push(part);\n        else if (!ids.includes(hits[0]!.id)) {\n          ids.push(hits[0]!.id);\n          words.push(hits[0]!.words);\n        }\n      }\n      if (none.length > 0 || many.length > 0) {\n        const table = opts.relationNames?.tableName ?? \"that table\";\n        const said = many.length > 0\n          ? `${many.map((w) => `“${w}”`).join(\", \")} names more than one record in ${table}.`\n          : `${table} has no record called ${none.map((w) => `“${w}”`).join(\" or \")}.`;\n        return { refusal: `${label}: ${said}`, unmatchedNames: none, ambiguousNames: many };\n      }\n      if (ids.length === 0) return { value: field.multi ? [] : null };\n      return { value: field.multi ? ids : ids[0], ...(words.length > 0 ? { words: words.join(\", \") } : {}) };\n    }\n    // SC-R / P12. An entity reference names a platform thing by kind and id.\n    // An import or an agent may hand over `note:<id>` — or a bare id when the\n    // column allows exactly one kind; the store then decides whether the thing\n    // is there and whether the writer may open it. Words are chosen, not typed.\n    case \"entity_reference\": {\n      const allowed = entityReferenceKindsOf(field);\n      const [maybeToken, maybeId] = text.includes(\":\") ? text.split(/:(.*)/s, 2) : [\"\", text];\n      const token = (maybeToken ?? \"\").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : \"\");\n      const id = (maybeId ?? \"\").trim();\n      if (token && isUuidShape(id)) {\n        const ref = { token, id: id.toLowerCase() };\n        return { value: field.multi ? [ref] : ref };\n      }\n      return {\n        refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`,\n      };\n    }\n    case \"formula\":\n    case \"lookup\":\n    case \"rollup\":\n      return {\n        refusal: `${label} is worked out by the system, so nothing can be written into it.`,\n      };\n    // A LINK AND AN EMAIL ARE READ, THE WAY AIRTABLE READS THEM (merged-grid review 2, A9).\n    // \"company.com\" is a link a person meant: it is kept as https://company.com. Words that\n    // cannot be one are refused NAMING THE KIND, WITH ONE EXAMPLE — never \"not written the way\n    // this field expects\". Any further format Rule on the column is still the store's to judge.\n    case \"url\": {\n      if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(text) || /^mailto:/i.test(text)) return { value: text };\n      if (!/\\s/.test(text) && /^[^./@\\s]+(\\.[^./\\s]+)+(\\/.*)?$/.test(text)) return { value: `https://${text}` };\n      return { refusal: `${label} holds a link, and “${raw}” is not one. A link looks like company.com or https://company.com/page.` };\n    }\n    case \"email\": {\n      if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(text)) return { value: text };\n      return { refusal: `${label} holds an email address, and “${raw}” is not one. An email looks like name@company.com.` };\n    }\n    default:\n      // text, long_text, phone — the string IS the value, and the format Rules on it are the\n      // store's to judge.\n      return { value: raw };\n  }\n}\n\n/** What a whole set of typed-in answers became. */\nexport interface CoercedAnswers {\n  /** Ready to send to the store. Only the keys that coerced cleanly. */\n  values: Record<string, unknown>;\n  /** Field key → the sentence saying why that answer was not written. */\n  refusals: Record<string, string>;\n  /** The field keys the caller sent that this table has no Field for. */\n  unknownKeys: string[];\n}\n\n/**\n * Every typed-in answer on one screen, coerced together — the shape the two\n * public doors and the import wizard actually need.\n *\n * Keys the table has no Field for are reported rather than dropped: the store\n * refuses an unknown key by name and a caller that quietly deleted it would\n * hide a real mismatch between a page and its table.\n */\nexport function coerceTypedAnswers(\n  fields: readonly CoercibleField[],\n  answers: Readonly<Record<string, unknown>>,\n  opts: {\n    kernel?: KernelTargets | undefined;\n    /** Choices per Field key, for the list columns. */\n    options?: ReadonlyMap<string, ChoiceOptions> | undefined;\n    /** Field key → the words that screen printed above the box. */\n    labels?: Readonly<Record<string, string>> | undefined;\n    /** Relation columns' targets, indexed by name, by field key (VISION-REACH G1). */\n    relationNames?: ReadonlyMap<string, RelationNameIndex> | undefined;\n  } = {},\n): CoercedAnswers {\n  const byKey = new Map(fields.map((f) => [f.key, f]));\n  const values: Record<string, unknown> = {};\n  const refusals: Record<string, string> = {};\n  const unknownKeys: string[] = [];\n\n  for (const [key, given] of Object.entries(answers)) {\n    const field = byKey.get(key);\n    if (!field) {\n      unknownKeys.push(key);\n      // Passed through untouched: it is the STORE's job to say it does not\n      // know this key, in its own words, naming it.\n      values[key] = given;\n      continue;\n    }\n    // ALREADY THE RIGHT SHAPE IS LEFT ALONE. A screen with real editors hands\n    // over a number, a boolean or an array already; only a STRING needs this.\n    if (typeof given !== \"string\") {\n      values[key] = given;\n      continue;\n    }\n    const opt: CoerceOptions = {};\n    const choices = opts.options?.get(key);\n    if (choices) opt.options = choices;\n    const named = opts.relationNames?.get(key);\n    if (named) opt.relationNames = named;\n    if (opts.kernel) opt.kernel = opts.kernel;\n    const label = opts.labels?.[key];\n    if (label) opt.label = label;\n\n    const answered = coerceTypedAnswer(field, given, opt);\n    if (\"refusal\" in answered) refusals[key] = answered.refusal;\n    else values[key] = answered.value;\n  }\n\n  return { values, refusals, unknownKeys };\n}\n","// src/forms/typedAnswers.ts — @ai-matrx/records/forms (moved whole from the website's\n// features/unified-data/typedAnswers.ts, 2026-10-08). WHAT A STRANGER TYPED, TURNED INTO\n// VALUES, ONCE, FOR EVERY PUBLIC DOOR.\n//\n// 🚨 WHY THIS FILE EXISTS. AGENT-BUILDS-2's closing walk, 2026-09-21: a\n// customer opened Ironclad Mobile Mechanic's public booking page, picked\n// 2:00 PM on Tuesday, typed Marcus Delgado, (415) 555-0178, a 2019 Honda CR-V,\n// pressed \"Book it\", and the page answered\n//\n//     \"Vehicle Year takes a number, and it was given a string\"\n//\n// and made no appointment. A browser input hands over a STRING; a Field takes a\n// number. The grid's paste and the CSV import each carried their own coercion —\n// good ones — and the two doors a STRANGER uses carried none, so a booking page\n// over ANY table with a number field was unbookable by anybody.\n//\n// THE COERCION ITSELF IS NOT HERE. It is `coerceTypedAnswers` in\n// `@ai-matrx/records`, the same body the grid's paste and the editors use, so\n// a number typed into a public form, pasted into a cell, imported from a CSV\n// or entered through a portal all mean the same thing. This file is the ONE\n// place the two route handlers agree on how to ASK it and how to answer when\n// it refuses.\n//\n// IT IS THE SERVER'S JOB AND NOT ONLY THE SCREEN'S. The public pages coerce\n// too, so a person is told at the box rather than after their slot is held —\n// but a route that trusted the browser to have done it would be one\n// hand-written POST away from the same 400. The screen is the courtesy; this\n// is the contract.\n//\n// IT DECIDES NOTHING ELSE. Required, the Rules, the cap, the rate window, the\n// quarantine Rule, every exposed key and every wall stay the store's, asked by\n// custom.form_submit and custom.booking_confirm. This turns strings into\n// values and stops.\n\nimport { coerceTypedAnswers } from \"../coerce\";\nimport type { Field } from \"../field\";\n\nexport interface TypedAnswers {\n  /** Ready for the door. */\n  values: Record<string, unknown>;\n  /**\n   * The sentences, in the order the page asks its questions, or null when\n   * every answer became a value. Each one names its Field in plain words.\n   */\n  refusal: string | null;\n  /** Field key → its sentence, so a screen can point at the question. */\n  byKey: Record<string, string>;\n}\n\n/**\n * Coerce what a public page collected against the Fields that page published.\n *\n * `fields` is exactly what `custom.form_public` / `custom.booking_public`\n * already hand back — the Field definitions of the questions being asked — so\n * nothing new is read and no door is opened to work this out.\n */\nexport function typedAnswersFor(\n  fields: Array<Record<string, unknown>> | null | undefined,\n  values: Record<string, unknown>,\n): TypedAnswers {\n  // A page with no resolved Fields coerces nothing rather than guessing. The\n  // door still judges the payload and says so in its own words — which is the\n  // behavior that existed before this file, not a new silence.\n  if (!Array.isArray(fields) || fields.length === 0) {\n    return { values, refusal: null, byKey: {} };\n  }\n\n  const coerced = coerceTypedAnswers(fields as unknown as Field[], values);\n  const keys = Object.keys(coerced.refusals);\n  if (keys.length === 0) return { values: coerced.values, refusal: null, byKey: {} };\n\n  // ONE SENTENCE, IN THE ORDER THE QUESTIONS WERE ASKED, because a person\n  // reading it is looking at a form and scanning down it.\n  const order = new Map((fields as Array<{ key?: string }>).map((f, at) => [String(f.key ?? \"\"), at]));\n  keys.sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));\n  return {\n    values: coerced.values,\n    refusal: keys.map((k) => coerced.refusals[k]).join(\" \"),\n    byKey: coerced.refusals,\n  };\n}\n"],"mappings":"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","names":["text","text","text","text","bad","day","text","text","text"]}