@mulmoclaude/core 1.2.0 → 1.2.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.
Files changed (63) hide show
  1. package/dist/collection/core/chatSeed.d.ts +4 -0
  2. package/dist/collection/core/completion.d.ts +32 -0
  3. package/dist/collection/core/ids.d.ts +5 -0
  4. package/dist/collection/core/recordKeys.d.ts +7 -0
  5. package/dist/collection/index.cjs +25 -2
  6. package/dist/collection/index.cjs.map +1 -1
  7. package/dist/collection/index.d.ts +1 -0
  8. package/dist/collection/index.js +20 -3
  9. package/dist/collection/index.js.map +1 -1
  10. package/dist/collection/registry/server/index.cjs +2 -2
  11. package/dist/collection/registry/server/index.js +2 -2
  12. package/dist/collection/server/index.cjs +3 -3
  13. package/dist/collection/server/index.js +3 -3
  14. package/dist/collection-watchers/index.cjs +4 -4
  15. package/dist/collection-watchers/index.js +4 -4
  16. package/dist/{discovery-UxkvBxV-.cjs → discovery--kRUWed0.cjs} +2 -2
  17. package/dist/{discovery-UxkvBxV-.cjs.map → discovery--kRUWed0.cjs.map} +1 -1
  18. package/dist/{discovery-DYlEOa9a.js → discovery-CKR4TOX3.js} +2 -2
  19. package/dist/{discovery-DYlEOa9a.js.map → discovery-CKR4TOX3.js.map} +1 -1
  20. package/dist/feeds/index.cjs +2 -2
  21. package/dist/feeds/index.js +2 -2
  22. package/dist/feeds/server/index.cjs +4 -4
  23. package/dist/feeds/server/index.js +4 -4
  24. package/dist/google/index.cjs +1 -1
  25. package/dist/google/index.js +1 -1
  26. package/dist/{graph-o5FZZbXf.js → graph-BcwqiSpR.js} +2 -14
  27. package/dist/graph-BcwqiSpR.js.map +1 -0
  28. package/dist/{graph-2R4HbQAg.cjs → graph-DjE1Az1C.cjs} +2 -14
  29. package/dist/graph-DjE1Az1C.cjs.map +1 -0
  30. package/dist/{ids-BJ7rdrDN.cjs → ids-BnTh3Hl0.cjs} +13 -1
  31. package/dist/{ids-BJ7rdrDN.cjs.map → ids-BnTh3Hl0.cjs.map} +1 -1
  32. package/dist/{ids-Bv6AOW9Z.js → ids-s4GfmE93.js} +8 -2
  33. package/dist/{ids-Bv6AOW9Z.js.map → ids-s4GfmE93.js.map} +1 -1
  34. package/dist/{ingestTypes-DuCiZqye.js → ingestTypes-L59cIGgX.js} +2 -2
  35. package/dist/{ingestTypes-DuCiZqye.js.map → ingestTypes-L59cIGgX.js.map} +1 -1
  36. package/dist/{ingestTypes-CkbM-zrO.cjs → ingestTypes-xX8VpQqh.cjs} +2 -2
  37. package/dist/{ingestTypes-CkbM-zrO.cjs.map → ingestTypes-xX8VpQqh.cjs.map} +1 -1
  38. package/dist/{promptSafety-BNelBKhh.js → promptSafety-CQ5Un4wi.js} +28 -3
  39. package/dist/promptSafety-CQ5Un4wi.js.map +1 -0
  40. package/dist/{promptSafety-QAtADXUF.cjs → promptSafety-DfZYNtjK.cjs} +45 -2
  41. package/dist/promptSafety-DfZYNtjK.cjs.map +1 -0
  42. package/dist/remote-host/server/hostRunner.d.ts +3 -0
  43. package/dist/remote-host/server/index.cjs +69 -16
  44. package/dist/remote-host/server/index.cjs.map +1 -1
  45. package/dist/remote-host/server/index.js +69 -16
  46. package/dist/remote-host/server/index.js.map +1 -1
  47. package/dist/{server-CnwgECW-.js → server-BZJCw2HU.js} +4 -4
  48. package/dist/{server-CnwgECW-.js.map → server-BZJCw2HU.js.map} +1 -1
  49. package/dist/{server-DFo3zFYB.cjs → server-w5ZZRLju.cjs} +4 -4
  50. package/dist/{server-DFo3zFYB.cjs.map → server-w5ZZRLju.cjs.map} +1 -1
  51. package/dist/wiki/index.cjs +20 -12
  52. package/dist/wiki/index.cjs.map +1 -1
  53. package/dist/wiki/index.js +20 -12
  54. package/dist/wiki/index.js.map +1 -1
  55. package/dist/wiki/link.d.ts +7 -1
  56. package/dist/wiki/render.d.ts +3 -3
  57. package/dist/wiki/server/index.cjs +1 -1
  58. package/dist/wiki/server/index.js +1 -1
  59. package/package.json +5 -4
  60. package/dist/graph-2R4HbQAg.cjs.map +0 -1
  61. package/dist/graph-o5FZZbXf.js.map +0 -1
  62. package/dist/promptSafety-BNelBKhh.js.map +0 -1
  63. package/dist/promptSafety-QAtADXUF.cjs.map +0 -1
@@ -0,0 +1,4 @@
1
+ /** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific
2
+ * record is addressed. The `id=` selector goes BEFORE the free-text message so
3
+ * the skill's argument parser reads it as a flag rather than as prose. */
4
+ export declare function skillCommandSeed(slug: string, message: string, itemId?: string): string;
@@ -1,4 +1,5 @@
1
1
  import { Where } from './where';
2
+ import { CollectionFieldSpec, CollectionItem } from './schema';
2
3
  /** The slice of a parsed schema the done predicate reads — minimal
3
4
  * structural shape (like `DerivableSchema`) so the client and server
4
5
  * `CollectionSchema` types both satisfy it as-is. */
@@ -37,3 +38,34 @@ export interface CompletionChipSchemaView {
37
38
  * `completionDoneValues: ["done"]`). A superset pair (extra done values)
38
39
  * still synthesizes — no field chip covers it. */
39
40
  export declare function completionCoveredByFieldChip(schema: CompletionChipSchemaView): boolean;
41
+ /** Whether a `toggle` field reads as checked: its projected enum field currently
42
+ * equals `onValue`. The toggle stores nothing of its own, so this reads the raw
43
+ * projected value; a non-toggle field is never checked. */
44
+ export declare function toggleChecked(item: CollectionItem, field: CollectionFieldSpec): boolean;
45
+ /** A `flag` FIELD's boolean for one row, read off the already-enriched record
46
+ * (so a flag computed from derived/rollup inputs is correct). Strict `=== true`
47
+ * on purpose: a truthy non-boolean ("yes", 1) is NOT a set flag, so a stray
48
+ * value can never render as an active flag. */
49
+ export declare function flagFieldValue(record: Record<string, unknown>, key: string): boolean;
50
+ /** One entry in the table's flag-filter menu: a real `flag` / `boolean` /
51
+ * `toggle` field, or the synthesized legacy-completion chip (`synthetic`,
52
+ * predicate = `itemIsDone`). */
53
+ export interface FlagChip {
54
+ key: string;
55
+ label: string;
56
+ synthetic?: boolean;
57
+ }
58
+ /** The schema slice `chipMatches` reads: full field specs (it inspects the
59
+ * `toggle` variant's `field` / `onValue`) plus the completion pair that
60
+ * `itemIsDone` needs for the synthesized chip. */
61
+ export interface ChipMatchSchema {
62
+ fields: Record<string, CollectionFieldSpec>;
63
+ completionField?: string;
64
+ completionDoneValues?: readonly string[];
65
+ }
66
+ /** Whether one row satisfies a chip's predicate: `itemIsDone` for the
67
+ * synthesized completion chip, the projected value for a `toggle`, the stored
68
+ * boolean for a `boolean`, else the computed flag value. The flag branch reads
69
+ * the ENRICHED record via the injected `deriveRecord` (kept a parameter so this
70
+ * stays pure and framework-free). */
71
+ export declare function chipMatches(chip: FlagChip, schema: ChipMatchSchema, item: CollectionItem, deriveRecord: (item: CollectionItem) => Record<string, unknown>): boolean;
@@ -1,3 +1,4 @@
1
+ import { CollectionItem } from './schema';
1
2
  export declare const SAFE_SLUG_PATTERN: RegExp;
2
3
  export declare const SAFE_RECORD_ID_PATTERN: RegExp;
3
4
  /** True when `value` is a well-formed collection slug (alphanumeric /
@@ -15,3 +16,7 @@ export declare function isSafeRecordId(value: string): boolean;
15
16
  * Collisions on a wide id space are astronomically unlikely, so a caller's
16
17
  * own overwrite guard is the final backstop rather than an unbounded loop. */
17
18
  export declare function generateUniqueId(existing: ReadonlySet<string>, generate: () => string, maxAttempts?: number): string;
19
+ /** An id not already used by any loaded record: collects the in-memory primary
20
+ * keys and re-rolls `generate()` against them (see {@link generateUniqueId}).
21
+ * The pure core of the view's create-form id prefill. */
22
+ export declare function nextUniqueItemId(items: readonly CollectionItem[], primaryKey: string, generate: () => string, maxAttempts?: number): string;
@@ -9,6 +9,13 @@ export interface EnumSnapshotSchema {
9
9
  }
10
10
  /** Stable key for one cell (`<rowId>:<fieldKey>`) in the per-cell state maps. */
11
11
  export declare function cellKey(rowId: string, fieldKey: string): string;
12
+ /** A row's stable identity: the primary-key value as text (`""` when the schema
13
+ * has no primary key, or the value is absent). Every per-row / per-cell state
14
+ * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses
15
+ * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the
16
+ * empty-enum snapshot always agree. A real primary key is a scalar, so this
17
+ * matches its plain string form. */
18
+ export declare function rowIdOf(primaryKey: string | undefined, item: CollectionItem): string;
12
19
  /** The set of enum cells that were empty in the freshly-fetched records — the
13
20
  * only cells whose inline dropdown offers an empty option. */
14
21
  export declare function snapshotEmptyEnums(schema: EnumSnapshotSchema, records: CollectionItem[]): Set<string>;
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dist = require("../dist-pWpC-b04.cjs");
3
- const require_ids = require("../ids-BJ7rdrDN.cjs");
3
+ const require_ids = require("../ids-BnTh3Hl0.cjs");
4
4
  const require_project = require("../project-BWI5w_BT.cjs");
5
- const require_promptSafety = require("../promptSafety-QAtADXUF.cjs");
5
+ const require_promptSafety = require("../promptSafety-DfZYNtjK.cjs");
6
6
  //#region src/collection/core/presentCollection.ts
7
7
  var TOOL_NAME = "presentCollection";
