@ai-matrx/records 0.57.3 → 0.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2222,6 +2222,7 @@ __export(react_exports, {
2222
2222
  PERMISSION_LEVELS: () => PERMISSION_LEVELS,
2223
2223
  PER_VALUE_ACCESS_WORDS: () => PER_VALUE_ACCESS_WORDS,
2224
2224
  PIPELINE_RULE_KINDS: () => PIPELINE_RULE_KINDS,
2225
+ PORTAL_ACCENTS: () => PORTAL_ACCENTS,
2225
2226
  PORTAL_ARCHIVE_FILTERS: () => PORTAL_ARCHIVE_FILTERS,
2226
2227
  REFUSALS_WORTH_REPEATING: () => REFUSALS_WORTH_REPEATING,
2227
2228
  RELATION_BINDINGS: () => RELATION_BINDINGS,
@@ -2257,6 +2258,11 @@ __export(react_exports, {
2257
2258
  coercionLabel: () => coercionLabel,
2258
2259
  emptyArchiveLine: () => emptyArchiveLine,
2259
2260
  emptyPortalArchiveLine: () => emptyPortalArchiveLine,
2261
+ entityReferenceKinds: () => entityReferenceKinds,
2262
+ entityReferenceKindsOf: () => entityReferenceKindsOf,
2263
+ entityReferenceWords: () => entityReferenceWords,
2264
+ entityRefsOf: () => entityRefsOf,
2265
+ entityRefsToWrite: () => entityRefsToWrite,
2260
2266
  err: () => err,
2261
2267
  exampleSpec: () => exampleSpec,
2262
2268
  exportPageXlsx: () => exportPageXlsx,
@@ -2267,20 +2273,31 @@ __export(react_exports, {
2267
2273
  indexedDbStore: () => indexedDbStore,
2268
2274
  isArchiveLane: () => isArchiveLane,
2269
2275
  isDecided: () => isDecided,
2276
+ isEntityReferenceConfig: () => isEntityReferenceConfig,
2270
2277
  isOwnOp: () => isOwnOp,
2278
+ isPortalAccent: () => isPortalAccent,
2271
2279
  isPortalArchiveFilter: () => isPortalArchiveFilter,
2272
2280
  isRecordsErr: () => isRecordsErr,
2281
+ isRuleFilter: () => isRuleFilter,
2273
2282
  isTheChosen: () => isTheChosen,
2283
+ keptGroups: () => keptGroups,
2284
+ keptOutOfSight: () => keptOutOfSight,
2285
+ keptTableHref: () => keptTableHref,
2274
2286
  looksLikeAnId: () => looksLikeAnId,
2275
2287
  mayDrag: () => mayDrag,
2276
2288
  measureKey: () => measureKey,
2277
2289
  memoryStore: () => memoryStore,
2278
2290
  mintClientKey: () => mintClientKey,
2279
2291
  mintOpId: () => mintOpId,
2292
+ moveConsequence: () => moveConsequence,
2293
+ moveInOrder: () => moveInOrder,
2294
+ offeredAsContext: () => offeredAsContext,
2280
2295
  ok: () => ok,
2281
2296
  openCaptureQueue: () => openCaptureQueue,
2282
2297
  optionKey: () => optionKey,
2283
2298
  optionLabel: () => optionLabel,
2299
+ parseTableHome: () => parseTableHome,
2300
+ parseTableMoved: () => parseTableMoved,
2284
2301
  portalArchiveConsequence: () => portalArchiveConsequence,
2285
2302
  portalConfirmLine: () => portalConfirmLine,
2286
2303
  portalPath: () => portalPath,
@@ -2290,12 +2307,15 @@ __export(react_exports, {
2290
2307
  rememberedOpCount: () => rememberedOpCount,
2291
2308
  sameDocument: () => sameDocument,
2292
2309
  scopeChip: () => scopeChip,
2310
+ sheetSyncSpec: () => sheetSyncSpec,
2293
2311
  stableFilterKey: () => stableFilterKey,
2294
2312
  tableShapeChanged: () => tableShapeChanged,
2295
2313
  useEntityFieldMutation: () => useEntityFieldMutation,
2296
2314
  useEntityFieldRights: () => useEntityFieldRights,
2297
2315
  useEntityFields: () => useEntityFields,
2298
2316
  useEntityRecord: () => useEntityRecord,
2317
+ useEntityReferenceKinds: () => useEntityReferenceKinds,
2318
+ useEntityReferenceWords: () => useEntityReferenceWords,
2299
2319
  useEntityTable: () => useEntityTable,
2300
2320
  useFieldMutation: () => useFieldMutation,
2301
2321
  useFields: () => useFields,
@@ -2311,7 +2331,9 @@ __export(react_exports, {
2311
2331
  useRecordsClient: () => useRecordsClient,
2312
2332
  useTable: () => useTable,
2313
2333
  useTableArchive: () => useTableArchive,
2314
- useTables: () => useTables
2334
+ useTables: () => useTables,
2335
+ withRowCounts: () => withRowCounts,
2336
+ withTablePlacement: () => withTablePlacement
2315
2337
  });
2316
2338
  module.exports = __toCommonJS(react_exports);
2317
2339
 
@@ -2358,6 +2380,31 @@ function sameDocument(a, b) {
2358
2380
  return JSON.stringify(a) === JSON.stringify(b);
2359
2381
  }
2360
2382
 
2383
+ // src/aggregate.ts
2384
+ function isRuleFilter(filter) {
2385
+ if (!filter || typeof filter !== "object" || Array.isArray(filter)) return false;
2386
+ const node = filter;
2387
+ return typeof node.op === "string" && Array.isArray(node.args);
2388
+ }
2389
+ function measureKey(measure) {
2390
+ return measure.op === "count" ? "count" : `${measure.op}_${measure.key ?? ""}`;
2391
+ }
2392
+ function stableFilterKey(filter) {
2393
+ if (!filter) return "{}";
2394
+ if (isRuleFilter(filter)) return JSON.stringify(filter);
2395
+ const keys = Object.keys(filter).sort();
2396
+ if (keys.length === 0) return "{}";
2397
+ const parts = keys.map((key) => {
2398
+ const value = filter[key];
2399
+ if (value !== null && typeof value === "object") {
2400
+ const window2 = value;
2401
+ return `${JSON.stringify(key)}:{"from":${JSON.stringify(window2.from ?? null)},"to":${JSON.stringify(window2.to ?? null)}}`;
2402
+ }
2403
+ return `${JSON.stringify(key)}:${JSON.stringify(value ?? null)}`;
2404
+ });
2405
+ return `{${parts.join(",")}}`;
2406
+ }
2407
+
2361
2408
  // src/ops.ts
2362
2409
  var REMEMBER_MS = 3e4;
2363
2410
  var REMEMBER_MAX = 500;
@@ -2397,6 +2444,116 @@ function rememberedOpCount() {
2397
2444
  return ours.size;
2398
2445
  }
2399
2446
 
2447
+ // src/placement.ts
2448
+ function keptOutOfSight(placement) {
2449
+ return placement?.kept_by_the_app === true;
2450
+ }
2451
+ function offeredAsContext(placement) {
2452
+ return placement?.offered_as_context === true;
2453
+ }
2454
+ function withTablePlacement(tables, facts) {
2455
+ if (!facts) return [...tables];
2456
+ const byId = new Map(facts.map((f) => [f.table_id, f]));
2457
+ return tables.map((table) => {
2458
+ const fact = byId.get(table.id);
2459
+ if (!fact || fact.kept_by_the_app === void 0) return table;
2460
+ return {
2461
+ ...table,
2462
+ kept_by_the_app: fact.kept_by_the_app,
2463
+ kept_for: fact.kept_for ?? null,
2464
+ offered_as_context: fact.offered_as_context ?? false,
2465
+ keeper_group: fact.keeper_group ?? null,
2466
+ keeper_says: fact.keeper_says ?? null,
2467
+ used_in_kind: fact.used_in_kind ?? null,
2468
+ used_in_id: fact.used_in_id ?? null,
2469
+ used_in_table_id: fact.used_in_table_id ?? null
2470
+ };
2471
+ });
2472
+ }
2473
+ function keptGroups(tables) {
2474
+ const out = [];
2475
+ const at = /* @__PURE__ */ new Map();
2476
+ for (const table of tables) {
2477
+ if (!keptOutOfSight(table)) continue;
2478
+ const group = table.keeper_group?.trim() || "Kept by the app";
2479
+ let i = at.get(group);
2480
+ if (i === void 0) {
2481
+ i = out.length;
2482
+ at.set(group, i);
2483
+ out.push({ group, tables: [] });
2484
+ }
2485
+ out[i].tables.push(table);
2486
+ }
2487
+ return out;
2488
+ }
2489
+ function keptTableHref(fact, hrefs) {
2490
+ if (fact.used_in_kind === "scope" && fact.used_in_id) return hrefs.scope(fact.used_in_id);
2491
+ if (fact.used_in_kind === "table" && (fact.used_in_table_id || fact.used_in_id)) {
2492
+ return hrefs.table(fact.used_in_table_id || fact.used_in_id);
2493
+ }
2494
+ return hrefs.table(fact.table_id);
2495
+ }
2496
+ function countOf(value) {
2497
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
2498
+ }
2499
+ function wordsOf(value) {
2500
+ return typeof value === "string" && value.length > 0 ? value : null;
2501
+ }
2502
+ function parseTableHome(raw) {
2503
+ if (!raw || typeof raw !== "object") return null;
2504
+ const row = raw;
2505
+ const table = row.table;
2506
+ const org = row.organization;
2507
+ if (!table || !org || !wordsOf(table.id) || !wordsOf(org.id) || !wordsOf(org.name)) return null;
2508
+ const carries = row.carries ?? {};
2509
+ const destinations = Array.isArray(row.destinations) ? row.destinations : [];
2510
+ return {
2511
+ table: { id: String(table.id), name: wordsOf(table.name) ?? "This table", version: countOf(table.version) },
2512
+ organization: { id: String(org.id), name: String(org.name) },
2513
+ mayMove: row.may_move === true,
2514
+ whyNot: wordsOf(row.why_not),
2515
+ heldBy: Array.isArray(row.held_by) ? row.held_by.filter((s) => typeof s === "string") : [],
2516
+ destinations: destinations.filter((d) => !!d && typeof d === "object" && !!wordsOf(d.id)).map((d) => ({
2517
+ id: String(d.id),
2518
+ name: wordsOf(d.name) ?? "An organization",
2519
+ role: wordsOf(d.role) ?? "member",
2520
+ ok: d.ok === true,
2521
+ why: wordsOf(d.why)
2522
+ })),
2523
+ carries: {
2524
+ fields: countOf(carries.fields),
2525
+ records: countOf(carries.records),
2526
+ inTrash: countOf(carries.in_trash),
2527
+ choiceLists: countOf(carries.choice_lists),
2528
+ withIt: countOf(carries.with_it),
2529
+ tablesInside: countOf(carries.tables_inside),
2530
+ linksAcross: countOf(carries.links_across)
2531
+ }
2532
+ };
2533
+ }
2534
+ function parseTableMoved(raw) {
2535
+ const row = raw ?? {};
2536
+ const to = row.to ?? {};
2537
+ const from = row.from ?? {};
2538
+ const table = row.table ?? {};
2539
+ if (row.moved !== true || !wordsOf(to.id)) return null;
2540
+ return {
2541
+ to: { id: String(to.id), name: wordsOf(to.name) ?? "the new organization" },
2542
+ from: { id: wordsOf(from.id) ?? "", name: wordsOf(from.name) ?? "its old organization" },
2543
+ tableName: wordsOf(table.name) ?? "The table"
2544
+ };
2545
+ }
2546
+ function plural(n, one, many) {
2547
+ return `${n} ${n === 1 ? one : many}`;
2548
+ }
2549
+ function moveConsequence(home, to) {
2550
+ const c = home.carries;
2551
+ const parts = [plural(c.fields, "column", "columns"), plural(c.records, "row", "rows")];
2552
+ if (c.inTrash > 0) parts.push(`${c.inTrash} in the trash`);
2553
+ const extra = c.choiceLists + c.withIt;
2554
+ return `${home.table.name} moves to ${to.name} with its ${parts.join(", ")}` + (c.tablesInside > 0 ? `, the ${plural(c.tablesInside, "table", "tables")} that live${c.tablesInside === 1 ? "s" : ""} inside it or its rows (with their own rows)` : "") + (extra > 0 ? `, and ${plural(extra, "thing", "things")} built only on it (choice lists, rules, dashboards, templates)` : "") + `, its comments, forms, saved views and history. ` + (c.linksAcross > 0 ? `${plural(c.linksAcross, "link", "links")} to rows that stay in ${home.organization.name} keep${c.linksAcross === 1 ? "s" : ""} pointing there, across the two organizations. ` : "") + `People who see it only because they are in ${home.organization.name} stop seeing it; people it was shared with by name keep it. You can move it back from the same place.`;
2555
+ }
2556
+
2400
2557
  // src/level.ts
2401
2558
  var PERMISSION_LEVELS = [
2402
2559
  "viewer",
@@ -2513,6 +2670,51 @@ function recordChangeTrigger(tableId, options = {}) {
2513
2670
  ...options.changedFields && options.changedFields.length > 0 ? { changed_fields: options.changedFields } : {}
2514
2671
  };
2515
2672
  }
2673
+ function sheetSyncSpec(args) {
2674
+ const [header = [], ...body] = args.cells;
2675
+ const columns = header.map((c, i) => {
2676
+ const name = c == null ? "" : String(c).trim();
2677
+ return name === "" ? `Column ${i + 1}` : name;
2678
+ });
2679
+ if (columns.length === 0) {
2680
+ throw new Error("The sheet has no header row, so there are no columns to sync. Nothing was sent.");
2681
+ }
2682
+ const seenCol = /* @__PURE__ */ new Set();
2683
+ for (const c of columns) {
2684
+ if (seenCol.has(c)) throw new Error(`The sheet has two columns named "${c}". Rename one and sync again.`);
2685
+ seenCol.add(c);
2686
+ }
2687
+ const refIdx = args.refColumn == null ? -1 : columns.indexOf(args.refColumn);
2688
+ if (args.refColumn != null && refIdx < 0) {
2689
+ throw new Error(`The id column "${args.refColumn}" is not in the sheet's header. Nothing was sent.`);
2690
+ }
2691
+ const seen = /* @__PURE__ */ new Set();
2692
+ const rows = [];
2693
+ body.forEach((cells, i) => {
2694
+ if (cells.every((v) => v == null || String(v).trim() === "")) return;
2695
+ const ref = refIdx >= 0 ? String(cells[refIdx] ?? "").trim() : `row-${i + 2}`;
2696
+ if (ref === "") throw new Error(`Sheet row ${i + 2} has no value in the id column "${args.refColumn}".`);
2697
+ if (seen.has(ref)) throw new Error(`Two sheet rows share the id "${ref}"; each row needs its own.`);
2698
+ seen.add(ref);
2699
+ const values = {};
2700
+ columns.forEach((c, j) => {
2701
+ const v = cells[j];
2702
+ values[c] = v === void 0 || v === "" ? null : v;
2703
+ });
2704
+ rows.push({ ref, values });
2705
+ });
2706
+ return { source: args.source, table_name: args.tableName, columns, rows };
2707
+ }
2708
+ function withRowCounts(tables, counts) {
2709
+ if (!counts) return [...tables];
2710
+ const byId = new Map(counts.map((c) => [c.table_id, Number(c.visible_rows)]));
2711
+ return tables.map((t) => byId.has(t.id) ? { ...t, row_count: byId.get(t.id) } : t);
2712
+ }
2713
+ function moveInOrder(order, moved, toIndex) {
2714
+ const rest = order.filter((id) => id !== moved);
2715
+ const at = Math.max(0, Math.min(Math.trunc(toIndex), rest.length));
2716
+ return [...rest.slice(0, at), moved, ...rest.slice(at)];
2717
+ }
2516
2718
 
2517
2719
  // src/store.generated.ts
2518
2720
  var ACTOR_VOCABULARY = ["user", "agent", "system"];
@@ -3188,6 +3390,7 @@ var STORE_DOORS = [
3188
3390
  { name: "table_work_kind", args: "p_organization_id uuid, p_table_id uuid", returns: "text", security: "invoker" },
3189
3391
  { name: "tables_at_home", args: "p_organization_id uuid, p_home_ids uuid[]", returns: "TABLE(table_id uuid, home_record_id uuid, kind text)", security: "definer" },
3190
3392
  { name: "tables_described_without_asking", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
3393
+ { name: "tables_i_can_open", args: "", returns: "TABLE(table_id uuid, table_name text, organization_id uuid, organization_name text, member boolean, visibility text, updated_at timestamp with time zone)", security: "definer" },
3191
3394
  { name: "tables_shared_with_me", args: "", returns: "TABLE(table_id uuid, organization_id uuid, organization text, table_name text, level text, level_label text, shared_at timestamp with time zone, opens boolean, say text)", security: "definer" },
3192
3395
  { name: "trg_associations_bump_visibility", args: "", returns: "trigger", security: "definer" },
3193
3396
  { name: "undeclared_keys", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "text[]", security: "invoker" },
@@ -3212,6 +3415,7 @@ var STORE_DOORS = [
3212
3415
  { name: "visible_predicate_sql", args: "p_user uuid, p_organization_id uuid, p_table_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level, p_alias text DEFAULT 'r'::text", returns: "text", security: "definer" },
3213
3416
  { name: "visible_record_ids", args: "p_user_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level", returns: "TABLE(id uuid)", security: "definer" },
3214
3417
  { name: "visible_set", args: "p_user uuid, p_organization_id uuid, p_table_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level, OUT o_all_visible boolean, OUT o_true_visibility platform.visibility[], OUT o_granted_all uuid[], OUT o_granted_visible uuid[], OUT o_carried_visible uuid[], OUT o_ladder_calls integer, OUT o_fallback boolean, OUT o_note text", returns: "record", security: "definer" },
3418
+ { name: "where_id_opens", args: "p_id uuid", returns: "jsonb", security: "definer" },
3215
3419
  { name: "widget_kernel_id", args: "", returns: "uuid", security: "invoker" },
3216
3420
  { name: "withheld_marker", args: "p_mask jsonb, p_key text", returns: "jsonb", security: "invoker" },
3217
3421
  { name: "withheld_sentence", args: "p_mask jsonb, p_key text", returns: "text", security: "invoker" },
@@ -3318,7 +3522,7 @@ var STORE_REFUSAL_CODES = [
3318
3522
  { code: "23505", raised_by: "_share_write_person,_unique_rule_holds,doc_sign,doc_signature_write,entity_field_declare,field_declare,home_add,inbound_declare,io_proposal_accept,relation_carry,relation_own,sign_request_cancel,sign_request_create,table_share_outside_resend,work_approval_decide,work_slot_hold" },
3319
3523
  { code: "23514", raised_by: "_checklist_instantiate,_checklist_step_guard,_containment_guard,_dated_values_guard,_derived_fields,_display_format_check,_display_spec_for,_entity_custom_fields_guard,_field_class_guard,_field_document_for,_field_shape_guard,_field_type_parity_guard,_merge_field_shape_guard,_merge_field_temporal_guard,_promoted_field_cap_guard,_record_rule_uses,_relation_halves_agree,_relation_kernel_targets,_resolve_choice_words,_rule_shape_guard,_rule_topology_guard,_store_relation_edge_names_its_field,_table_columns_word_guard,_table_shape_guard,_undeclared_key_guard,_value_envelope,_with_display,_work_shape_guard,_workdoors_approval_guard,action_run,assert_columns_are_defined,assert_entity_is_organization_scoped,booking_declare,capture_publish,capture_sheet_declare,checklist_declare,checklist_step_complete,doc_sign,doc_signature_write,doc_template_save,entity_field_declare,entity_field_retire,entity_field_update,entity_records_find,entity_table,field_declare,field_retire,field_update,home_add,io_comment_write,io_import_rows,migrate_merge,migrate_purge_hard,portal_archive,portal_restore,record_write_graph,relation_carry,relation_halves_repair,relation_own,sign_request_create,sign_request_sign,table_declare,table_from_example,validate_value_envelope,validate_values,work_set_state,work_slots_declare,work_take_assignment,work_template_instantiate" },
3320
3524
  { code: "40001", raised_by: "has_visibility_at" },
3321
- { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,agent_table_claim,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,doc_sign,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_purge_hard,migrate_purge_hard_request,migrate_reclass,my_levels,organization_clear,portal_declare,portal_invitation,portal_invite,portal_invite_accept,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,read_records_archived,read_records_by_ids,read_records_matching,record_table,record_write_graph,relation_halves_disagreements,relation_halves_repair,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_webhook_declare,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
3525
+ { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,agent_table_claim,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,doc_sign,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_purge_hard,migrate_purge_hard_request,migrate_reclass,my_levels,organization_clear,organization_contents,portal_declare,portal_invitation,portal_invite,portal_invite_accept,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,read_records_archived,read_records_by_ids,read_records_matching,record_table,record_write_graph,relation_halves_disagreements,relation_halves_repair,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_webhook_declare,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
3322
3526
  { code: "42703", raised_by: "entity_table,portal_declare" },
3323
3527
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
3324
3528
  { code: "54000", raised_by: "action_run,hub_changed_by,table_decorate,table_from_example,table_webhook_declare" },
@@ -4358,6 +4562,8 @@ var ENTITY_TOKENS = [
4358
4562
 
4359
4563
  // src/core/doors.ts
4360
4564
  var OWED_BY = {
4565
+ table_home: "SC-1' \u2014 matrx-frontend migrations/campaign/sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql (and its grant file), applied by the chair",
4566
+ table_move: "SC-1' / SC-1-TAILS \u2014 sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql, then sc1t_a_table_moves_with_the_tables_inside_it.sql, applied by the chair",
4361
4567
  metadata_search: "W5-SRV / W5-AGENT \u2014 the agent surface's eight verbs",
4362
4568
  record_aggregate: "W5-SRV / W5-AGENT \u2014 `record_aggregate` is one of the eight verbs",
4363
4569
  field_propose: "W5-AGENT \u2014 AGT-4/AGT-8, the proposal and its approvers",
@@ -4379,6 +4585,20 @@ var OWED_BY = {
4379
4585
  table_webhook_archive: "GRID-PRIMITIVES G4 \u2014 gridprim_every_row_change_reaches_the_webhook.sql (chair's production apply)",
4380
4586
  table_from_example: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
4381
4587
  autonumber_backfill: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
4588
+ table_list_everywhere: "GRID-PRIMITIVES G9 \u2014 gridprim_every_picker_lists_both_stores.sql (chair's production apply)",
4589
+ table_sync: "GRID-PRIMITIVES G10 \u2014 gridprim_a_synced_sheet_is_one_table_that_refreshes.sql (chair's production apply)",
4590
+ scope_table_provision: "GRID-PRIMITIVES G11 \u2014 gridprim_a_scope_provisions_its_table_in_the_store.sql (chair's production apply)",
4591
+ table_row_counts: "GRID-PRIMITIVES G12 \u2014 gridprim_every_table_counts_its_rows_and_every_row_shows_its_header.sql + its grant file (chair's production apply)",
4592
+ view_record_order_set: "GRID-PRIMITIVES G13 \u2014 gridprim_a_view_keeps_the_order_a_person_dragged.sql + its grant file (chair's production apply)",
4593
+ table_share_outside_grant: "GRID-PRIMITIVES G14 \u2014 gridprim_a_known_outsider_is_granted_at_once.sql + gridprim_a_signed_in_person_may_share_with_a_known_outsider.sql (chair's production apply)",
4594
+ read_records_in_view_order: "GRID-PRIMITIVES G13 \u2014 gridprim_a_view_keeps_the_order_a_person_dragged.sql + its grant file (chair's production apply)",
4595
+ rule_members_visible: "S2-PRIME FILTER-GROUPS \u2014 filtergroups_a_views_nested_question_is_one_where_clause.sql + its grant file (chair's production apply)",
4596
+ record_headers: "GRID-PRIMITIVES G12 \u2014 gridprim_every_table_counts_its_rows_and_every_row_shows_its_header.sql + its grant file (chair's production apply)",
4597
+ pipeline_move_many: "UI-CHAMPIONS S4 \u2014 uichamp_s4_a_selection_moves_and_decides_in_one_call.sql + its grant file (chair's production apply)",
4598
+ work_decide_many: "UI-CHAMPIONS S4 \u2014 uichamp_s4_a_selection_moves_and_decides_in_one_call.sql + its grant file (chair's production apply)",
4599
+ portal_form: "UI-CHAMPIONS S6 \u2014 uichamp_s6_a_portal_carries_its_look_and_its_forms.sql + uichamp_s6_a_signed_in_person_may_open_a_portals_look_and_forms.sql (chair's production apply)",
4600
+ portal_form_submit: "UI-CHAMPIONS S6 \u2014 uichamp_s6_a_portal_carries_its_look_and_its_forms.sql + uichamp_s6_a_signed_in_person_may_open_a_portals_look_and_forms.sql (chair's production apply)",
4601
+ form_preview_asks: "FORMS-FIX-1 \u2014 formsfix1_the_owners_preview_asks_the_store_the_same_question.sql + its grant file (chair's production apply)",
4382
4602
  rule_declare: "W1-RULE \u2014 `custom.record_write` writes `data_class = 'record'`, so the ONE write door cannot make a Rule record; declaring a Rule (and therefore a DOOR-18 subscription) needs a door of its own"
4383
4603
  };
4384
4604
  var TRUST_SCHEMA = "iam";
@@ -4582,6 +4802,8 @@ var DOORS = {
4582
4802
  pipelineBoard: "pipeline_board",
4583
4803
  pipelineTransitionRefusal: "pipeline_transition_refusal",
4584
4804
  pipelineMove: "pipeline_move",
4805
+ /** Lane S4: many cards, one call, a verdict and a version per card. */
4806
+ pipelineMoveMany: "pipeline_move_many",
4585
4807
  tableStageField: "table_stage_field",
4586
4808
  // STAGE-RULES. A gate can refuse or it can hand the change to the approvals queue; the
4587
4809
  // closed list is the store's, the knob is the organization's, and what is WAITING on a
@@ -4592,6 +4814,7 @@ var DOORS = {
4592
4814
  pipelinePending: "pipeline_pending",
4593
4815
  pipelineGatePreview: "pipeline_gate_preview",
4594
4816
  ruleMembers: "rule_members",
4817
+ ruleMembersVisible: "rule_members_visible",
4595
4818
  ruleMembership: "rule_membership",
4596
4819
  ruleRun: "rule_run",
4597
4820
  ruleEval: "rule_eval",
@@ -4606,6 +4829,12 @@ var DOORS = {
4606
4829
  // are server-lane doors and a browser never calls them.
4607
4830
  forms: "forms",
4608
4831
  formDeclare: "form_declare",
4832
+ /**
4833
+ * WHICH QUESTIONS THE OWNER'S PREVIEW ASKS NEXT (lane FORMS-FIX-1). The same store answer a
4834
+ * stranger's form gets (`custom._form_questions_asked` → `custom.rule_eval`), behind the
4835
+ * organization wall and the caller's own rung on the Table — never `rule_eval` from a browser.
4836
+ */
4837
+ formPreviewAsks: "form_preview_asks",
4609
4838
  // PRODUCTS row 14 — "let clients book a 30-minute consult". A booking page IS an
4610
4839
  // `anon_form`, so it is opened and closed by `anonPublish` like any other; this is
4611
4840
  // the owner's list of them with the numbers that make it worth opening. The
@@ -4660,11 +4889,22 @@ var DOORS = {
4660
4889
  // ask, under the same wall and visibility gate as `portals` itself.
4661
4890
  portalTables: "portal_tables",
4662
4891
  tableFacts: "table_facts",
4892
+ // WHERE A TABLE LIVES, AND MOVING IT (SC-1' / SC-1-TAILS). `table_home` answers only when the
4893
+ // caller may open the Table (custom.where_id_opens) and names its organization FROM THE TABLE;
4894
+ // `table_move` moves it into another organization the caller belongs to, with every Table
4895
+ // inside it, or refuses with one sentence.
4896
+ tableHome: "table_home",
4897
+ tableMove: "table_move",
4663
4898
  portalCard: "portal_card",
4664
4899
  portalDeclare: "portal_declare",
4665
4900
  portalInvite: "portal_invite",
4666
4901
  portalRevoke: "portal_revoke",
4667
4902
  portalPreview: "portal_preview",
4903
+ // S6 — the CLIENT's two form doors. They are store doors (schema `custom`), called by a
4904
+ // signed-in portal principal, and each refuses anybody who is not a live principal of that
4905
+ // live portal — so, unlike the sign-in pair above, they belong in this table.
4906
+ portalForm: "portal_form",
4907
+ portalFormSubmit: "portal_form_submit",
4668
4908
  // ORG-CLEANUP, 2026-09-22 — A PORTAL IS ARCHIVED, NEVER DELETED.
4669
4909
  // `portals` keeps its signature and HIDES archived portals (the law's default
4670
4910
  // is to hide); `list_portals(org, 'active'|'archived'|'all')` is the
@@ -4860,6 +5100,8 @@ var DOORS = {
4860
5100
  workApprovalRequest: "work_approval_request",
4861
5101
  workApprovalMayDecide: "work_approval_may_decide",
4862
5102
  workApprovalDecide: "work_approval_decide",
5103
+ /** Lane S4: many inbox decisions, one call, a verdict per item. */
5104
+ workDecideMany: "work_decide_many",
4863
5105
  workApprovalRead: "work_approval_read",
4864
5106
  // PRODUCTS row 13 — checklists and SOP runs.
4865
5107
  checklistRefusal: "checklist_refusal",
@@ -4885,6 +5127,14 @@ var DOORS = {
4885
5127
  tableFromExample: "table_from_example",
4886
5128
  autonumberBackfill: "autonumber_backfill",
4887
5129
  ioExport: "io_export",
5130
+ tableListEverywhere: "table_list_everywhere",
5131
+ tableSync: "table_sync",
5132
+ scopeTableProvision: "scope_table_provision",
5133
+ tableRowCounts: "table_row_counts",
5134
+ recordHeaders: "record_headers",
5135
+ viewRecordOrderSet: "view_record_order_set",
5136
+ readRecordsInViewOrder: "read_records_in_view_order",
5137
+ tableShareOutsideGrant: "table_share_outside_grant",
4888
5138
  // Declared here BECAUSE they do not exist yet: naming them is what makes the
4889
5139
  // absence visible to `pnpm check:store-registry` and to the suite.
4890
5140
  metadataSearch: "metadata_search",
@@ -5288,6 +5538,40 @@ function createRecordsClient(config) {
5288
5538
  if (!doorExists(DOORS.tableFacts)) return ok(null);
5289
5539
  return callDoor(DOORS.tableFacts, { p_organization_id: org }, "tableFacts");
5290
5540
  },
5541
+ async tableHome({ table_id }) {
5542
+ const answered = await callDoor(DOORS.tableHome, { p_table_id: table_id }, "tableHome");
5543
+ if (!answered.ok) return answered;
5544
+ if (answered.data === null || answered.data === void 0) return ok(null);
5545
+ const home = parseTableHome(answered.data);
5546
+ if (!home) {
5547
+ const refusal = {
5548
+ code: "not_supported",
5549
+ message: "The record store answered where this table lives in a shape this client cannot read, so nothing is claimed about it.",
5550
+ hint: "Regenerate @ai-matrx/records against the store (pnpm records:generate) \u2014 the door and this client disagree."
5551
+ };
5552
+ scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "tableHome" });
5553
+ return err(refusal);
5554
+ }
5555
+ return ok(home);
5556
+ },
5557
+ async tableMove({ table_id, to_organization_id, expected_version }) {
5558
+ const answered = await callDoor(
5559
+ DOORS.tableMove,
5560
+ { p_table_id: table_id, p_to_organization_id: to_organization_id, p_expected_version: expected_version ?? null },
5561
+ "tableMove"
5562
+ );
5563
+ if (!answered.ok) return answered;
5564
+ const moved = parseTableMoved(answered.data);
5565
+ if (!moved) {
5566
+ const refusal = {
5567
+ code: "not_supported",
5568
+ message: "The record store answered the move in a shape this client cannot read. Look where the table lives before trying again."
5569
+ };
5570
+ scream({ code: refusal.code, message: refusal.message, where: "tableMove" });
5571
+ return err(refusal);
5572
+ }
5573
+ return ok(moved);
5574
+ },
5291
5575
  async tableArchived({ table_id }) {
5292
5576
  const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
5293
5577
  if (!kernel.ok) return err(kernel.error);
@@ -5664,10 +5948,11 @@ function createRecordsClient(config) {
5664
5948
  "pipelineRead"
5665
5949
  );
5666
5950
  },
5667
- async pipelineBoard({ table_id, measure }) {
5951
+ async pipelineBoard({ table_id, measure, filter }) {
5952
+ const asked = filter && stableFilterKey(filter) !== "{}";
5668
5953
  return callDoor(
5669
5954
  DOORS.pipelineBoard,
5670
- { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
5955
+ asked ? { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null, p_filter: filter } : { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
5671
5956
  "pipelineBoard"
5672
5957
  );
5673
5958
  },
@@ -5691,6 +5976,13 @@ function createRecordsClient(config) {
5691
5976
  "pipelineMove"
5692
5977
  );
5693
5978
  },
5979
+ async pipelineMoveMany({ moves }) {
5980
+ return callDoor(
5981
+ DOORS.pipelineMoveMany,
5982
+ { p_organization_id: org, p_moves: moves },
5983
+ "pipelineMoveMany"
5984
+ );
5985
+ },
5694
5986
  async pipelineDeclare({ table_id, spec }) {
5695
5987
  return callDoor(
5696
5988
  DOORS.pipelineDeclare,
@@ -5740,6 +6032,18 @@ function createRecordsClient(config) {
5740
6032
  "pipelineGatePreview"
5741
6033
  );
5742
6034
  },
6035
+ async ruleMembersVisible({ rule_id, limit = 200, offset = 0 }) {
6036
+ const door = await callDoor(
6037
+ DOORS.ruleMembersVisible,
6038
+ { p_organization_id: org, p_rule_id: rule_id, p_limit: limit, p_offset: offset },
6039
+ "ruleMembersVisible"
6040
+ );
6041
+ if (!door.ok) return err(door.error);
6042
+ return ok((door.data ?? []).map((row) => {
6043
+ const { document, hidden } = unfold(row.document);
6044
+ return { id: row.id, document, level: row.level, hidden };
6045
+ }));
6046
+ },
5743
6047
  async ruleMembers({ rule_id }) {
5744
6048
  return callDoor(
5745
6049
  DOORS.ruleMembers,
@@ -5792,6 +6096,13 @@ function createRecordsClient(config) {
5792
6096
  "bookingDeclare"
5793
6097
  );
5794
6098
  },
6099
+ async formPreviewAsks({ table_id, questions, values }) {
6100
+ return callDoor(
6101
+ DOORS.formPreviewAsks,
6102
+ { p_organization_id: org, p_table_id: table_id, p_questions: questions, p_values: values },
6103
+ "formPreviewAsks"
6104
+ );
6105
+ },
5795
6106
  async formDeclare(args) {
5796
6107
  return callDoor(
5797
6108
  DOORS.formDeclare,
@@ -5967,11 +6278,33 @@ function createRecordsClient(config) {
5967
6278
  p_tables: args.tables,
5968
6279
  p_portal_id: args.portal_id ?? null,
5969
6280
  p_slug: args.slug ?? null,
5970
- p_sign_in_method: args.sign_in_method ?? "magic_link"
6281
+ p_sign_in_method: args.sign_in_method ?? "magic_link",
6282
+ // S6: sent only when given, so a caller that says nothing about the look keeps it.
6283
+ ...args.config !== void 0 ? { p_config: args.config } : {}
5971
6284
  },
5972
6285
  "portalDeclare"
5973
6286
  );
5974
6287
  },
6288
+ async portalForm({ portal_id, form_id }) {
6289
+ return callDoor(
6290
+ DOORS.portalForm,
6291
+ { p_organization_id: org, p_portal_id: portal_id, p_form_id: form_id },
6292
+ "portalForm"
6293
+ );
6294
+ },
6295
+ async portalFormSubmit({ portal_id, form_id, answers, client_key }) {
6296
+ return callDoor(
6297
+ DOORS.portalFormSubmit,
6298
+ {
6299
+ p_organization_id: org,
6300
+ p_portal_id: portal_id,
6301
+ p_form_id: form_id,
6302
+ p_payload: answers,
6303
+ p_client_key: client_key ?? null
6304
+ },
6305
+ "portalFormSubmit"
6306
+ );
6307
+ },
5975
6308
  async portalInvite(args) {
5976
6309
  return callDoor(
5977
6310
  DOORS.portalInvite,
@@ -6170,10 +6503,17 @@ function createRecordsClient(config) {
6170
6503
  "dashboardDeclare"
6171
6504
  );
6172
6505
  },
6173
- async dashboardRun({ dashboard_id, filter }) {
6506
+ async dashboardRun({ dashboard_id, filter, compare, grain }) {
6174
6507
  return callDoor(
6175
6508
  DOORS.dashboardRun,
6176
- { p_organization_id: org, p_dashboard_id: dashboard_id, p_filter: filter ?? {} },
6509
+ {
6510
+ p_organization_id: org,
6511
+ p_dashboard_id: dashboard_id,
6512
+ p_filter: filter ?? {},
6513
+ // Sent only when asked, so a caller that never compares calls exactly what it did.
6514
+ ...compare ? { p_compare: compare } : {},
6515
+ ...grain ? { p_grain: grain } : {}
6516
+ },
6177
6517
  "dashboardRun"
6178
6518
  );
6179
6519
  },
@@ -6198,7 +6538,7 @@ function createRecordsClient(config) {
6198
6538
  "dashboardDelete"
6199
6539
  );
6200
6540
  },
6201
- async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit }) {
6541
+ async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
6202
6542
  return callDoor(
6203
6543
  DOORS.recordAggregate,
6204
6544
  {
@@ -6208,7 +6548,8 @@ function createRecordsClient(config) {
6208
6548
  p_measures: measures ?? [],
6209
6549
  p_bucket: bucket ?? null,
6210
6550
  p_filter: filter ?? {},
6211
- p_limit: limit ?? 200
6551
+ p_limit: limit ?? 200,
6552
+ ...compare ? { p_compare: compare } : {}
6212
6553
  },
6213
6554
  "recordAggregate"
6214
6555
  );
@@ -6936,6 +7277,13 @@ function createRecordsClient(config) {
6936
7277
  "workApprovalDecide"
6937
7278
  );
6938
7279
  },
7280
+ async workDecideMany({ decisions }) {
7281
+ return callDoor(
7282
+ DOORS.workDecideMany,
7283
+ { p_organization_id: org, p_decisions: decisions },
7284
+ "workDecideMany"
7285
+ );
7286
+ },
6939
7287
  async workApprovalRead({ approval_id }) {
6940
7288
  return callDoor(
6941
7289
  DOORS.workApprovalRead,
@@ -7139,6 +7487,69 @@ function createRecordsClient(config) {
7139
7487
  "tableFromExample"
7140
7488
  );
7141
7489
  },
7490
+ async tableListEverywhere() {
7491
+ return callDoor(
7492
+ DOORS.tableListEverywhere,
7493
+ { p_organization_id: org },
7494
+ "tableListEverywhere"
7495
+ );
7496
+ },
7497
+ async tableSync({ home_id, spec }) {
7498
+ return callDoor(
7499
+ DOORS.tableSync,
7500
+ { p_organization_id: org, p_home_id: home_id, p_spec: spec },
7501
+ "tableSync"
7502
+ );
7503
+ },
7504
+ async scopeTableProvision({ home_id, item_id, scope_id }) {
7505
+ return callDoor(
7506
+ DOORS.scopeTableProvision,
7507
+ { p_organization_id: org, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
7508
+ "scopeTableProvision"
7509
+ );
7510
+ },
7511
+ async tableRowCounts({ table_ids }) {
7512
+ return callDoor(
7513
+ DOORS.tableRowCounts,
7514
+ { p_organization_id: org, p_table_ids: table_ids },
7515
+ "tableRowCounts"
7516
+ );
7517
+ },
7518
+ async recordHeaders({ ids }) {
7519
+ return callDoor(
7520
+ DOORS.recordHeaders,
7521
+ { p_organization_id: org, p_ids: ids },
7522
+ "recordHeaders"
7523
+ );
7524
+ },
7525
+ async tableShareOutsideGrant({ table_id, person, level }) {
7526
+ return callDoor(
7527
+ DOORS.tableShareOutsideGrant,
7528
+ { p_organization_id: org, p_table_id: table_id, p_person: person, p_level: level ?? "viewer" },
7529
+ "tableShareOutsideGrant"
7530
+ );
7531
+ },
7532
+ async viewRecordOrderSet({ view_id, record_ids }) {
7533
+ return callDoor(
7534
+ DOORS.viewRecordOrderSet,
7535
+ { p_organization_id: org, p_view_id: view_id, p_record_ids: record_ids },
7536
+ "viewRecordOrderSet"
7537
+ );
7538
+ },
7539
+ async readInViewOrder({ view_id, limit, offset }) {
7540
+ const door = await callDoor(
7541
+ DOORS.readRecordsInViewOrder,
7542
+ { p_organization_id: org, p_view_id: view_id, p_by_id: false, p_limit: limit ?? 200, p_offset: offset ?? 0 },
7543
+ "readInViewOrder"
7544
+ );
7545
+ if (!door.ok) return err(door.error);
7546
+ return ok(
7547
+ (door.data ?? []).map((row) => {
7548
+ const { document, hidden } = unfold(row.document);
7549
+ return { id: row.id, document, level: row.level, hidden, position: row.position ?? null };
7550
+ })
7551
+ );
7552
+ },
7142
7553
  async autonumberBackfill({ field_id }) {
7143
7554
  return callDoor(
7144
7555
  DOORS.autonumberBackfill,
@@ -7233,27 +7644,6 @@ function useRecordsActor() {
7233
7644
 
7234
7645
  // src/react/hooks.ts
7235
7646
  var import_react2 = require("react");
7236
-
7237
- // src/aggregate.ts
7238
- function measureKey(measure) {
7239
- return measure.op === "count" ? "count" : `${measure.op}_${measure.key ?? ""}`;
7240
- }
7241
- function stableFilterKey(filter) {
7242
- if (!filter) return "{}";
7243
- const keys = Object.keys(filter).sort();
7244
- if (keys.length === 0) return "{}";
7245
- const parts = keys.map((key) => {
7246
- const value = filter[key];
7247
- if (value !== null && typeof value === "object") {
7248
- const window2 = value;
7249
- return `${JSON.stringify(key)}:{"from":${JSON.stringify(window2.from ?? null)},"to":${JSON.stringify(window2.to ?? null)}}`;
7250
- }
7251
- return `${JSON.stringify(key)}:${JSON.stringify(value ?? null)}`;
7252
- });
7253
- return `{${parts.join(",")}}`;
7254
- }
7255
-
7256
- // src/react/hooks.ts
7257
7647
  function useAsync(run, deps) {
7258
7648
  const client = useRecordsClient();
7259
7649
  const [state, setState] = (0, import_react2.useState)(
@@ -7733,6 +8123,88 @@ function useMergeField(key, state) {
7733
8123
  return resolution;
7734
8124
  }
7735
8125
 
8126
+ // src/react/entity-reference.ts
8127
+ var import_react3 = require("react");
8128
+
8129
+ // src/relation.ts
8130
+ var ENTITY_REF_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8131
+ function isRefLike(value) {
8132
+ return typeof value === "object" && value !== null && "token" in value && "id" in value;
8133
+ }
8134
+ function entityRefsOf(value) {
8135
+ const items = Array.isArray(value) ? value : value == null ? [] : [value];
8136
+ const out = [];
8137
+ for (const item of items) {
8138
+ if (!isRefLike(item)) continue;
8139
+ const token = String(item.token ?? "").trim().toLowerCase();
8140
+ const id = String(item.id ?? "").trim();
8141
+ if (!token || !ENTITY_REF_ID.test(id)) continue;
8142
+ const label = item.label;
8143
+ out.push({ token, id: id.toLowerCase(), label: typeof label === "string" ? label : null });
8144
+ }
8145
+ return out;
8146
+ }
8147
+ function entityRefsToWrite(value, multi) {
8148
+ const seen = /* @__PURE__ */ new Set();
8149
+ const refs = [];
8150
+ for (const ref of entityRefsOf(value)) {
8151
+ const k = `${ref.token}:${ref.id}`;
8152
+ if (seen.has(k)) continue;
8153
+ seen.add(k);
8154
+ refs.push({ token: ref.token, id: ref.id });
8155
+ }
8156
+ if (multi) return refs;
8157
+ return refs[0] ?? null;
8158
+ }
8159
+ async function entityReferenceKinds(source) {
8160
+ const { data, error } = await source.rpc("entity_reference_kinds", {}, { schema: "custom" });
8161
+ if (error) return err(mapPgError(error, "entityReferenceKinds"));
8162
+ return ok(Array.isArray(data) ? data : []);
8163
+ }
8164
+ async function entityReferenceWords(source, organizationId, refs) {
8165
+ if (refs.length === 0) return ok([]);
8166
+ const { data, error } = await source.rpc(
8167
+ "entity_reference_words",
8168
+ { p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) },
8169
+ { schema: "custom" }
8170
+ );
8171
+ if (error) return err(mapPgError(error, "entityReferenceWords"));
8172
+ return ok(entityRefsOf(data));
8173
+ }
8174
+
8175
+ // src/react/entity-reference.ts
8176
+ function useEntityReferenceKinds() {
8177
+ const client = useRecordsClient();
8178
+ const [state, setState] = (0, import_react3.useState)({ data: null, loading: true, error: null });
8179
+ (0, import_react3.useEffect)(() => {
8180
+ let live = true;
8181
+ void entityReferenceKinds(client.config.dataSource).then((res) => {
8182
+ if (!live) return;
8183
+ setState(res.ok ? { data: res.data, loading: false, error: null } : { data: null, loading: false, error: res.error });
8184
+ });
8185
+ return () => {
8186
+ live = false;
8187
+ };
8188
+ }, [client]);
8189
+ return state;
8190
+ }
8191
+ function useEntityReferenceWords(refs) {
8192
+ const client = useRecordsClient();
8193
+ const key = refs.map((r) => `${r.token}:${r.id}`).join(",");
8194
+ const [state, setState] = (0, import_react3.useState)({ data: null, loading: refs.length > 0, error: null });
8195
+ (0, import_react3.useEffect)(() => {
8196
+ let live = true;
8197
+ void entityReferenceWords(client.config.dataSource, client.config.organizationId, refs).then((res) => {
8198
+ if (!live) return;
8199
+ setState(res.ok ? { data: res.data, loading: false, error: null } : { data: null, loading: false, error: res.error });
8200
+ });
8201
+ return () => {
8202
+ live = false;
8203
+ };
8204
+ }, [client, key]);
8205
+ return state;
8206
+ }
8207
+
7736
8208
  // src/vocabulary.ts
7737
8209
  var TABLE_TYPES = [
7738
8210
  "entity",
@@ -7844,6 +8316,7 @@ var FIELD_KINDS = [
7844
8316
  "currency",
7845
8317
  "datetime",
7846
8318
  "email",
8319
+ "entity_reference",
7847
8320
  "formula",
7848
8321
  "json",
7849
8322
  "long_text",
@@ -7882,6 +8355,7 @@ function fieldKindFor(field, kernel) {
7882
8355
  case "list":
7883
8356
  return field.multi ? "multi_select" : "select";
7884
8357
  case "relation": {
8358
+ if (isEntityReferenceConfig(config)) return "entity_reference";
7885
8359
  const parity = String(config["parity"] ?? "");
7886
8360
  if (parity === "attachment" || parity === "member") return parity;
7887
8361
  if (kernel?.file && field.relation_target === kernel.file) return "attachment";
@@ -7899,6 +8373,14 @@ function fieldKindFor(field, kernel) {
7899
8373
  }
7900
8374
  }
7901
8375
  var UUID_TEXT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8376
+ function isEntityReferenceConfig(config) {
8377
+ const allowed = config?.["allowed_types"];
8378
+ return Array.isArray(allowed) && allowed.length > 0;
8379
+ }
8380
+ function entityReferenceKindsOf(field) {
8381
+ const allowed = field.config?.["allowed_types"];
8382
+ return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];
8383
+ }
7902
8384
  var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
7903
8385
  var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
7904
8386
  function coercionLabel(field) {
@@ -8014,6 +8496,23 @@ function coerceTypedAnswer(field, raw, opts = {}) {
8014
8496
  return {
8015
8497
  refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`
8016
8498
  };
8499
+ // SC-R / P12. An entity reference names a platform thing by kind and id.
8500
+ // An import or an agent may hand over `note:<id>` — or a bare id when the
8501
+ // column allows exactly one kind; the store then decides whether the thing
8502
+ // is there and whether the writer may open it. Words are chosen, not typed.
8503
+ case "entity_reference": {
8504
+ const allowed = entityReferenceKindsOf(field);
8505
+ const [maybeToken, maybeId] = text.includes(":") ? text.split(/:(.*)/s, 2) : ["", text];
8506
+ const token = (maybeToken ?? "").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : "");
8507
+ const id = (maybeId ?? "").trim();
8508
+ if (token && UUID_TEXT.test(id)) {
8509
+ const ref = { token, id: id.toLowerCase() };
8510
+ return { value: field.multi ? [ref] : ref };
8511
+ }
8512
+ return {
8513
+ refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`
8514
+ };
8515
+ }
8017
8516
  case "formula":
8018
8517
  case "lookup":
8019
8518
  case "rollup":
@@ -8070,6 +8569,10 @@ function publicFormPath(formId) {
8070
8569
  }
8071
8570
 
8072
8571
  // src/portal.ts
8572
+ var PORTAL_ACCENTS = ["slate", "green", "amber", "red", "blue", "violet", "teal"];
8573
+ function isPortalAccent(word) {
8574
+ return typeof word === "string" && PORTAL_ACCENTS.includes(word);
8575
+ }
8073
8576
  function portalPath(slug) {
8074
8577
  return `/portal/c/${slug}`;
8075
8578
  }
@@ -8160,7 +8663,7 @@ function memoryStore() {
8160
8663
  setMeta: async (key, value) => void meta.set(key, value)
8161
8664
  };
8162
8665
  }
8163
- function countOf(items) {
8666
+ function countOf2(items) {
8164
8667
  const c = { waiting: 0, sending: 0, refused: 0, landed: 0 };
8165
8668
  for (const i of items) c[i.state] += 1;
8166
8669
  return c;
@@ -8177,7 +8680,7 @@ function openCaptureQueue(opts) {
8177
8680
  const announce = async () => {
8178
8681
  if (!opts.onChange) return;
8179
8682
  const items = await store.all();
8180
- opts.onChange(countOf(items), items);
8683
+ opts.onChange(countOf2(items), items);
8181
8684
  };
8182
8685
  const oldestFirst = (items) => items.slice().sort((a, b) => a.captured_at.localeCompare(b.captured_at));
8183
8686
  const queue = {
@@ -8206,7 +8709,7 @@ function openCaptureQueue(opts) {
8206
8709
  return key;
8207
8710
  },
8208
8711
  list: () => store.all().then(oldestFirst),
8209
- counts: () => store.all().then(countOf),
8712
+ counts: () => store.all().then(countOf2),
8210
8713
  async sync() {
8211
8714
  if (syncing) return syncing;
8212
8715
  syncing = (async () => {