8
8
  var TOOL_DEFINITION = {
@@ -307,6 +307,14 @@ function firstMissingRequiredField(draft, schema) {
307
307
  return null;
308
308
  }
309
309
  //#endregion
310
+ //#region src/collection/core/chatSeed.ts
311
+ /** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific
312
+ * record is addressed. The `id=` selector goes BEFORE the free-text message so
313
+ * the skill's argument parser reads it as a flag rather than as prose. */
314
+ function skillCommandSeed(slug, message, itemId) {
315
+ return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
316
+ }
317
+ //#endregion
310
318
  //#region src/collection/core/sortItems.ts
311
319
  var EMPTY = { empty: true };
312
320
  /** Field types that render no value text in the table, so offer no sort. */
@@ -451,6 +459,15 @@ function itemMatchesQuery(item, query) {
451
459
  function cellKey(rowId, fieldKey) {
452
460
  return `${rowId}:${fieldKey}`;
453
461
  }
462
+ /** A row's stable identity: the primary-key value as text (`""` when the schema
463
+ * has no primary key, or the value is absent). Every per-row / per-cell state
464
+ * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses
465
+ * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the
466
+ * empty-enum snapshot always agree. A real primary key is a scalar, so this
467
+ * matches its plain string form. */
468
+ function rowIdOf(primaryKey, item) {
469
+ return primaryKey ? require_ids.fieldText(item[primaryKey]) : "";
470
+ }
454
471
  /** The set of enum cells that were empty in the freshly-fetched records — the
455
472
  * only cells whose inline dropdown offers an empty option. */
456
473
  function snapshotEmptyEnums(schema, records) {
@@ -602,6 +619,7 @@ exports.buildMonthGrid = require_promptSafety.buildMonthGrid;
602
619
  exports.buildOntologyGraph = buildOntologyGraph;
603
620
  exports.buildUpdatedRecord = buildUpdatedRecord;
604
621
  exports.cellKey = cellKey;
622
+ exports.chipMatches = require_promptSafety.chipMatches;
605
623
  exports.coerceInlineValue = coerceInlineValue;
606
624
  exports.coerceNumeric = require_promptSafety.coerceNumeric;
607
625
  exports.compareSortValues = compareSortValues;
@@ -627,6 +645,7 @@ exports.fieldTextOrNull = require_ids.fieldTextOrNull;
627
645
  exports.fieldVisible = require_promptSafety.fieldVisible;
628
646
  exports.firstDateField = require_promptSafety.firstDateField;
629
647
  exports.firstMissingRequiredField = firstMissingRequiredField;
648
+ exports.flagFieldValue = require_promptSafety.flagFieldValue;
630
649
  exports.generateUniqueId = require_ids.generateUniqueId;
631
650
  exports.isFieldDrivenEvery = require_ids.isFieldDrivenEvery;
632
651
  exports.isReadOnlySchema = require_ids.isReadOnlySchema;
@@ -641,6 +660,7 @@ exports.labelFieldFor = labelFieldFor;
641
660
  exports.matchesWhere = require_promptSafety.matchesWhere;
642
661
  exports.monthAnchorDate = require_promptSafety.monthAnchorDate;
643
662
  exports.nextSortDirection = nextSortDirection;
663
+ exports.nextUniqueItemId = require_ids.nextUniqueItemId;
644
664
  exports.numericSortValue = numericSortValue;
645
665
  exports.parseIsoDate = require_promptSafety.parseIsoDate;
646
666
  exports.parseIsoDateTime = require_promptSafety.parseIsoDateTime;
@@ -653,15 +673,18 @@ exports.resolveIcon = require_promptSafety.resolveIcon;
653
673
  exports.resolveRowRefs = require_promptSafety.resolveRowRefs;
654
674
  exports.rollupValue = require_promptSafety.rollupValue;
655
675
  exports.rowFromItem = rowFromItem;
676
+ exports.rowIdOf = rowIdOf;
656
677
  exports.scalarSortValue = scalarSortValue;
657
678
  exports.selectDynamicRecord = require_promptSafety.selectDynamicRecord;
658
679
  exports.shortHexId = shortHexId;
680
+ exports.skillCommandSeed = skillCommandSeed;
659
681
  exports.snapshotEmptyEnums = snapshotEmptyEnums;
660
682
  exports.sortItems = sortItems;
661
683
  exports.sortValueOf = sortValueOf;
662
684
  exports.spanCoversDay = require_promptSafety.spanCoversDay;
663
685
  exports.storageKindFor = require_ids.storageKindFor;
664
686
  exports.stringSortValue = stringSortValue;
687
+ exports.toggleChecked = require_promptSafety.toggleChecked;
665
688
  exports.uniqueBacklinkSources = require_promptSafety.uniqueBacklinkSources;
666
689
  exports.uniqueEmbedTargets = require_promptSafety.uniqueEmbedTargets;
667
690
  exports.uniqueRefTargets = require_promptSafety.uniqueRefTargets;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/collection/core/presentCollection.ts","../../src/collection/core/enumColors.ts","../../src/collection/core/draft.ts","../../src/collection/core/sortItems.ts","../../src/collection/core/sortValueOf.ts","../../src/collection/core/textSearch.ts","../../src/collection/core/recordKeys.ts","../../src/collection/core/itemLabel.ts","../../src/collection/core/shortHexId.ts","../../src/collection/core/ontologyGraph.ts"],"sourcesContent":["// presentCollection tool — definition + pure server-side executor.\n//\n// Isomorphic (no Vue, no Node): bundled to the browser as the plugin's\n// `execute`, and run server-side via the host's plugin dispatch route. The\n// executor only validates + echoes the addressing; the live schema + items\n// are fetched client-side by the View through the host's /api/collections\n// routes, so a bad slug surfaces as the View's \"not found\" state.\n\nimport type { ToolContext, ToolDefinition, ToolResult } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentCollection\";\n\n/** Render payload carried in the tool result's `data` field; the View mounts\n * off these. Same shape as the tool args. */\nexport interface PresentCollectionData {\n /** Slug of the collection to display (e.g. \"clients\", \"invoices\"). */\n collectionSlug: string;\n /** Optional primary-key value of a single item to open on mount. */\n itemId?: string;\n}\n\nexport type PresentCollectionArgs = PresentCollectionData;\n\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description:\n \"Display a schema-driven collection inline in the chat as an interactive, editable card. Shows the collection's list of records. Pass `itemId` to open one specific record on mount.\",\n parameters: {\n type: \"object\",\n properties: {\n collectionSlug: {\n type: \"string\",\n description: \"The slug of the collection to display (e.g. 'clients', 'invoices', 'contacts').\",\n },\n itemId: {\n type: \"string\",\n description: \"Optional primary-key value of a single record to open in detail view on mount. Omit to show the full list.\",\n },\n },\n required: [\"collectionSlug\"],\n },\n prompt: `After making changes to schema-driven collections, use ${TOOL_NAME} to present either the collection or the item`,\n};\n\nexport const executePresentCollection = async (\n _context: ToolContext,\n args: PresentCollectionArgs,\n): Promise<ToolResult<PresentCollectionData, PresentCollectionData>> => {\n const collectionSlug = typeof args?.collectionSlug === \"string\" ? args.collectionSlug.trim() : \"\";\n if (!collectionSlug) {\n return {\n message: \"presentCollection error: collectionSlug is required\",\n instructions: \"Tell the user you couldn't display the collection because no collection was specified, and ask which collection they mean.\",\n };\n }\n const itemId = typeof args.itemId === \"string\" && args.itemId.trim().length > 0 ? args.itemId.trim() : undefined;\n const data: PresentCollectionData = itemId ? { collectionSlug, itemId } : { collectionSlug };\n const target = itemId ? `${collectionSlug} / ${itemId}` : collectionSlug;\n return {\n message: `Presented collection ${target}`,\n // `data` is the view's source (also the host's render-eligibility signal);\n // `jsonData` is what the LLM sees. Same payload, two audiences.\n data,\n jsonData: data,\n // Phrased to stay correct for BOTH storage kinds without loading the\n // schema (this executor is deliberately pure/isomorphic): a writable\n // collection offers edit/create/delete in the card; a read-only\n // `dataSource` collection hides those and changes flow through its\n // data file instead.\n instructions:\n \"The collection has been presented to the user as an interactive card. They can browse and open records directly; on a writable collection they can also edit, create, and delete (a read-only dataSource collection shows no edit controls — its records change by editing the backing data file). No further action is needed unless they ask.\",\n };\n};\n","// A standard, ordered colour palette for `enum` field values, shared by every\n// collection surface (list, calendar, kanban). Each value in an\n// enum's `values` array is assigned the palette entry at its index — cycling\n// when an enum declares more values than the palette holds — so colouring is\n// automatic and consistent without any per-value schema config.\n//\n// Tailwind only detects class names that appear as complete string literals,\n// so every surface's classes are spelled out per colour below rather than\n// built from a colour name at runtime.\n//\n// One enum is special: the field a schema's `notifyWhen` targets (the\n// \"notification enum\"). Its flagged values read the notification severity\n// colours — the first flagged value (most urgent) red, the rest amber — and\n// every other value reads neutral grey, mirroring the notification bell\n// (red = urgent, amber = nudge) rather than the rotating palette.\n// `resolveEnumColor` encapsulates that rule.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionSchema } from \"./schema\";\n\nexport interface EnumColorClasses {\n /** Stat-card style: border + fill + text + hover. */\n card: string;\n /** Small status dot (kanban column header). */\n dot: string;\n /** Pill / badge / inline `<select>` fill + text (no border width). */\n badge: string;\n /** Border colour, paired with a `border` width class by the caller. */\n border: string;\n}\n\n// The palette deliberately EXCLUDES the warm warning band (red / orange /\n// amber): those are reserved for the notification-enum severity colours\n// (`ENUM_ALERT` red, `ENUM_NUDGE` amber), so a normal enum value can never\n// draw a colour that reads like a notification. Eight well-separated cool /\n// green / magenta hues keep enough variety for wide enums.\nconst PALETTE: readonly EnumColorClasses[] = [\n {\n card: \"border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100\",\n dot: \"bg-indigo-500\",\n badge: \"bg-indigo-100 text-indigo-700\",\n border: \"border-indigo-200\",\n },\n { card: \"border-sky-200 bg-sky-50 text-sky-600 hover:bg-sky-100\", dot: \"bg-sky-500\", badge: \"bg-sky-100 text-sky-700\", border: \"border-sky-200\" },\n { card: \"border-cyan-200 bg-cyan-50 text-cyan-600 hover:bg-cyan-100\", dot: \"bg-cyan-500\", badge: \"bg-cyan-100 text-cyan-700\", border: \"border-cyan-200\" },\n { card: \"border-teal-200 bg-teal-50 text-teal-600 hover:bg-teal-100\", dot: \"bg-teal-500\", badge: \"bg-teal-100 text-teal-700\", border: \"border-teal-200\" },\n {\n card: \"border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100\",\n dot: \"bg-emerald-500\",\n badge: \"bg-emerald-100 text-emerald-700\",\n border: \"border-emerald-200\",\n },\n { card: \"border-lime-200 bg-lime-50 text-lime-600 hover:bg-lime-100\", dot: \"bg-lime-500\", badge: \"bg-lime-100 text-lime-700\", border: \"border-lime-200\" },\n {\n card: \"border-violet-200 bg-violet-50 text-violet-600 hover:bg-violet-100\",\n dot: \"bg-violet-500\",\n badge: \"bg-violet-100 text-violet-700\",\n border: \"border-violet-200\",\n },\n {\n card: \"border-fuchsia-200 bg-fuchsia-50 text-fuchsia-600 hover:bg-fuchsia-100\",\n dot: \"bg-fuchsia-500\",\n badge: \"bg-fuchsia-100 text-fuchsia-700\",\n border: \"border-fuchsia-200\",\n },\n];\n\n/** Neutral styling for the empty / Uncategorized bucket — never a palette\n * colour, so an unset or unknown value reads grey across every surface. */\nexport const ENUM_NEUTRAL: EnumColorClasses = {\n card: \"border-slate-200 bg-slate-50 text-slate-500 hover:bg-slate-100\",\n dot: \"bg-slate-300\",\n badge: \"bg-slate-100 text-slate-500\",\n border: \"border-slate-200\",\n};\n\n/** The urgent notification colour (red), matching the bell's `urgent`\n * severity. The first value a schema's `notifyWhen` flags reads this. */\nexport const ENUM_ALERT: EnumColorClasses = {\n card: \"border-red-200 bg-red-50 text-red-600 hover:bg-red-100\",\n dot: \"bg-red-500\",\n badge: \"bg-red-100 text-red-700\",\n border: \"border-red-200\",\n};\n\n/** The nudge notification colour (amber), matching the bell's `nudge`\n * severity. Flagged `notifyWhen` values after the first read this. */\nexport const ENUM_NUDGE: EnumColorClasses = {\n card: \"border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100\",\n dot: \"bg-amber-500\",\n badge: \"bg-amber-100 text-amber-700\",\n border: \"border-amber-200\",\n};\n\n/** Classes for the enum value at `index` in its field's `values` array. A\n * negative index (value unset or not among the declared values) reads\n * neutral. */\nexport function enumColorClasses(index: number): EnumColorClasses {\n if (index < 0) return ENUM_NEUTRAL;\n return PALETTE[index % PALETTE.length] ?? ENUM_NEUTRAL;\n}\n\n/** Index of `value` within an enum field's declared `values`, or -1 when the\n * value is empty / unknown (→ neutral). */\nexport function enumValueIndex(values: readonly string[] | undefined, value: unknown): number {\n // No text form (array/object) means no enum member — \"[object Object]\" must\n // not index into the palette.\n const text = fieldTextOrNull(value);\n if (text === null || text === \"\") return -1;\n return values?.indexOf(text) ?? -1;\n}\n\n/** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the\n * \"notification enum\"); undefined for every other field. */\nfunction notifyValuesFor(schema: CollectionSchema, fieldKey: string): readonly string[] | undefined {\n const spec = schema.notifyWhen;\n return spec && spec.field === fieldKey ? spec.in : undefined;\n}\n\n/** Resolve a value's colour for enum field `fieldKey`:\n * - Notification enum (`notifyWhen` targets it): the first flagged value (in\n * `notifyWhen.in` order, the most urgent) reads notification red, the rest\n * amber, and every non-flagged value neutral grey.\n * - Any other enum: the standard palette by the value's declared index. */\nexport function resolveEnumColor(schema: CollectionSchema, fieldKey: string, value: unknown): EnumColorClasses {\n const notifyValues = notifyValuesFor(schema, fieldKey);\n if (notifyValues) {\n const str = fieldText(value);\n const rank = notifyValues.indexOf(str);\n if (rank < 0) return ENUM_NEUTRAL;\n return rank === 0 ? ENUM_ALERT : ENUM_NUDGE;\n }\n const spec = schema.fields[fieldKey];\n return enumColorClasses(enumValueIndex(spec?.type === \"enum\" ? spec.values : undefined, value));\n}\n","// Pure edit-draft logic for the collection record form, extracted from\n// CollectionView.vue so the parent (build draft / save / validate) and\n// the extracted CollectionRecordPanel (per-row table mutators) share one\n// implementation. No Vue, no I/O — every function maps a draft + schema\n// to a value, so the omission/validation semantics are unit-testable.\n\nimport { fieldText } from \"./fieldText\";\nimport { fieldVisible } from \"./actionVisible\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType, CollectionItem, CollectionSchema } from \"./schema\";\nimport type { EditState, TableRowDraft } from \"./uiTypes\";\n\n/** Build a row draft, reading each sub-field's persisted value via\n * `readSub`. An empty row passes a reader that always returns\n * `undefined`; a row-from-item reads the source record. Boolean\n * presence uses `typeof raw === \"boolean\"` so an existing explicit\n * `false` is recorded as present and round-trips on a no-op save. */\nfunction buildTableRowDraft(subFields: Record<string, FieldSpec>, readSub: (subKey: string) => unknown): TableRowDraft {\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n const raw = readSub(subKey);\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = fieldText(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\n}\n\n/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, () => undefined);\n}\n\n/** Build a row draft from an existing persisted row. */\nexport function rowFromItem(item: Record<string, unknown>, subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, (subKey) => item[subKey]);\n}\n\n/** Own-property boolean read: a field named `toString` / `constructor` /\n * `__proto__` must read `false` here, not inherit a truthy Object.prototype\n * member and force an untouched boolean into every saved record. */\nfunction ownFlag(flags: Record<string, boolean>, key: string): boolean {\n return Object.hasOwn(flags, key) && flags[key] === true;\n}\n\n/** Decide whether a boolean field's draft value should be emitted (vs.\n * omitted so a downstream default applies). */\nfunction shouldEmitBoolean(state: EditState, key: string, field: FieldSpec): boolean {\n return ownFlag(state.boolOriginallyPresent, key) || ownFlag(state.boolTouched, key) || Boolean(field.required);\n}\n\n/** Convert a scalar draft slot to its persisted form. `undefined` = omit. */\nfunction scalarDraftToValue(raw: string | undefined, fieldType: FieldType): unknown {\n if (raw === undefined || raw === \"\") return undefined;\n if (fieldType === \"number\" || fieldType === \"money\") {\n const num = Number(raw);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\n/** Convert one table row draft to its persisted record. */\nfunction rowDraftToRecord(rowDraft: TableRowDraft, subFields: Record<string, FieldSpec>): Record<string, unknown> {\n const row: Record<string, unknown> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n if (subField.type === \"boolean\") {\n const value = ownFlag(rowDraft.bool, subKey);\n if (ownFlag(rowDraft.boolOriginallyPresent, subKey) || ownFlag(rowDraft.boolTouched, subKey) || value || subField.required) row[subKey] = value;\n continue;\n }\n const value = scalarDraftToValue(rowDraft.text[subKey], subField.type);\n if (value !== undefined) row[subKey] = value;\n }\n return row;\n}\n\n/** Convert a full edit draft to the record to persist. */\nexport function draftToRecord(state: EditState, schema: CollectionSchema): CollectionItem {\n const record: CollectionItem = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (COMPUTED_TYPES.has(field.type)) continue; // never persisted (toggle projects an enum field)\n if (field.type === \"boolean\") {\n if (shouldEmitBoolean(state, key, field)) record[key] = ownFlag(state.bool, key);\n continue;\n }\n if (field.type === \"table\" && field.of) {\n const subFields = field.of;\n record[key] = (state.table[key] ?? []).map((rowDraft) => rowDraftToRecord(rowDraft, subFields));\n continue;\n }\n const value = scalarDraftToValue(state.text[key], field.type);\n if (value !== undefined) record[key] = value;\n }\n return record;\n}\n\n/** Normalise a raw inline-edit input (table-cell checkbox/select) to its\n * persisted form. Mirrors `draftToRecord`'s boolean strictness; an empty\n * enum selection (the placeholder option) clears the field via `undefined`. */\nexport function coerceInlineValue(field: FieldSpec, raw: boolean | string): unknown {\n if (field.type === \"boolean\") return raw === true;\n return raw === \"\" ? undefined : raw;\n}\n\n/** Build the full record to PUT for a single-cell inline edit, without\n * mutating `item`. A `undefined` value omits the key (enum cleared),\n * matching `draftToRecord`'s omission semantics. */\nexport function buildUpdatedRecord(item: CollectionItem, key: string, value: unknown): CollectionItem {\n if (value === undefined) {\n const { [key]: __omit, ...rest } = item;\n return rest;\n }\n return { ...item, [key]: value };\n}\n\n/** Empty for required-field validation — NOT a truthiness check (a\n * numeric `0` is a filled value). */\nfunction isMissingDraftValue(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/** Label of the first required table sub-field empty in any row, else null. */\nfunction firstMissingTableSubField(field: FieldSpec, rows: TableRowDraft[] | undefined): string | null {\n if (field.type !== \"table\" || !rows) return null;\n for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {\n const row = rows[rowIdx];\n for (const [subKey, subField] of Object.entries(field.of)) {\n if (!subField.required || subField.type === \"boolean\") continue;\n if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;\n }\n }\n return null;\n}\n\n/** Field types the required check never flags: booleans always hold a\n * value, and the computed/projected kinds (COMPUTED_TYPES — the shared\n * set, so a future computed type can't drift past this check) have no\n * draft input at all. */\nconst NEVER_MISSING_TYPES: ReadonlySet<FieldType> = new Set<FieldType>([\"boolean\", ...COMPUTED_TYPES]);\n\nfunction validateOneField(key: string, field: FieldSpec, draft: EditState, record: CollectionItem): string | null {\n // A `when`-hidden field has no input the user can fill — never missing.\n if (!fieldVisible(field, record)) return null;\n if (field.type === \"table\" && field.of) {\n const rows = draft.table[key];\n if (field.required && (!rows || rows.length === 0)) return field.label;\n return firstMissingTableSubField(field, rows);\n }\n if (!field.required) return null;\n if (draft.mode === \"create\" && field.primary === true) return null; // server auto-generates id\n if (NEVER_MISSING_TYPES.has(field.type)) return null;\n return isMissingDraftValue(draft.text[key]) ? field.label : null;\n}\n\n/** Human-readable label of the first missing required field, or null. */\nexport function firstMissingRequiredField(draft: EditState, schema: CollectionSchema): string | null {\n const record = draftToRecord(draft, schema);\n for (const [key, field] of Object.entries(schema.fields)) {\n const missing = validateOneField(key, field, draft, record);\n if (missing) return missing;\n }\n return null;\n}\n","// Sorting for the collection list table. The header sort toggle (next to\n// each field title) cycles a single active column through none → asc →\n// desc → none; this module turns that state plus a per-field value\n// extractor into an ordered copy of the rows.\n//\n// Two invariants the comparator guarantees:\n// 1. Rows with an empty/missing value for the sorted field always sink\n// to the bottom, regardless of direction.\n// 2. Ties (and the empty group) keep their original order — the sort is\n// stable via the captured source index.\n//\n// Field-type → comparable mapping (see `isSortableField`):\n// string/text/email → string · number/money → numeric ·\n// date/datetime → epoch-ms · enum → declared-index · boolean/toggle/flag →\n// false<true · ref → display label · derived → its display type.\n// markdown/table/image/file/embed get no sort button.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem, CollectionFieldSpec, CollectionFieldType } from \"./schema\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface SortState {\n /** Field key of the single active sort column. */\n field: string;\n direction: SortDirection;\n}\n\n/** A row's comparable value for the active field. Exactly one of `num` /\n * `str` is set when not empty; `empty` rows always sort last. */\nexport interface SortValue {\n empty: boolean;\n num?: number;\n str?: string;\n}\n\nconst EMPTY: SortValue = { empty: true };\n\n/** Field types that render no value text in the table, so offer no sort. */\nconst NON_SORTABLE: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"markdown\", \"table\", \"image\", \"file\", \"embed\", \"backlinks\", \"rollup\"]);\n\nexport function isSortableField(field: CollectionFieldSpec): boolean {\n return !NON_SORTABLE.has(field.type);\n}\n\n/** Cycle one column's state: none → asc → desc → none. */\nexport function nextSortDirection(current: SortDirection | null): SortDirection | null {\n if (current === null) return \"asc\";\n if (current === \"asc\") return \"desc\";\n return null;\n}\n\n// ── SortValue constructors (one per comparable kind) ────────────────\n\nexport function numericSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) ? { empty: false, num } : EMPTY;\n}\n\nexport function stringSortValue(raw: unknown): SortValue {\n // An array/object has no sort key — treat it as empty (sorts last) instead of\n // grouping every such record together under \"[object Object]\".\n const str = fieldTextOrNull(raw);\n if (str === null) return EMPTY;\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const epoch = Date.parse(text);\n // Unparseable dates fall back to a lexical compare rather than vanishing.\n return Number.isNaN(epoch) ? stringSortValue(raw) : { empty: false, num: epoch };\n}\n\n/** Enum sorts by the value's index in the declared `values` list. A value\n * outside the list (or unset) is treated as empty → last. */\nexport function enumSortValue(values: readonly string[] | undefined, raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const idx = values ? values.indexOf(text) : -1;\n return idx < 0 ? EMPTY : { empty: false, num: idx };\n}\n\n/** Boolean / toggle: false < true, never empty (unset reads as false). */\nexport function boolSortValue(checked: boolean): SortValue {\n return { empty: false, num: checked ? 1 : 0 };\n}\n\n// ── Comparator + driver ─────────────────────────────────────────────\n\nexport function compareSortValues(left: SortValue, right: SortValue): number {\n if (left.num !== undefined && right.num !== undefined) return left.num - right.num;\n const leftStr = left.str ?? String(left.num ?? \"\");\n const rightStr = right.str ?? String(right.num ?? \"\");\n return leftStr.localeCompare(rightStr);\n}\n\n/** Stable sort of `items` by `valueOf`. Empties always last; ties hold\n * source order. Returns a new array (does not mutate `items`). */\nexport function sortItems(items: readonly CollectionItem[], direction: SortDirection, valueOf: (item: CollectionItem) => SortValue): CollectionItem[] {\n const dir = direction === \"asc\" ? 1 : -1;\n return items\n .map((item, index) => ({ item, index, sv: valueOf(item) }))\n .sort((left, right) => {\n if (left.sv.empty || right.sv.empty) {\n if (left.sv.empty && right.sv.empty) return left.index - right.index;\n return left.sv.empty ? 1 : -1;\n }\n const base = compareSortValues(left.sv, right.sv);\n return base !== 0 ? base * dir : left.index - right.index;\n })\n .map((decorated) => decorated.item);\n}\n","// Field-type → comparable value for the collection list-table sort. Splits\n// the header sort's per-row value extraction out of the Vue view so the\n// dispatch (which field type maps to which SortValue constructor) is testable\n// on its own. The comparators live in `./sortItems`; this module only routes.\n//\n// A few field kinds can't be sorted from the raw cell alone — `toggle` and\n// `flag` are projected booleans, `derived` is a formula, and `ref` sorts by\n// its resolved display label. Those readers live in the view's rendering\n// composable, so they are passed in (dependency injection) rather than\n// imported, keeping this module pure and framework-free.\n\nimport { numericSortValue, stringSortValue, dateSortValue, enumSortValue, boolSortValue, type SortValue } from \"./sortItems\";\nimport type { CollectionItem, CollectionFieldSpec as FieldSpec } from \"./schema\";\n\n/** Resolve a `ref` cell to its human display label (target slug + item id). */\nexport type RefDisplayResolver = (targetSlug: string, itemSlug: string) => string;\n\n/** The row readers the view supplies for the field kinds that can't sort off\n * the raw cell value. All come from the rendering composable. */\nexport interface SortValueDeps {\n toggleChecked: (item: CollectionItem, field: FieldSpec) => boolean;\n flagValueOf: (key: string, item: CollectionItem) => boolean;\n evaluateDerived: (field: FieldSpec, key: string, item: CollectionItem) => number | null;\n deriveRecord: (item: CollectionItem) => CollectionItem;\n resolveRefDisplay: RefDisplayResolver;\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nexport function scalarSortValue(field: FieldSpec, raw: unknown, resolveRefDisplay: RefDisplayResolver): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(resolveRefDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date → epoch, anything else → the enriched value as a string. */\nexport function derivedSortValue(\n field: FieldSpec,\n key: string,\n item: CollectionItem,\n deps: Pick<SortValueDeps, \"evaluateDerived\" | \"deriveRecord\">,\n): SortValue {\n const display = field.type === \"derived\" ? field.display : undefined;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(deps.evaluateDerived(field, key, item));\n }\n const enriched = deps.deriveRecord(item);\n if (display === \"date\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\n/** Comparable value for one row under the active field. Toggle, flag, and\n * derived need the whole record; every other type keys off the raw cell. */\nexport function sortValueOf(field: FieldSpec, key: string, item: CollectionItem, deps: SortValueDeps): SortValue {\n if (field.type === \"toggle\") return boolSortValue(deps.toggleChecked(item, field));\n if (field.type === \"flag\") return boolSortValue(deps.flagValueOf(key, item));\n if (field.type === \"derived\") return derivedSortValue(field, key, item, deps);\n return scalarSortValue(field, item[key], deps.resolveRefDisplay);\n}\n","// Free-text record matching for the collection list search box. Pure and\n// framework-free so the view's `filteredItems` computed just calls it.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object/array-valued fields (table rows, nested records) have no text\n * form (`fieldTextOrNull` → `null`) and are skipped — they don't render as\n * searchable text in the list table.\n *\n * `query` is matched against each cell's lower-cased text but is NOT\n * lower-cased here: callers pass an already-normalised (trimmed +\n * lower-cased) query, so an upper-case `query` intentionally matches\n * nothing. Keeping the normalisation at the call site avoids re-lowering\n * the same query once per row. */\nexport function itemMatchesQuery(item: CollectionItem, query: string): boolean {\n // An empty query matches every record — otherwise a row whose only fields are\n // object/array-valued (no scalar cell reaches `includes(\"\")`) would vanish\n // when the search box is cleared. The list caller already short-circuits the\n // empty case, so this only guards a future caller that doesn't.\n if (query === \"\") return true;\n return Object.values(item).some((value) => {\n const text = fieldTextOrNull(value);\n return text !== null && text.toLowerCase().includes(query);\n });\n}\n","// Composite cell keys for the list table's per-cell state maps, plus the\n// load-time \"which enum cells were empty\" snapshot the inline enum dropdown\n// reads. Pure and framework-free so the view keeps only the reactive refs.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** The schema slice `snapshotEmptyEnums` reads — minimal structural shape so\n * both the client and server collection-schema types satisfy it as-is. */\nexport interface EnumSnapshotSchema {\n fields: Record<string, { type: string }>;\n primaryKey: string;\n}\n\n/** Stable key for one cell (`<rowId>:<fieldKey>`) in the per-cell state maps. */\nexport function cellKey(rowId: string, fieldKey: string): string {\n return `${rowId}:${fieldKey}`;\n}\n\n/** The set of enum cells that were empty in the freshly-fetched records — the\n * only cells whose inline dropdown offers an empty option. */\nexport function snapshotEmptyEnums(schema: EnumSnapshotSchema, records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = fieldText(record[schema.primaryKey]);\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n","// Shared chip/label resolution for the collection calendar surfaces (month\n// grid + day view), so both label a record identically. Pure, type-only\n// dependency on the schema shape.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem, CollectionSchema } from \"./schema\";\n\n// Text-like field types that make a sensible human-readable label.\nconst LABEL_FIELD_TYPES = new Set([\"string\", \"text\", \"markdown\", \"email\"]);\n\n/** Which field labels a record: the schema's explicit `displayField`, else the\n * first non-primary text-like field (so a collection without a displayField\n * shows e.g. the title rather than the opaque primaryKey), else null → the\n * caller falls back to the primary-key value. */\nexport function labelFieldFor(schema: CollectionSchema): string | null {\n if (schema.displayField) return schema.displayField;\n for (const [key, spec] of Object.entries(schema.fields)) {\n if (key !== schema.primaryKey && LABEL_FIELD_TYPES.has(spec.type)) return key;\n }\n return null;\n}\n\n/** A record's primary-key value as a string. */\nexport function itemIdOf(item: CollectionItem, schema: CollectionSchema): string {\n return fieldText(item[schema.primaryKey]);\n}\n\n/** A record's display label: the resolved `labelField` value, else the\n * primary key. Pass the result of `labelFieldFor(schema)` as `labelField`\n * (compute it once per render rather than per item). */\nexport function itemLabelOf(item: CollectionItem, schema: CollectionSchema, labelField: string | null): string {\n if (labelField) {\n const value = item[labelField];\n // Accept any primitive (string / number / boolean) so a numeric or boolean\n // display field still labels the record; objects/arrays fall through to the\n // id rather than rendering as \"[object Object]\".\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n const text = String(value);\n if (text.length > 0) return text;\n }\n }\n return itemIdOf(item, schema);\n}\n","/**\n * 8-char hex id — short, slug-safe, and editable. Produces the same id *shape*\n * as the server's `generateItemId()` (8 hex chars) so a UI-created collection\n * record looks like one the server would have generated for a form submitted\n * with a blank primary key. The source of randomness differs (UUID-derived here\n * vs `randomBytes` on the server); only the shape is intentionally shared.\n */\nexport function shortHexId(): string {\n return crypto.randomUUID().replace(/-/g, \"\").slice(0, 8);\n}\n","// Pure builder for the workspace ontology graph — the `/collections` Map\n// panel (phase 2 of plans/done/collection-ontology.md step ①; plan:\n// plans/done/feat-collection-ontology-graph.md). Consumes the per-collection\n// ontology entries `buildWorkspaceOntology` derives server-side and turns\n// them into a node/edge structure a graph view can render directly.\n// Browser-safe (no `node:*` imports) so the Vue plugin builds the same\n// graph from the raw entries — same parity discipline as `deriveAll` and\n// the sibling `wiki/graph.ts`.\n\n/** One relationship a schema declares: a `ref` (the record stores the\n * target's primaryKey slug) or `embed` (display-only pull) pointing at\n * collection `to`, or `backlinks` / `rollup` (display-only REVERSE\n * refs — `to` is the relation's source collection, i.e. its `from`,\n * and `via` names the exact ref field there being reversed).\n * A `ref` column inside a `table` field is reported with a dotted path\n * (`lines.clientId`). Whether `to` exists is NOT checked — resolution\n * is fail-soft at render. */\nexport interface OntologyRelation {\n field: string;\n kind: \"ref\" | \"embed\" | \"backlinks\" | \"rollup\";\n to: string;\n via?: string;\n}\n\nexport interface CollectionOntologyEntry {\n slug: string;\n title: string;\n icon: string;\n primaryKey: string;\n /** The effective display field: the schema's `displayField`, falling\n * back to the primaryKey exactly as render-time labelling does. */\n displayField: string;\n /** Records in the collection, or `null` when the backend couldn't be\n * reached to count them (an engine that failed to load, a closed\n * session). Deliberately not 0: an unreachable collection reported as\n * empty invites \"restoring\" data that is intact but out of reach. */\n recordCount: number | null;\n relations: OntologyRelation[];\n}\n\nexport interface OntologyGraphNode {\n slug: string;\n title: string;\n icon: string;\n recordCount: number | null;\n /** No discovered collection has this slug — the node exists only\n * because a relation points at it. Rendered as a ghost so the graph\n * doubles as a broken-ref lint surface (lint, not lock). */\n missing?: boolean;\n}\n\nexport interface OntologyGraphEdge {\n /** Tail: the collection whose records store (`ref`) or pull (`embed`)\n * the link — or, for an uncollapsed reverse relation, the collection\n * whose refs the `backlinks`/`rollup` field aggregates. */\n from: string;\n /** Head: the collection the link points at. */\n to: string;\n /** Field declaring the relation — on `from` for `ref`/`embed`, on\n * `to` for an uncollapsed `backlinks`/`rollup`. */\n field: string;\n kind: OntologyRelation[\"kind\"];\n /** `backlinks`/`rollup` field names on `to` that declare the reverse\n * of this `ref` edge, collapsed here so one link never draws twice. */\n reverseFields?: string[];\n}\n\nexport interface OntologyGraph {\n nodes: OntologyGraphNode[];\n edges: OntologyGraphEdge[];\n}\n\nconst isForwardKind = (kind: OntologyRelation[\"kind\"]): boolean => kind === \"ref\" || kind === \"embed\";\n\n/** Fold a reverse relation (`backlinks`/`rollup` declared on `owner`)\n * into the edge list: collapse onto the forward `ref` edge whose field\n * is the relation's declared `via` (several refs can link the same\n * pair — matches.homeTeam vs matches.awayTeam — so the pair alone is\n * ambiguous), else emit its own edge oriented in true data direction\n * (refs' source → owner). A `via` naming no forward edge stays\n * uncollapsed — fail-soft, like every other dangling reference. */\nconst addReverseRelation = (edges: OntologyGraphEdge[], owner: string, rel: OntologyRelation): void => {\n const forward = edges.find((edge) => edge.kind === \"ref\" && edge.from === rel.to && edge.to === owner && (rel.via === undefined || edge.field === rel.via));\n if (forward) {\n forward.reverseFields = [...(forward.reverseFields ?? []), rel.field];\n return;\n }\n edges.push({ from: rel.to, to: owner, field: rel.field, kind: rel.kind });\n};\n\n/** Two passes so every forward `ref` edge exists before any reverse\n * relation tries to collapse onto one — entry order must not matter. */\nconst collectEdges = (entries: CollectionOntologyEntry[]): OntologyGraphEdge[] => {\n const edges: OntologyGraphEdge[] = [];\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (isForwardKind(rel.kind)) edges.push({ from: entry.slug, to: rel.to, field: rel.field, kind: rel.kind });\n }\n }\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (!isForwardKind(rel.kind)) addReverseRelation(edges, entry.slug, rel);\n }\n }\n return edges;\n};\n\n/** Slugs referenced by an edge endpoint but backed by no discovered\n * collection, slug-sorted for deterministic node order. */\nconst ghostSlugs = (edges: OntologyGraphEdge[], known: ReadonlySet<string>): string[] => {\n const ghosts = new Set<string>();\n for (const edge of edges) {\n if (!known.has(edge.from)) ghosts.add(edge.from);\n if (!known.has(edge.to)) ghosts.add(edge.to);\n }\n return [...ghosts].sort();\n};\n\n/** Build the renderable graph: one node per discovered collection (in\n * the entries' slug-sorted order) plus ghost nodes for dangling\n * targets, one edge per underlying link. */\nexport const buildOntologyGraph = (entries: CollectionOntologyEntry[]): OntologyGraph => {\n const edges = collectEdges(entries);\n const known = new Set(entries.map((entry) => entry.slug));\n const nodes: OntologyGraphNode[] = entries.map(({ slug, title, icon, recordCount }) => ({ slug, title, icon, recordCount }));\n for (const slug of ghostSlugs(edges, known)) {\n // A ghost node has no collection behind it at all, so it genuinely holds\n // no records — 0, not the \"couldn't count\" null.\n nodes.push({ slug, title: slug, icon: \"\", recordCount: 0, missing: true });\n }\n return { nodes, edges };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/collection/core/presentCollection.ts","../../src/collection/core/enumColors.ts","../../src/collection/core/draft.ts","../../src/collection/core/chatSeed.ts","../../src/collection/core/sortItems.ts","../../src/collection/core/sortValueOf.ts","../../src/collection/core/textSearch.ts","../../src/collection/core/recordKeys.ts","../../src/collection/core/itemLabel.ts","../../src/collection/core/shortHexId.ts","../../src/collection/core/ontologyGraph.ts"],"sourcesContent":["// presentCollection tool — definition + pure server-side executor.\n//\n// Isomorphic (no Vue, no Node): bundled to the browser as the plugin's\n// `execute`, and run server-side via the host's plugin dispatch route. The\n// executor only validates + echoes the addressing; the live schema + items\n// are fetched client-side by the View through the host's /api/collections\n// routes, so a bad slug surfaces as the View's \"not found\" state.\n\nimport type { ToolContext, ToolDefinition, ToolResult } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentCollection\";\n\n/** Render payload carried in the tool result's `data` field; the View mounts\n * off these. Same shape as the tool args. */\nexport interface PresentCollectionData {\n /** Slug of the collection to display (e.g. \"clients\", \"invoices\"). */\n collectionSlug: string;\n /** Optional primary-key value of a single item to open on mount. */\n itemId?: string;\n}\n\nexport type PresentCollectionArgs = PresentCollectionData;\n\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description:\n \"Display a schema-driven collection inline in the chat as an interactive, editable card. Shows the collection's list of records. Pass `itemId` to open one specific record on mount.\",\n parameters: {\n type: \"object\",\n properties: {\n collectionSlug: {\n type: \"string\",\n description: \"The slug of the collection to display (e.g. 'clients', 'invoices', 'contacts').\",\n },\n itemId: {\n type: \"string\",\n description: \"Optional primary-key value of a single record to open in detail view on mount. Omit to show the full list.\",\n },\n },\n required: [\"collectionSlug\"],\n },\n prompt: `After making changes to schema-driven collections, use ${TOOL_NAME} to present either the collection or the item`,\n};\n\nexport const executePresentCollection = async (\n _context: ToolContext,\n args: PresentCollectionArgs,\n): Promise<ToolResult<PresentCollectionData, PresentCollectionData>> => {\n const collectionSlug = typeof args?.collectionSlug === \"string\" ? args.collectionSlug.trim() : \"\";\n if (!collectionSlug) {\n return {\n message: \"presentCollection error: collectionSlug is required\",\n instructions: \"Tell the user you couldn't display the collection because no collection was specified, and ask which collection they mean.\",\n };\n }\n const itemId = typeof args.itemId === \"string\" && args.itemId.trim().length > 0 ? args.itemId.trim() : undefined;\n const data: PresentCollectionData = itemId ? { collectionSlug, itemId } : { collectionSlug };\n const target = itemId ? `${collectionSlug} / ${itemId}` : collectionSlug;\n return {\n message: `Presented collection ${target}`,\n // `data` is the view's source (also the host's render-eligibility signal);\n // `jsonData` is what the LLM sees. Same payload, two audiences.\n data,\n jsonData: data,\n // Phrased to stay correct for BOTH storage kinds without loading the\n // schema (this executor is deliberately pure/isomorphic): a writable\n // collection offers edit/create/delete in the card; a read-only\n // `dataSource` collection hides those and changes flow through its\n // data file instead.\n instructions:\n \"The collection has been presented to the user as an interactive card. They can browse and open records directly; on a writable collection they can also edit, create, and delete (a read-only dataSource collection shows no edit controls — its records change by editing the backing data file). No further action is needed unless they ask.\",\n };\n};\n","// A standard, ordered colour palette for `enum` field values, shared by every\n// collection surface (list, calendar, kanban). Each value in an\n// enum's `values` array is assigned the palette entry at its index — cycling\n// when an enum declares more values than the palette holds — so colouring is\n// automatic and consistent without any per-value schema config.\n//\n// Tailwind only detects class names that appear as complete string literals,\n// so every surface's classes are spelled out per colour below rather than\n// built from a colour name at runtime.\n//\n// One enum is special: the field a schema's `notifyWhen` targets (the\n// \"notification enum\"). Its flagged values read the notification severity\n// colours — the first flagged value (most urgent) red, the rest amber — and\n// every other value reads neutral grey, mirroring the notification bell\n// (red = urgent, amber = nudge) rather than the rotating palette.\n// `resolveEnumColor` encapsulates that rule.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionSchema } from \"./schema\";\n\nexport interface EnumColorClasses {\n /** Stat-card style: border + fill + text + hover. */\n card: string;\n /** Small status dot (kanban column header). */\n dot: string;\n /** Pill / badge / inline `<select>` fill + text (no border width). */\n badge: string;\n /** Border colour, paired with a `border` width class by the caller. */\n border: string;\n}\n\n// The palette deliberately EXCLUDES the warm warning band (red / orange /\n// amber): those are reserved for the notification-enum severity colours\n// (`ENUM_ALERT` red, `ENUM_NUDGE` amber), so a normal enum value can never\n// draw a colour that reads like a notification. Eight well-separated cool /\n// green / magenta hues keep enough variety for wide enums.\nconst PALETTE: readonly EnumColorClasses[] = [\n {\n card: \"border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100\",\n dot: \"bg-indigo-500\",\n badge: \"bg-indigo-100 text-indigo-700\",\n border: \"border-indigo-200\",\n },\n { card: \"border-sky-200 bg-sky-50 text-sky-600 hover:bg-sky-100\", dot: \"bg-sky-500\", badge: \"bg-sky-100 text-sky-700\", border: \"border-sky-200\" },\n { card: \"border-cyan-200 bg-cyan-50 text-cyan-600 hover:bg-cyan-100\", dot: \"bg-cyan-500\", badge: \"bg-cyan-100 text-cyan-700\", border: \"border-cyan-200\" },\n { card: \"border-teal-200 bg-teal-50 text-teal-600 hover:bg-teal-100\", dot: \"bg-teal-500\", badge: \"bg-teal-100 text-teal-700\", border: \"border-teal-200\" },\n {\n card: \"border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100\",\n dot: \"bg-emerald-500\",\n badge: \"bg-emerald-100 text-emerald-700\",\n border: \"border-emerald-200\",\n },\n { card: \"border-lime-200 bg-lime-50 text-lime-600 hover:bg-lime-100\", dot: \"bg-lime-500\", badge: \"bg-lime-100 text-lime-700\", border: \"border-lime-200\" },\n {\n card: \"border-violet-200 bg-violet-50 text-violet-600 hover:bg-violet-100\",\n dot: \"bg-violet-500\",\n badge: \"bg-violet-100 text-violet-700\",\n border: \"border-violet-200\",\n },\n {\n card: \"border-fuchsia-200 bg-fuchsia-50 text-fuchsia-600 hover:bg-fuchsia-100\",\n dot: \"bg-fuchsia-500\",\n badge: \"bg-fuchsia-100 text-fuchsia-700\",\n border: \"border-fuchsia-200\",\n },\n];\n\n/** Neutral styling for the empty / Uncategorized bucket — never a palette\n * colour, so an unset or unknown value reads grey across every surface. */\nexport const ENUM_NEUTRAL: EnumColorClasses = {\n card: \"border-slate-200 bg-slate-50 text-slate-500 hover:bg-slate-100\",\n dot: \"bg-slate-300\",\n badge: \"bg-slate-100 text-slate-500\",\n border: \"border-slate-200\",\n};\n\n/** The urgent notification colour (red), matching the bell's `urgent`\n * severity. The first value a schema's `notifyWhen` flags reads this. */\nexport const ENUM_ALERT: EnumColorClasses = {\n card: \"border-red-200 bg-red-50 text-red-600 hover:bg-red-100\",\n dot: \"bg-red-500\",\n badge: \"bg-red-100 text-red-700\",\n border: \"border-red-200\",\n};\n\n/** The nudge notification colour (amber), matching the bell's `nudge`\n * severity. Flagged `notifyWhen` values after the first read this. */\nexport const ENUM_NUDGE: EnumColorClasses = {\n card: \"border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100\",\n dot: \"bg-amber-500\",\n badge: \"bg-amber-100 text-amber-700\",\n border: \"border-amber-200\",\n};\n\n/** Classes for the enum value at `index` in its field's `values` array. A\n * negative index (value unset or not among the declared values) reads\n * neutral. */\nexport function enumColorClasses(index: number): EnumColorClasses {\n if (index < 0) return ENUM_NEUTRAL;\n return PALETTE[index % PALETTE.length] ?? ENUM_NEUTRAL;\n}\n\n/** Index of `value` within an enum field's declared `values`, or -1 when the\n * value is empty / unknown (→ neutral). */\nexport function enumValueIndex(values: readonly string[] | undefined, value: unknown): number {\n // No text form (array/object) means no enum member — \"[object Object]\" must\n // not index into the palette.\n const text = fieldTextOrNull(value);\n if (text === null || text === \"\") return -1;\n return values?.indexOf(text) ?? -1;\n}\n\n/** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the\n * \"notification enum\"); undefined for every other field. */\nfunction notifyValuesFor(schema: CollectionSchema, fieldKey: string): readonly string[] | undefined {\n const spec = schema.notifyWhen;\n return spec && spec.field === fieldKey ? spec.in : undefined;\n}\n\n/** Resolve a value's colour for enum field `fieldKey`:\n * - Notification enum (`notifyWhen` targets it): the first flagged value (in\n * `notifyWhen.in` order, the most urgent) reads notification red, the rest\n * amber, and every non-flagged value neutral grey.\n * - Any other enum: the standard palette by the value's declared index. */\nexport function resolveEnumColor(schema: CollectionSchema, fieldKey: string, value: unknown): EnumColorClasses {\n const notifyValues = notifyValuesFor(schema, fieldKey);\n if (notifyValues) {\n const str = fieldText(value);\n const rank = notifyValues.indexOf(str);\n if (rank < 0) return ENUM_NEUTRAL;\n return rank === 0 ? ENUM_ALERT : ENUM_NUDGE;\n }\n const spec = schema.fields[fieldKey];\n return enumColorClasses(enumValueIndex(spec?.type === \"enum\" ? spec.values : undefined, value));\n}\n","// Pure edit-draft logic for the collection record form, extracted from\n// CollectionView.vue so the parent (build draft / save / validate) and\n// the extracted CollectionRecordPanel (per-row table mutators) share one\n// implementation. No Vue, no I/O — every function maps a draft + schema\n// to a value, so the omission/validation semantics are unit-testable.\n\nimport { fieldText } from \"./fieldText\";\nimport { fieldVisible } from \"./actionVisible\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType, CollectionItem, CollectionSchema } from \"./schema\";\nimport type { EditState, TableRowDraft } from \"./uiTypes\";\n\n/** Build a row draft, reading each sub-field's persisted value via\n * `readSub`. An empty row passes a reader that always returns\n * `undefined`; a row-from-item reads the source record. Boolean\n * presence uses `typeof raw === \"boolean\"` so an existing explicit\n * `false` is recorded as present and round-trips on a no-op save. */\nfunction buildTableRowDraft(subFields: Record<string, FieldSpec>, readSub: (subKey: string) => unknown): TableRowDraft {\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n const raw = readSub(subKey);\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = fieldText(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\n}\n\n/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, () => undefined);\n}\n\n/** Build a row draft from an existing persisted row. */\nexport function rowFromItem(item: Record<string, unknown>, subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, (subKey) => item[subKey]);\n}\n\n/** Own-property boolean read: a field named `toString` / `constructor` /\n * `__proto__` must read `false` here, not inherit a truthy Object.prototype\n * member and force an untouched boolean into every saved record. */\nfunction ownFlag(flags: Record<string, boolean>, key: string): boolean {\n return Object.hasOwn(flags, key) && flags[key] === true;\n}\n\n/** Decide whether a boolean field's draft value should be emitted (vs.\n * omitted so a downstream default applies). */\nfunction shouldEmitBoolean(state: EditState, key: string, field: FieldSpec): boolean {\n return ownFlag(state.boolOriginallyPresent, key) || ownFlag(state.boolTouched, key) || Boolean(field.required);\n}\n\n/** Convert a scalar draft slot to its persisted form. `undefined` = omit. */\nfunction scalarDraftToValue(raw: string | undefined, fieldType: FieldType): unknown {\n if (raw === undefined || raw === \"\") return undefined;\n if (fieldType === \"number\" || fieldType === \"money\") {\n const num = Number(raw);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\n/** Convert one table row draft to its persisted record. */\nfunction rowDraftToRecord(rowDraft: TableRowDraft, subFields: Record<string, FieldSpec>): Record<string, unknown> {\n const row: Record<string, unknown> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n if (subField.type === \"boolean\") {\n const value = ownFlag(rowDraft.bool, subKey);\n if (ownFlag(rowDraft.boolOriginallyPresent, subKey) || ownFlag(rowDraft.boolTouched, subKey) || value || subField.required) row[subKey] = value;\n continue;\n }\n const value = scalarDraftToValue(rowDraft.text[subKey], subField.type);\n if (value !== undefined) row[subKey] = value;\n }\n return row;\n}\n\n/** Convert a full edit draft to the record to persist. */\nexport function draftToRecord(state: EditState, schema: CollectionSchema): CollectionItem {\n const record: CollectionItem = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (COMPUTED_TYPES.has(field.type)) continue; // never persisted (toggle projects an enum field)\n if (field.type === \"boolean\") {\n if (shouldEmitBoolean(state, key, field)) record[key] = ownFlag(state.bool, key);\n continue;\n }\n if (field.type === \"table\" && field.of) {\n const subFields = field.of;\n record[key] = (state.table[key] ?? []).map((rowDraft) => rowDraftToRecord(rowDraft, subFields));\n continue;\n }\n const value = scalarDraftToValue(state.text[key], field.type);\n if (value !== undefined) record[key] = value;\n }\n return record;\n}\n\n/** Normalise a raw inline-edit input (table-cell checkbox/select) to its\n * persisted form. Mirrors `draftToRecord`'s boolean strictness; an empty\n * enum selection (the placeholder option) clears the field via `undefined`. */\nexport function coerceInlineValue(field: FieldSpec, raw: boolean | string): unknown {\n if (field.type === \"boolean\") return raw === true;\n return raw === \"\" ? undefined : raw;\n}\n\n/** Build the full record to PUT for a single-cell inline edit, without\n * mutating `item`. A `undefined` value omits the key (enum cleared),\n * matching `draftToRecord`'s omission semantics. */\nexport function buildUpdatedRecord(item: CollectionItem, key: string, value: unknown): CollectionItem {\n if (value === undefined) {\n const { [key]: __omit, ...rest } = item;\n return rest;\n }\n return { ...item, [key]: value };\n}\n\n/** Empty for required-field validation — NOT a truthiness check (a\n * numeric `0` is a filled value). */\nfunction isMissingDraftValue(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/** Label of the first required table sub-field empty in any row, else null. */\nfunction firstMissingTableSubField(field: FieldSpec, rows: TableRowDraft[] | undefined): string | null {\n if (field.type !== \"table\" || !rows) return null;\n for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {\n const row = rows[rowIdx];\n for (const [subKey, subField] of Object.entries(field.of)) {\n if (!subField.required || subField.type === \"boolean\") continue;\n if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;\n }\n }\n return null;\n}\n\n/** Field types the required check never flags: booleans always hold a\n * value, and the computed/projected kinds (COMPUTED_TYPES — the shared\n * set, so a future computed type can't drift past this check) have no\n * draft input at all. */\nconst NEVER_MISSING_TYPES: ReadonlySet<FieldType> = new Set<FieldType>([\"boolean\", ...COMPUTED_TYPES]);\n\nfunction validateOneField(key: string, field: FieldSpec, draft: EditState, record: CollectionItem): string | null {\n // A `when`-hidden field has no input the user can fill — never missing.\n if (!fieldVisible(field, record)) return null;\n if (field.type === \"table\" && field.of) {\n const rows = draft.table[key];\n if (field.required && (!rows || rows.length === 0)) return field.label;\n return firstMissingTableSubField(field, rows);\n }\n if (!field.required) return null;\n if (draft.mode === \"create\" && field.primary === true) return null; // server auto-generates id\n if (NEVER_MISSING_TYPES.has(field.type)) return null;\n return isMissingDraftValue(draft.text[key]) ? field.label : null;\n}\n\n/** Human-readable label of the first missing required field, or null. */\nexport function firstMissingRequiredField(draft: EditState, schema: CollectionSchema): string | null {\n const record = draftToRecord(draft, schema);\n for (const [key, field] of Object.entries(schema.fields)) {\n const missing = validateOneField(key, field, draft, record);\n if (missing) return missing;\n }\n return null;\n}\n","// The slash-command chat seed a collection view sends to the agent when the\n// user starts a chat from a skill-backed collection or one of its records.\n// Skill-backed collections have a `/<slug>` command, so seed that. (Feeds are\n// skill-less and seeded differently; that branch stays in the view because it\n// is i18n-driven.)\n\n/** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific\n * record is addressed. The `id=` selector goes BEFORE the free-text message so\n * the skill's argument parser reads it as a flag rather than as prose. */\nexport function skillCommandSeed(slug: string, message: string, itemId?: string): string {\n return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;\n}\n","// Sorting for the collection list table. The header sort toggle (next to\n// each field title) cycles a single active column through none → asc →\n// desc → none; this module turns that state plus a per-field value\n// extractor into an ordered copy of the rows.\n//\n// Two invariants the comparator guarantees:\n// 1. Rows with an empty/missing value for the sorted field always sink\n// to the bottom, regardless of direction.\n// 2. Ties (and the empty group) keep their original order — the sort is\n// stable via the captured source index.\n//\n// Field-type → comparable mapping (see `isSortableField`):\n// string/text/email → string · number/money → numeric ·\n// date/datetime → epoch-ms · enum → declared-index · boolean/toggle/flag →\n// false<true · ref → display label · derived → its display type.\n// markdown/table/image/file/embed get no sort button.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem, CollectionFieldSpec, CollectionFieldType } from \"./schema\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface SortState {\n /** Field key of the single active sort column. */\n field: string;\n direction: SortDirection;\n}\n\n/** A row's comparable value for the active field. Exactly one of `num` /\n * `str` is set when not empty; `empty` rows always sort last. */\nexport interface SortValue {\n empty: boolean;\n num?: number;\n str?: string;\n}\n\nconst EMPTY: SortValue = { empty: true };\n\n/** Field types that render no value text in the table, so offer no sort. */\nconst NON_SORTABLE: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"markdown\", \"table\", \"image\", \"file\", \"embed\", \"backlinks\", \"rollup\"]);\n\nexport function isSortableField(field: CollectionFieldSpec): boolean {\n return !NON_SORTABLE.has(field.type);\n}\n\n/** Cycle one column's state: none → asc → desc → none. */\nexport function nextSortDirection(current: SortDirection | null): SortDirection | null {\n if (current === null) return \"asc\";\n if (current === \"asc\") return \"desc\";\n return null;\n}\n\n// ── SortValue constructors (one per comparable kind) ────────────────\n\nexport function numericSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) ? { empty: false, num } : EMPTY;\n}\n\nexport function stringSortValue(raw: unknown): SortValue {\n // An array/object has no sort key — treat it as empty (sorts last) instead of\n // grouping every such record together under \"[object Object]\".\n const str = fieldTextOrNull(raw);\n if (str === null) return EMPTY;\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const epoch = Date.parse(text);\n // Unparseable dates fall back to a lexical compare rather than vanishing.\n return Number.isNaN(epoch) ? stringSortValue(raw) : { empty: false, num: epoch };\n}\n\n/** Enum sorts by the value's index in the declared `values` list. A value\n * outside the list (or unset) is treated as empty → last. */\nexport function enumSortValue(values: readonly string[] | undefined, raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const idx = values ? values.indexOf(text) : -1;\n return idx < 0 ? EMPTY : { empty: false, num: idx };\n}\n\n/** Boolean / toggle: false < true, never empty (unset reads as false). */\nexport function boolSortValue(checked: boolean): SortValue {\n return { empty: false, num: checked ? 1 : 0 };\n}\n\n// ── Comparator + driver ─────────────────────────────────────────────\n\nexport function compareSortValues(left: SortValue, right: SortValue): number {\n if (left.num !== undefined && right.num !== undefined) return left.num - right.num;\n const leftStr = left.str ?? String(left.num ?? \"\");\n const rightStr = right.str ?? String(right.num ?? \"\");\n return leftStr.localeCompare(rightStr);\n}\n\n/** Stable sort of `items` by `valueOf`. Empties always last; ties hold\n * source order. Returns a new array (does not mutate `items`). */\nexport function sortItems(items: readonly CollectionItem[], direction: SortDirection, valueOf: (item: CollectionItem) => SortValue): CollectionItem[] {\n const dir = direction === \"asc\" ? 1 : -1;\n return items\n .map((item, index) => ({ item, index, sv: valueOf(item) }))\n .sort((left, right) => {\n if (left.sv.empty || right.sv.empty) {\n if (left.sv.empty && right.sv.empty) return left.index - right.index;\n return left.sv.empty ? 1 : -1;\n }\n const base = compareSortValues(left.sv, right.sv);\n return base !== 0 ? base * dir : left.index - right.index;\n })\n .map((decorated) => decorated.item);\n}\n","// Field-type → comparable value for the collection list-table sort. Splits\n// the header sort's per-row value extraction out of the Vue view so the\n// dispatch (which field type maps to which SortValue constructor) is testable\n// on its own. The comparators live in `./sortItems`; this module only routes.\n//\n// A few field kinds can't be sorted from the raw cell alone — `toggle` and\n// `flag` are projected booleans, `derived` is a formula, and `ref` sorts by\n// its resolved display label. Those readers live in the view's rendering\n// composable, so they are passed in (dependency injection) rather than\n// imported, keeping this module pure and framework-free.\n\nimport { numericSortValue, stringSortValue, dateSortValue, enumSortValue, boolSortValue, type SortValue } from \"./sortItems\";\nimport type { CollectionItem, CollectionFieldSpec as FieldSpec } from \"./schema\";\n\n/** Resolve a `ref` cell to its human display label (target slug + item id). */\nexport type RefDisplayResolver = (targetSlug: string, itemSlug: string) => string;\n\n/** The row readers the view supplies for the field kinds that can't sort off\n * the raw cell value. All come from the rendering composable. */\nexport interface SortValueDeps {\n toggleChecked: (item: CollectionItem, field: FieldSpec) => boolean;\n flagValueOf: (key: string, item: CollectionItem) => boolean;\n evaluateDerived: (field: FieldSpec, key: string, item: CollectionItem) => number | null;\n deriveRecord: (item: CollectionItem) => CollectionItem;\n resolveRefDisplay: RefDisplayResolver;\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nexport function scalarSortValue(field: FieldSpec, raw: unknown, resolveRefDisplay: RefDisplayResolver): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(resolveRefDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date → epoch, anything else → the enriched value as a string. */\nexport function derivedSortValue(\n field: FieldSpec,\n key: string,\n item: CollectionItem,\n deps: Pick<SortValueDeps, \"evaluateDerived\" | \"deriveRecord\">,\n): SortValue {\n const display = field.type === \"derived\" ? field.display : undefined;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(deps.evaluateDerived(field, key, item));\n }\n const enriched = deps.deriveRecord(item);\n if (display === \"date\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\n/** Comparable value for one row under the active field. Toggle, flag, and\n * derived need the whole record; every other type keys off the raw cell. */\nexport function sortValueOf(field: FieldSpec, key: string, item: CollectionItem, deps: SortValueDeps): SortValue {\n if (field.type === \"toggle\") return boolSortValue(deps.toggleChecked(item, field));\n if (field.type === \"flag\") return boolSortValue(deps.flagValueOf(key, item));\n if (field.type === \"derived\") return derivedSortValue(field, key, item, deps);\n return scalarSortValue(field, item[key], deps.resolveRefDisplay);\n}\n","// Free-text record matching for the collection list search box. Pure and\n// framework-free so the view's `filteredItems` computed just calls it.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object/array-valued fields (table rows, nested records) have no text\n * form (`fieldTextOrNull` → `null`) and are skipped — they don't render as\n * searchable text in the list table.\n *\n * `query` is matched against each cell's lower-cased text but is NOT\n * lower-cased here: callers pass an already-normalised (trimmed +\n * lower-cased) query, so an upper-case `query` intentionally matches\n * nothing. Keeping the normalisation at the call site avoids re-lowering\n * the same query once per row. */\nexport function itemMatchesQuery(item: CollectionItem, query: string): boolean {\n // An empty query matches every record — otherwise a row whose only fields are\n // object/array-valued (no scalar cell reaches `includes(\"\")`) would vanish\n // when the search box is cleared. The list caller already short-circuits the\n // empty case, so this only guards a future caller that doesn't.\n if (query === \"\") return true;\n return Object.values(item).some((value) => {\n const text = fieldTextOrNull(value);\n return text !== null && text.toLowerCase().includes(query);\n });\n}\n","// Composite cell keys for the list table's per-cell state maps, plus the\n// load-time \"which enum cells were empty\" snapshot the inline enum dropdown\n// reads. Pure and framework-free so the view keeps only the reactive refs.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** The schema slice `snapshotEmptyEnums` reads — minimal structural shape so\n * both the client and server collection-schema types satisfy it as-is. */\nexport interface EnumSnapshotSchema {\n fields: Record<string, { type: string }>;\n primaryKey: string;\n}\n\n/** Stable key for one cell (`<rowId>:<fieldKey>`) in the per-cell state maps. */\nexport function cellKey(rowId: string, fieldKey: string): string {\n return `${rowId}:${fieldKey}`;\n}\n\n/** A row's stable identity: the primary-key value as text (`\"\"` when the schema\n * has no primary key, or the value is absent). Every per-row / per-cell state\n * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses\n * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the\n * empty-enum snapshot always agree. A real primary key is a scalar, so this\n * matches its plain string form. */\nexport function rowIdOf(primaryKey: string | undefined, item: CollectionItem): string {\n return primaryKey ? fieldText(item[primaryKey]) : \"\";\n}\n\n/** The set of enum cells that were empty in the freshly-fetched records — the\n * only cells whose inline dropdown offers an empty option. */\nexport function snapshotEmptyEnums(schema: EnumSnapshotSchema, records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = fieldText(record[schema.primaryKey]);\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n","// Shared chip/label resolution for the collection calendar surfaces (month\n// grid + day view), so both label a record identically. Pure, type-only\n// dependency on the schema shape.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem, CollectionSchema } from \"./schema\";\n\n// Text-like field types that make a sensible human-readable label.\nconst LABEL_FIELD_TYPES = new Set([\"string\", \"text\", \"markdown\", \"email\"]);\n\n/** Which field labels a record: the schema's explicit `displayField`, else the\n * first non-primary text-like field (so a collection without a displayField\n * shows e.g. the title rather than the opaque primaryKey), else null → the\n * caller falls back to the primary-key value. */\nexport function labelFieldFor(schema: CollectionSchema): string | null {\n if (schema.displayField) return schema.displayField;\n for (const [key, spec] of Object.entries(schema.fields)) {\n if (key !== schema.primaryKey && LABEL_FIELD_TYPES.has(spec.type)) return key;\n }\n return null;\n}\n\n/** A record's primary-key value as a string. */\nexport function itemIdOf(item: CollectionItem, schema: CollectionSchema): string {\n return fieldText(item[schema.primaryKey]);\n}\n\n/** A record's display label: the resolved `labelField` value, else the\n * primary key. Pass the result of `labelFieldFor(schema)` as `labelField`\n * (compute it once per render rather than per item). */\nexport function itemLabelOf(item: CollectionItem, schema: CollectionSchema, labelField: string | null): string {\n if (labelField) {\n const value = item[labelField];\n // Accept any primitive (string / number / boolean) so a numeric or boolean\n // display field still labels the record; objects/arrays fall through to the\n // id rather than rendering as \"[object Object]\".\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n const text = String(value);\n if (text.length > 0) return text;\n }\n }\n return itemIdOf(item, schema);\n}\n","/**\n * 8-char hex id — short, slug-safe, and editable. Produces the same id *shape*\n * as the server's `generateItemId()` (8 hex chars) so a UI-created collection\n * record looks like one the server would have generated for a form submitted\n * with a blank primary key. The source of randomness differs (UUID-derived here\n * vs `randomBytes` on the server); only the shape is intentionally shared.\n */\nexport function shortHexId(): string {\n return crypto.randomUUID().replace(/-/g, \"\").slice(0, 8);\n}\n","// Pure builder for the workspace ontology graph — the `/collections` Map\n// panel (phase 2 of plans/done/collection-ontology.md step ①; plan:\n// plans/done/feat-collection-ontology-graph.md). Consumes the per-collection\n// ontology entries `buildWorkspaceOntology` derives server-side and turns\n// them into a node/edge structure a graph view can render directly.\n// Browser-safe (no `node:*` imports) so the Vue plugin builds the same\n// graph from the raw entries — same parity discipline as `deriveAll` and\n// the sibling `wiki/graph.ts`.\n\n/** One relationship a schema declares: a `ref` (the record stores the\n * target's primaryKey slug) or `embed` (display-only pull) pointing at\n * collection `to`, or `backlinks` / `rollup` (display-only REVERSE\n * refs — `to` is the relation's source collection, i.e. its `from`,\n * and `via` names the exact ref field there being reversed).\n * A `ref` column inside a `table` field is reported with a dotted path\n * (`lines.clientId`). Whether `to` exists is NOT checked — resolution\n * is fail-soft at render. */\nexport interface OntologyRelation {\n field: string;\n kind: \"ref\" | \"embed\" | \"backlinks\" | \"rollup\";\n to: string;\n via?: string;\n}\n\nexport interface CollectionOntologyEntry {\n slug: string;\n title: string;\n icon: string;\n primaryKey: string;\n /** The effective display field: the schema's `displayField`, falling\n * back to the primaryKey exactly as render-time labelling does. */\n displayField: string;\n /** Records in the collection, or `null` when the backend couldn't be\n * reached to count them (an engine that failed to load, a closed\n * session). Deliberately not 0: an unreachable collection reported as\n * empty invites \"restoring\" data that is intact but out of reach. */\n recordCount: number | null;\n relations: OntologyRelation[];\n}\n\nexport interface OntologyGraphNode {\n slug: string;\n title: string;\n icon: string;\n recordCount: number | null;\n /** No discovered collection has this slug — the node exists only\n * because a relation points at it. Rendered as a ghost so the graph\n * doubles as a broken-ref lint surface (lint, not lock). */\n missing?: boolean;\n}\n\nexport interface OntologyGraphEdge {\n /** Tail: the collection whose records store (`ref`) or pull (`embed`)\n * the link — or, for an uncollapsed reverse relation, the collection\n * whose refs the `backlinks`/`rollup` field aggregates. */\n from: string;\n /** Head: the collection the link points at. */\n to: string;\n /** Field declaring the relation — on `from` for `ref`/`embed`, on\n * `to` for an uncollapsed `backlinks`/`rollup`. */\n field: string;\n kind: OntologyRelation[\"kind\"];\n /** `backlinks`/`rollup` field names on `to` that declare the reverse\n * of this `ref` edge, collapsed here so one link never draws twice. */\n reverseFields?: string[];\n}\n\nexport interface OntologyGraph {\n nodes: OntologyGraphNode[];\n edges: OntologyGraphEdge[];\n}\n\nconst isForwardKind = (kind: OntologyRelation[\"kind\"]): boolean => kind === \"ref\" || kind === \"embed\";\n\n/** Fold a reverse relation (`backlinks`/`rollup` declared on `owner`)\n * into the edge list: collapse onto the forward `ref` edge whose field\n * is the relation's declared `via` (several refs can link the same\n * pair — matches.homeTeam vs matches.awayTeam — so the pair alone is\n * ambiguous), else emit its own edge oriented in true data direction\n * (refs' source → owner). A `via` naming no forward edge stays\n * uncollapsed — fail-soft, like every other dangling reference. */\nconst addReverseRelation = (edges: OntologyGraphEdge[], owner: string, rel: OntologyRelation): void => {\n const forward = edges.find((edge) => edge.kind === \"ref\" && edge.from === rel.to && edge.to === owner && (rel.via === undefined || edge.field === rel.via));\n if (forward) {\n forward.reverseFields = [...(forward.reverseFields ?? []), rel.field];\n return;\n }\n edges.push({ from: rel.to, to: owner, field: rel.field, kind: rel.kind });\n};\n\n/** Two passes so every forward `ref` edge exists before any reverse\n * relation tries to collapse onto one — entry order must not matter. */\nconst collectEdges = (entries: CollectionOntologyEntry[]): OntologyGraphEdge[] => {\n const edges: OntologyGraphEdge[] = [];\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (isForwardKind(rel.kind)) edges.push({ from: entry.slug, to: rel.to, field: rel.field, kind: rel.kind });\n }\n }\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (!isForwardKind(rel.kind)) addReverseRelation(edges, entry.slug, rel);\n }\n }\n return edges;\n};\n\n/** Slugs referenced by an edge endpoint but backed by no discovered\n * collection, slug-sorted for deterministic node order. */\nconst ghostSlugs = (edges: OntologyGraphEdge[], known: ReadonlySet<string>): string[] => {\n const ghosts = new Set<string>();\n for (const edge of edges) {\n if (!known.has(edge.from)) ghosts.add(edge.from);\n if (!known.has(edge.to)) ghosts.add(edge.to);\n }\n return [...ghosts].sort();\n};\n\n/** Build the renderable graph: one node per discovered collection (in\n * the entries' slug-sorted order) plus ghost nodes for dangling\n * targets, one edge per underlying link. */\nexport const buildOntologyGraph = (entries: CollectionOntologyEntry[]): OntologyGraph => {\n const edges = collectEdges(entries);\n const known = new Set(entries.map((entry) => entry.slug));\n const nodes: OntologyGraphNode[] = entries.map(({ slug, title, icon, recordCount }) => ({ slug, title, icon, recordCount }));\n for (const slug of ghostSlugs(edges, known)) {\n // A ghost node has no collection behind it at all, so it genuinely holds\n // no records — 0, not the \"couldn't count\" null.\n nodes.push({ slug, title: slug, icon: \"\", recordCount: 0, missing: true });\n }\n return { nodes, edges };\n};\n"],"mappings":"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"}
@@ -12,6 +12,7 @@ export * from './core/backlinks';
12
12
  export * from './core/linkTargets';
13
13
  export * from './core/where';
14
14
  export * from './core/completion';
15
+ export * from './core/chatSeed';
15
16
  export * from './core/dynamicIcon';
16
17
  export * from './core/derivedFormula';
17
18
  export * from './core/deriveAll';
@@ -1,7 +1,7 @@
1
1
  import { t as errorMessage } from "../dist-Cwk0e12G.js";
2
- import { a as isSafeSlug, c as FEED_SCHEDULES, d as isFieldDrivenEvery, f as isReadOnlySchema, h as fieldTextOrNull, i as isSafeRecordId, l as INGEST_KINDS, m as fieldText, n as SAFE_SLUG_PATTERN, o as AGENT_INGEST_KIND, p as storageKindFor, r as generateUniqueId, s as COMPUTED_TYPES, t as SAFE_RECORD_ID_PATTERN, u as embedTargetId } from "../ids-Bv6AOW9Z.js";
2
+ import { a as isSafeSlug, c as COMPUTED_TYPES, d as embedTargetId, f as isFieldDrivenEvery, g as fieldTextOrNull, h as fieldText, i as isSafeRecordId, l as FEED_SCHEDULES, m as storageKindFor, n as SAFE_SLUG_PATTERN, o as nextUniqueItemId, p as isReadOnlySchema, r as generateUniqueId, s as AGENT_INGEST_KIND, t as SAFE_RECORD_ID_PATTERN, u as INGEST_KINDS } from "../ids-s4GfmE93.js";
3
3
  import { t as projectRecordFields } from "../project-bU98ycsy.js";
4
- import { A as coerceNumeric, C as completionCoveredByFieldChip, D as uniqueEmbedTargets, E as uniqueBacklinkSources, F as agentActionRunKey, I as fieldVisible, L as whenMatches, M as rollupValue, N as viaMatches, O as uniqueRefTargets, P as actionVisible, S as selectDynamicRecord, T as matchesWhere, _ as resolveRowRefs, a as buildMonthGrid, b as firstDateField, c as daySlice, d as parseIsoDateTime, f as parseTimeRange, g as deriveAll, h as ymdKey, i as bucketRecords, j as projectBacklinkRow, k as backlinkRows, l as monthAnchorDate, m as spanCoversDay, n as MINUTES_PER_DAY, o as compareYmd, p as recordSpan, r as assignLanes, s as dateOf, t as defangForPrompt, u as parseIsoDate, w as itemIsDone, x as resolveIcon, y as evaluateDerived } from "../promptSafety-BNelBKhh.js";
4
+ import { A as uniqueEmbedTargets, B as whenMatches, C as chipMatches, D as toggleChecked, E as itemIsDone, F as rollupValue, I as viaMatches, L as actionVisible, M as backlinkRows, N as coerceNumeric, O as matchesWhere, P as projectBacklinkRow, R as agentActionRunKey, S as selectDynamicRecord, T as flagFieldValue, _ as resolveRowRefs, a as buildMonthGrid, b as firstDateField, c as daySlice, d as parseIsoDateTime, f as parseTimeRange, g as deriveAll, h as ymdKey, i as bucketRecords, j as uniqueRefTargets, k as uniqueBacklinkSources, l as monthAnchorDate, m as spanCoversDay, n as MINUTES_PER_DAY, o as compareYmd, p as recordSpan, r as assignLanes, s as dateOf, t as defangForPrompt, u as parseIsoDate, w as completionCoveredByFieldChip, x as resolveIcon, y as evaluateDerived, z as fieldVisible } from "../promptSafety-CQ5Un4wi.js";
5
5
  //#region src/collection/core/presentCollection.ts
6
6
  var TOOL_NAME = "presentCollection";
7
7
  var TOOL_DEFINITION = {
@@ -306,6 +306,14 @@ function firstMissingRequiredField(draft, schema) {
306
306
  return null;
307
307
  }
308
308
  //#endregion
309
+ //#region src/collection/core/chatSeed.ts
310
+ /** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific
311
+ * record is addressed. The `id=` selector goes BEFORE the free-text message so
312
+ * the skill's argument parser reads it as a flag rather than as prose. */
313
+ function skillCommandSeed(slug, message, itemId) {
314
+ return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
315
+ }
316
+ //#endregion
309
317
  //#region src/collection/core/sortItems.ts
310
318
  var EMPTY = { empty: true };
311
319
  /** Field types that render no value text in the table, so offer no sort. */
@@ -450,6 +458,15 @@ function itemMatchesQuery(item, query) {
450
458
  function cellKey(rowId, fieldKey) {
451
459
  return `${rowId}:${fieldKey}`;
452
460
  }
461
+ /** A row's stable identity: the primary-key value as text (`""` when the schema
462
+ * has no primary key, or the value is absent). Every per-row / per-cell state
463
+ * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses
464
+ * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the
465
+ * empty-enum snapshot always agree. A real primary key is a scalar, so this
466
+ * matches its plain string form. */
467
+ function rowIdOf(primaryKey, item) {
468
+ return primaryKey ? fieldText(item[primaryKey]) : "";
469
+ }
453
470
  /** The set of enum cells that were empty in the freshly-fetched records — the
454
471
  * only cells whose inline dropdown offers an empty option. */
455
472
  function snapshotEmptyEnums(schema, records) {
@@ -579,6 +596,6 @@ var buildOntologyGraph = (entries) => {
579
596
  };
580
597
  };
581
598
  //#endregion
582
- export { AGENT_INGEST_KIND, COMPUTED_TYPES, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, SAFE_RECORD_ID_PATTERN, SAFE_SLUG_PATTERN, TOOL_DEFINITION, TOOL_NAME, actionVisible, agentActionRunKey, assignLanes, backlinkRows, boolSortValue, bucketRecords, buildMonthGrid, buildOntologyGraph, buildUpdatedRecord, cellKey, coerceInlineValue, coerceNumeric, compareSortValues, compareYmd, completionCoveredByFieldChip, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, derivedSortValue, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldText, fieldTextOrNull, fieldVisible, firstDateField, firstMissingRequiredField, generateUniqueId, isFieldDrivenEvery, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSortableField, itemIdOf, itemIsDone, itemLabelOf, itemMatchesQuery, labelFieldFor, matchesWhere, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, scalarSortValue, selectDynamicRecord, shortHexId, snapshotEmptyEnums, sortItems, sortValueOf, spanCoversDay, storageKindFor, stringSortValue, uniqueBacklinkSources, uniqueEmbedTargets, uniqueRefTargets, viaMatches, whenMatches, ymdKey };
599
+ export { AGENT_INGEST_KIND, COMPUTED_TYPES, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, SAFE_RECORD_ID_PATTERN, SAFE_SLUG_PATTERN, TOOL_DEFINITION, TOOL_NAME, actionVisible, agentActionRunKey, assignLanes, backlinkRows, boolSortValue, bucketRecords, buildMonthGrid, buildOntologyGraph, buildUpdatedRecord, cellKey, chipMatches, coerceInlineValue, coerceNumeric, compareSortValues, compareYmd, completionCoveredByFieldChip, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, derivedSortValue, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldText, fieldTextOrNull, fieldVisible, firstDateField, firstMissingRequiredField, flagFieldValue, generateUniqueId, isFieldDrivenEvery, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSortableField, itemIdOf, itemIsDone, itemLabelOf, itemMatchesQuery, labelFieldFor, matchesWhere, monthAnchorDate, nextSortDirection, nextUniqueItemId, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, rowIdOf, scalarSortValue, selectDynamicRecord, shortHexId, skillCommandSeed, snapshotEmptyEnums, sortItems, sortValueOf, spanCoversDay, storageKindFor, stringSortValue, toggleChecked, uniqueBacklinkSources, uniqueEmbedTargets, uniqueRefTargets, viaMatches, whenMatches, ymdKey };
583
600
 
584
601
  //# sourceMappingURL=index.js.map