@mulmoclaude/core 0.25.1 → 0.26.0

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 (30) hide show
  1. package/assets/helps/billing-clients-worklog.md +3 -2
  2. package/assets/helps/collection-skills.md +3 -1
  3. package/assets/helps/todo-collection.md +2 -2
  4. package/dist/collection/core/fieldText.d.ts +11 -0
  5. package/dist/collection/index.cjs +14 -9
  6. package/dist/collection/index.cjs.map +1 -1
  7. package/dist/collection/index.d.ts +1 -0
  8. package/dist/collection/index.js +13 -10
  9. package/dist/collection/index.js.map +1 -1
  10. package/dist/collection/registry/server/index.cjs +1 -1
  11. package/dist/collection/registry/server/index.js +1 -1
  12. package/dist/collection/server/index.cjs +1 -1
  13. package/dist/collection/server/index.js +1 -1
  14. package/dist/collection-watchers/index.cjs +48 -10
  15. package/dist/collection-watchers/index.cjs.map +1 -1
  16. package/dist/collection-watchers/index.js +50 -12
  17. package/dist/collection-watchers/index.js.map +1 -1
  18. package/dist/feeds/server/index.cjs +1 -1
  19. package/dist/feeds/server/index.js +1 -1
  20. package/dist/{promptSafety-Boz5qOs-.js → promptSafety-B5KPkaNY.js} +32 -9
  21. package/dist/promptSafety-B5KPkaNY.js.map +1 -0
  22. package/dist/{promptSafety-Co4cYQ_Z.cjs → promptSafety-BOc1B3im.cjs} +43 -8
  23. package/dist/promptSafety-BOc1B3im.cjs.map +1 -0
  24. package/dist/{server-CKdWENZ-.cjs → server-B7mtx4Cd.cjs} +66 -11
  25. package/dist/{server-CKdWENZ-.cjs.map → server-B7mtx4Cd.cjs.map} +1 -1
  26. package/dist/{server-BI3wnTwl.js → server-CNLZZONa.js} +66 -11
  27. package/dist/{server-BI3wnTwl.js.map → server-CNLZZONa.js.map} +1 -1
  28. package/package.json +1 -1
  29. package/dist/promptSafety-Boz5qOs-.js.map +0 -1
  30. package/dist/promptSafety-Co4cYQ_Z.cjs.map +0 -1
@@ -114,8 +114,9 @@ one-line confirmation ("Added Acme Corp.") is enough.
114
114
  **Update**: `putItems` with `mode: "merge"` and a partial row
115
115
  (`{ id, <changed fields> }`) — the default upsert replaces the whole record.
116
116
 
117
- **Delete**: confirm once if the request is ambiguous, then remove the file
118
- (`manageCollection` has no delete).
117
+ **Delete**: confirm once if the request is ambiguous, then `deleteItems` with
118
+ `ids`. Check `rejected` in the result — an id that didn't exist is reported
119
+ there, not as deleted.
119
120
 
120
121
  ## Linking to a client in chat
121
122
  Link to the collection view, not the raw JSON path:
@@ -881,7 +881,9 @@ records through **`manageCollection`**, not raw file I/O:
881
881
  `toggle` / `embed` values (the stored JSON never contains them). Pass `ids`
882
882
  / `fields` on large collections to keep the result small — e.g.
883
883
  `fields: ["id"]` to check for an id collision before an add.
884
- - **Delete**remove the record file (`manageCollection` has no delete).
884
+ - **Delete — `deleteItems`.** Pass `ids`; the result is `{ deleted, rejected }`.
885
+ An id that doesn't exist lands in `rejected` rather than being counted as
886
+ deleted, so a typo'd id can't be reported back to the user as done.
885
887
  - **Aggregate — `queryItems`.** Counts, sums, averages, group-bys on ANY
886
888
  collection via a structured query (`groupBy` / `aggregates` / `where` /
887
889
  `orderBy` / `limit` — full shape in the "External data (CSV) collections"
@@ -88,8 +88,8 @@ description: The user's personal todo list. Use whenever they add, list, edit,
88
88
  the table.
89
89
  - **Mark done**: `putItems` with `mode: "merge"` and `{ id, status: "done" }`.
90
90
  - **Edit**: `putItems` `mode: "merge"` with a partial row — the default upsert
91
- replaces the whole record. **Delete**: remove the file (`manageCollection`
92
- has no delete).
91
+ replaces the whole record. **Delete**: `deleteItems` with `ids` (an id that
92
+ doesn't exist comes back in `rejected`, not `deleted`).
93
93
  - After a change, call `presentCollection` with slug `todos` (and the id) to show
94
94
  it inline.
95
95
  ```
@@ -0,0 +1,11 @@
1
+ /** The field's text, or `null` when it has no meaningful one (absent, or an
2
+ * array/object that would stringify to `"[object Object]"`).
3
+ *
4
+ * Returning `null` rather than `""` keeps "the field is empty" distinct from
5
+ * "the field can't be text" — a matcher must not treat an object-valued field
6
+ * as an empty string and match `""`. */
7
+ export declare function fieldTextOrNull(value: unknown): string | null;
8
+ /** The field's text, or `fallback` (default `""`) when it has none. Use where
9
+ * a string is required and an empty one is a safe stand-in — display, sort
10
+ * keys, CSV cells. Where the distinction matters, use {@link fieldTextOrNull}. */
11
+ export declare function fieldText(value: unknown, fallback?: string): string;
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_ids = require("../ids-Dl9Ij63K.cjs");
3
+ const require_promptSafety = require("../promptSafety-BOc1B3im.cjs");
3
4
  const require_project = require("../project-BWI5w_BT.cjs");
4
- const require_promptSafety = require("../promptSafety-Co4cYQ_Z.cjs");
5
5
  const require_errorMessage = require("../errorMessage--LvXe_Jx.cjs");
6
6
  //#region src/collection/core/presentCollection.ts
7
7
  var TOOL_NAME = "presentCollection";
@@ -129,8 +129,9 @@ function enumColorClasses(index) {
129
129
  /** Index of `value` within an enum field's declared `values`, or -1 when the
130
130
  * value is empty / unknown (→ neutral). */
131
131
  function enumValueIndex(values, value) {
132
- if (value === void 0 || value === null || value === "") return -1;
133
- return values?.indexOf(String(value)) ?? -1;
132
+ const text = require_promptSafety.fieldTextOrNull(value);
133
+ if (text === null || text === "") return -1;
134
+ return values?.indexOf(text) ?? -1;
134
135
  }
135
136
  /** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the
136
137
  * "notification enum"); undefined for every other field. */
@@ -336,16 +337,17 @@ function numericSortValue(raw) {
336
337
  } : EMPTY;
337
338
  }
338
339
  function stringSortValue(raw) {
339
- if (raw == null) return EMPTY;
340
- const str = String(raw);
340
+ const str = require_promptSafety.fieldTextOrNull(raw);
341
+ if (str === null) return EMPTY;
341
342
  return str.trim() === "" ? EMPTY : {
342
343
  empty: false,
343
344
  str
344
345
  };
345
346
  }
346
347
  function dateSortValue(raw) {
347
- if (raw == null || raw === "") return EMPTY;
348
- const epoch = Date.parse(String(raw));
348
+ const text = require_promptSafety.fieldTextOrNull(raw);
349
+ if (text === null || text === "") return EMPTY;
350
+ const epoch = Date.parse(text);
349
351
  return Number.isNaN(epoch) ? stringSortValue(raw) : {
350
352
  empty: false,
351
353
  num: epoch
@@ -354,8 +356,9 @@ function dateSortValue(raw) {
354
356
  /** Enum sorts by the value's index in the declared `values` list. A value
355
357
  * outside the list (or unset) is treated as empty → last. */
356
358
  function enumSortValue(values, raw) {
357
- if (raw == null || raw === "") return EMPTY;
358
- const idx = values ? values.indexOf(String(raw)) : -1;
359
+ const text = require_promptSafety.fieldTextOrNull(raw);
360
+ if (text === null || text === "") return EMPTY;
361
+ const idx = values ? values.indexOf(text) : -1;
359
362
  return idx < 0 ? EMPTY : {
360
363
  empty: false,
361
364
  num: idx
@@ -476,6 +479,8 @@ exports.enumValueIndex = enumValueIndex;
476
479
  exports.errorMessage = require_errorMessage.errorMessage;
477
480
  exports.evaluateDerived = require_promptSafety.evaluateDerived;
478
481
  exports.executePresentCollection = executePresentCollection;
482
+ exports.fieldText = require_promptSafety.fieldText;
483
+ exports.fieldTextOrNull = require_promptSafety.fieldTextOrNull;
479
484
  exports.fieldVisible = require_promptSafety.fieldVisible;
480
485
  exports.firstDateField = require_promptSafety.firstDateField;
481
486
  exports.firstMissingRequiredField = firstMissingRequiredField;
@@ -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/itemLabel.ts","../../src/collection/core/shortHexId.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 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 if (value === undefined || value === null || value === \"\") return -1;\n return values?.indexOf(String(value)) ?? -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 = value === undefined || value === null ? \"\" : String(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 { 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/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): 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 if (subField.type === \"boolean\") {\n bool[subKey] = false;\n boolOriginallyPresent[subKey] = false; // brand-new row\n boolTouched[subKey] = false;\n } else {\n text[subKey] = \"\";\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 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 = item[subKey];\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n // `typeof raw === \"boolean\"` so an existing explicit `false` is\n // recorded as present and round-trips on a no-op save.\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 Boolean(state.boolOriginallyPresent[key] || state.boolTouched[key] || 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 = rowDraft.bool[subKey] === true;\n if (rowDraft.boolOriginallyPresent[subKey] || 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] = state.bool[key] === true;\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 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 if (raw == null) return EMPTY;\n const str = String(raw);\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const epoch = Date.parse(String(raw));\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 if (raw == null || raw === \"\") return EMPTY;\n const idx = values ? values.indexOf(String(raw)) : -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","// 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 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 String(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"],"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/sortItems.ts","../../src/collection/core/itemLabel.ts","../../src/collection/core/shortHexId.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 { 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 = value === undefined || value === null ? \"\" : String(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 { 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/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): 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 if (subField.type === \"boolean\") {\n bool[subKey] = false;\n boolOriginallyPresent[subKey] = false; // brand-new row\n boolTouched[subKey] = false;\n } else {\n text[subKey] = \"\";\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 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 = item[subKey];\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n // `typeof raw === \"boolean\"` so an existing explicit `false` is\n // recorded as present and round-trips on a no-op save.\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 Boolean(state.boolOriginallyPresent[key] || state.boolTouched[key] || 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 = rowDraft.bool[subKey] === true;\n if (rowDraft.boolOriginallyPresent[subKey] || 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] = state.bool[key] === true;\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","// 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 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 String(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"],"mappings":";;;;;;AAUA,IAAa,YAAY;AAazB,IAAa,kBAAkC;CAC7C,MAAM;CACN,MAAM;CACN,aACE;CACF,YAAY;EACV,MAAM;EACN,YAAY;GACV,gBAAgB;IACd,MAAM;IACN,aAAa;GACf;GACA,QAAQ;IACN,MAAM;IACN,aAAa;GACf;EACF;EACA,UAAU,CAAC,gBAAgB;CAC7B;CACA,QAAQ,0DAA0D,UAAU;AAC9E;AAEA,IAAa,2BAA2B,OACtC,UACA,SACsE;CACtE,MAAM,iBAAiB,OAAO,MAAM,mBAAmB,WAAW,KAAK,eAAe,KAAK,IAAI;CAC/F,IAAI,CAAC,gBACH,OAAO;EACL,SAAS;EACT,cAAc;CAChB;CAEF,MAAM,SAAS,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,IAAI,KAAA;CACvG,MAAM,OAA8B,SAAS;EAAE;EAAgB;CAAO,IAAI,EAAE,eAAe;CAE3F,OAAO;EACL,SAAS,wBAFI,SAAS,GAAG,eAAe,KAAK,WAAW;EAKxD;EACA,UAAU;EAMV,cACE;CACJ;AACF;;;ACrCA,IAAM,UAAuC;CAC3C;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EAAE,MAAM;EAA0D,KAAK;EAAc,OAAO;EAA2B,QAAQ;CAAiB;CAChJ;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;AACF;;;AAIA,IAAa,eAAiC;CAC5C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;AAIA,IAAa,aAA+B;CAC1C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;AAIA,IAAa,aAA+B;CAC1C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;;AAKA,SAAgB,iBAAiB,OAAiC;CAChE,IAAI,QAAQ,GAAG,OAAO;CACtB,OAAO,QAAQ,QAAQ,QAAQ,WAAW;AAC5C;;;AAIA,SAAgB,eAAe,QAAuC,OAAwB;CAG5F,MAAM,OAAO,qBAAA,gBAAgB,KAAK;CAClC,IAAI,SAAS,QAAQ,SAAS,IAAI,OAAO;CACzC,OAAO,QAAQ,QAAQ,IAAI,KAAK;AAClC;;;AAIA,SAAS,gBAAgB,QAA0B,UAAiD;CAClG,MAAM,OAAO,OAAO;CACpB,OAAO,QAAQ,KAAK,UAAU,WAAW,KAAK,KAAK,KAAA;AACrD;;;;;;AAOA,SAAgB,iBAAiB,QAA0B,UAAkB,OAAkC;CAC7G,MAAM,eAAe,gBAAgB,QAAQ,QAAQ;CACrD,IAAI,cAAc;EAChB,MAAM,MAAM,UAAU,KAAA,KAAa,UAAU,OAAO,KAAK,OAAO,KAAK;EACrE,MAAM,OAAO,aAAa,QAAQ,GAAG;EACrC,IAAI,OAAO,GAAG,OAAO;EACrB,OAAO,SAAS,IAAI,aAAa;CACnC;CACA,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO,iBAAiB,eAAe,MAAM,SAAS,SAAS,KAAK,SAAS,KAAA,GAAW,KAAK,CAAC;AAChG;;;;AC1HA,SAAgB,SAAS,WAAqD;CAC5E,MAAM,OAA+B,CAAC;CACtC,MAAM,OAAgC,CAAC;CACvC,MAAM,wBAAiD,CAAC;CACxD,MAAM,cAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GACvD,IAAI,SAAS,SAAS,WAAW;EAC/B,KAAK,UAAU;EACf,sBAAsB,UAAU;EAChC,YAAY,UAAU;CACxB,OACE,KAAK,UAAU;CAGnB,OAAO;EAAE;EAAM;EAAM;EAAuB;CAAY;AAC1D;;AAGA,SAAgB,YAAY,MAA+B,WAAqD;CAC9G,MAAM,OAA+B,CAAC;CACtC,MAAM,OAAgC,CAAC;CACvC,MAAM,wBAAiD,CAAC;CACxD,MAAM,cAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GAAG;EAC1D,MAAM,MAAM,KAAK;EACjB,IAAI,SAAS,SAAS,WAAW;GAC/B,KAAK,UAAU,QAAQ;GAGvB,sBAAsB,UAAU,OAAO,QAAQ;GAC/C,YAAY,UAAU;EACxB,OACE,KAAK,UAAU,QAAQ,KAAA,KAAa,QAAQ,OAAO,KAAK,OAAO,GAAG;CAEtE;CACA,OAAO;EAAE;EAAM;EAAM;EAAuB;CAAY;AAC1D;;;AAIA,SAAS,kBAAkB,OAAkB,KAAa,OAA2B;CACnF,OAAO,QAAQ,MAAM,sBAAsB,QAAQ,MAAM,YAAY,QAAQ,MAAM,QAAQ;AAC7F;;AAGA,SAAS,mBAAmB,KAAyB,WAA+B;CAClF,IAAI,QAAQ,KAAA,KAAa,QAAQ,IAAI,OAAO,KAAA;CAC5C,IAAI,cAAc,YAAY,cAAc,SAAS;EACnD,MAAM,MAAM,OAAO,GAAG;EACtB,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;CACtC;CACA,OAAO;AACT;;AAGA,SAAS,iBAAiB,UAAyB,WAA+D;CAChH,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GAAG;EAC1D,IAAI,SAAS,SAAS,WAAW;GAC/B,MAAM,QAAQ,SAAS,KAAK,YAAY;GACxC,IAAI,SAAS,sBAAsB,WAAW,SAAS,YAAY,WAAW,SAAS,SAAS,UAAU,IAAI,UAAU;GACxH;EACF;EACA,MAAM,QAAQ,mBAAmB,SAAS,KAAK,SAAS,SAAS,IAAI;EACrE,IAAI,UAAU,KAAA,GAAW,IAAI,UAAU;CACzC;CACA,OAAO;AACT;;AAGA,SAAgB,cAAc,OAAkB,QAA0C;CACxF,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,GAAG;EACxD,IAAI,YAAA,eAAe,IAAI,MAAM,IAAI,GAAG;EACpC,IAAI,MAAM,SAAS,WAAW;GAC5B,IAAI,kBAAkB,OAAO,KAAK,KAAK,GAAG,OAAO,OAAO,MAAM,KAAK,SAAS;GAC5E;EACF;EACA,IAAI,MAAM,SAAS,WAAW,MAAM,IAAI;GACtC,MAAM,YAAY,MAAM;GACxB,OAAO,QAAQ,MAAM,MAAM,QAAQ,CAAC,EAAA,CAAG,KAAK,aAAa,iBAAiB,UAAU,SAAS,CAAC;GAC9F;EACF;EACA,MAAM,QAAQ,mBAAmB,MAAM,KAAK,MAAM,MAAM,IAAI;EAC5D,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO;CACzC;CACA,OAAO;AACT;;;;AAKA,SAAgB,kBAAkB,OAAkB,KAAgC;CAClF,IAAI,MAAM,SAAS,WAAW,OAAO,QAAQ;CAC7C,OAAO,QAAQ,KAAK,KAAA,IAAY;AAClC;;;;AAKA,SAAgB,mBAAmB,MAAsB,KAAa,OAAgC;CACpG,IAAI,UAAU,KAAA,GAAW;EACvB,MAAM,GAAG,MAAM,QAAQ,GAAG,SAAS;EACnC,OAAO;CACT;CACA,OAAO;EAAE,GAAG;GAAO,MAAM;CAAM;AACjC;;;AAIA,SAAS,oBAAoB,OAAyB;CACpD,OAAO,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU;AAC5D;;AAGA,SAAS,0BAA0B,OAAkB,MAAkD;CACrG,IAAI,MAAM,SAAS,WAAW,CAAC,MAAM,OAAO;CAC5C,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU;EACnD,MAAM,MAAM,KAAK;EACjB,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,MAAM,EAAE,GAAG;GACzD,IAAI,CAAC,SAAS,YAAY,SAAS,SAAS,WAAW;GACvD,IAAI,oBAAoB,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,MAAM,MAAM,IAAI,SAAS,EAAE,IAAI,SAAS;EAC/F;CACF;CACA,OAAO;AACT;;;;;AAMA,IAAM,sCAA8C,IAAI,IAAe,CAAC,WAAW,GAAG,YAAA,cAAc,CAAC;AAErG,SAAS,iBAAiB,KAAa,OAAkB,OAAkB,QAAuC;CAEhH,IAAI,CAAC,qBAAA,aAAa,OAAO,MAAM,GAAG,OAAO;CACzC,IAAI,MAAM,SAAS,WAAW,MAAM,IAAI;EACtC,MAAM,OAAO,MAAM,MAAM;EACzB,IAAI,MAAM,aAAa,CAAC,QAAQ,KAAK,WAAW,IAAI,OAAO,MAAM;EACjE,OAAO,0BAA0B,OAAO,IAAI;CAC9C;CACA,IAAI,CAAC,MAAM,UAAU,OAAO;CAC5B,IAAI,MAAM,SAAS,YAAY,MAAM,YAAY,MAAM,OAAO;CAC9D,IAAI,oBAAoB,IAAI,MAAM,IAAI,GAAG,OAAO;CAChD,OAAO,oBAAoB,MAAM,KAAK,IAAI,IAAI,MAAM,QAAQ;AAC9D;;AAGA,SAAgB,0BAA0B,OAAkB,QAAyC;CACnG,MAAM,SAAS,cAAc,OAAO,MAAM;CAC1C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,GAAG;EACxD,MAAM,UAAU,iBAAiB,KAAK,OAAO,OAAO,MAAM;EAC1D,IAAI,SAAS,OAAO;CACtB;CACA,OAAO;AACT;;;ACnIA,IAAM,QAAmB,EAAE,OAAO,KAAK;;AAGvC,IAAM,+BAAiD,IAAI,IAAyB;CAAC;CAAY;CAAS;CAAS;CAAQ;CAAS;CAAa;AAAQ,CAAC;AAE1J,SAAgB,gBAAgB,OAAqC;CACnE,OAAO,CAAC,aAAa,IAAI,MAAM,IAAI;AACrC;;AAGA,SAAgB,kBAAkB,SAAqD;CACrF,IAAI,YAAY,MAAM,OAAO;CAC7B,IAAI,YAAY,OAAO,OAAO;CAC9B,OAAO;AACT;AAIA,SAAgB,iBAAiB,KAAyB;CACxD,IAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO;CACtC,MAAM,MAAM,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;CACtD,OAAO,OAAO,SAAS,GAAG,IAAI;EAAE,OAAO;EAAO;CAAI,IAAI;AACxD;AAEA,SAAgB,gBAAgB,KAAyB;CAGvD,MAAM,MAAM,qBAAA,gBAAgB,GAAG;CAC/B,IAAI,QAAQ,MAAM,OAAO;CACzB,OAAO,IAAI,KAAK,MAAM,KAAK,QAAQ;EAAE,OAAO;EAAO;CAAI;AACzD;AAEA,SAAgB,cAAc,KAAyB;CACrD,MAAM,OAAO,qBAAA,gBAAgB,GAAG;CAChC,IAAI,SAAS,QAAQ,SAAS,IAAI,OAAO;CACzC,MAAM,QAAQ,KAAK,MAAM,IAAI;CAE7B,OAAO,OAAO,MAAM,KAAK,IAAI,gBAAgB,GAAG,IAAI;EAAE,OAAO;EAAO,KAAK;CAAM;AACjF;;;AAIA,SAAgB,cAAc,QAAuC,KAAyB;CAC5F,MAAM,OAAO,qBAAA,gBAAgB,GAAG;CAChC,IAAI,SAAS,QAAQ,SAAS,IAAI,OAAO;CACzC,MAAM,MAAM,SAAS,OAAO,QAAQ,IAAI,IAAI;CAC5C,OAAO,MAAM,IAAI,QAAQ;EAAE,OAAO;EAAO,KAAK;CAAI;AACpD;;AAGA,SAAgB,cAAc,SAA6B;CACzD,OAAO;EAAE,OAAO;EAAO,KAAK,UAAU,IAAI;CAAE;AAC9C;AAIA,SAAgB,kBAAkB,MAAiB,OAA0B;CAC3E,IAAI,KAAK,QAAQ,KAAA,KAAa,MAAM,QAAQ,KAAA,GAAW,OAAO,KAAK,MAAM,MAAM;CAC/E,MAAM,UAAU,KAAK,OAAO,OAAO,KAAK,OAAO,EAAE;CACjD,MAAM,WAAW,MAAM,OAAO,OAAO,MAAM,OAAO,EAAE;CACpD,OAAO,QAAQ,cAAc,QAAQ;AACvC;;;AAIA,SAAgB,UAAU,OAAkC,WAA0B,SAAgE;CACpJ,MAAM,MAAM,cAAc,QAAQ,IAAI;CACtC,OAAO,MACJ,KAAK,MAAM,WAAW;EAAE;EAAM;EAAO,IAAI,QAAQ,IAAI;CAAE,EAAE,CAAC,CAC1D,MAAM,MAAM,UAAU;EACrB,IAAI,KAAK,GAAG,SAAS,MAAM,GAAG,OAAO;GACnC,IAAI,KAAK,GAAG,SAAS,MAAM,GAAG,OAAO,OAAO,KAAK,QAAQ,MAAM;GAC/D,OAAO,KAAK,GAAG,QAAQ,IAAI;EAC7B;EACA,MAAM,OAAO,kBAAkB,KAAK,IAAI,MAAM,EAAE;EAChD,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,MAAM;CACtD,CAAC,CAAC,CACD,KAAK,cAAc,UAAU,IAAI;AACtC;;;AC3GA,IAAM,oCAAoB,IAAI,IAAI;CAAC;CAAU;CAAQ;CAAY;AAAO,CAAC;;;;;AAMzE,SAAgB,cAAc,QAAyC;CACrE,IAAI,OAAO,cAAc,OAAO,OAAO;CACvC,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,OAAO,MAAM,GACpD,IAAI,QAAQ,OAAO,cAAc,kBAAkB,IAAI,KAAK,IAAI,GAAG,OAAO;CAE5E,OAAO;AACT;;AAGA,SAAgB,SAAS,MAAsB,QAAkC;CAC/E,OAAO,OAAO,KAAK,OAAO,eAAe,EAAE;AAC7C;;;;AAKA,SAAgB,YAAY,MAAsB,QAA0B,YAAmC;CAC7G,IAAI,YAAY;EACd,MAAM,QAAQ,KAAK;EAInB,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;GACxF,MAAM,OAAO,OAAO,KAAK;GACzB,IAAI,KAAK,SAAS,GAAG,OAAO;EAC9B;CACF;CACA,OAAO,SAAS,MAAM,MAAM;AAC9B;;;;;;;;;;AClCA,SAAgB,aAAqB;CACnC,OAAO,OAAO,WAAW,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;AACzD"}
@@ -1,6 +1,7 @@
1
1
  export * from './core/schema';
2
2
  export type { CollectionQuery, CollectionQueryAggregate, CollectionQueryOrder, CollectionQueryWhere } from './core/queryZ';
3
3
  export * from './core/ids';
4
+ export * from './core/fieldText';
4
5
  export * from './core/project';
5
6
  export * from './core/uiTypes';
6
7
  export * from './core/presentCollection';
@@ -1,6 +1,6 @@
1
1
  import { a as AGENT_INGEST_KIND, c as INGEST_KINDS, d as isReadOnlySchema, f as storageKindFor, i as isSafeSlug, l as embedTargetId, n as SAFE_SLUG_PATTERN, o as COMPUTED_TYPES, r as isSafeRecordId, s as FEED_SCHEDULES, t as SAFE_RECORD_ID_PATTERN, u as isFieldDrivenEvery } from "../ids-P2T89Gou.js";
2
+ import { A as fieldVisible, C as matchesWhere, D as rollupValue, E as projectBacklinkRow, M as fieldText, N as fieldTextOrNull, O as actionVisible, S as itemIsDone, T as coerceNumeric, _ as resolveRowRefs, a as buildMonthGrid, b as resolveIcon, c as daySlice, d as parseIsoDateTime, f as parseTimeRange, g as deriveAll, h as ymdKey, i as bucketRecords, j as whenMatches, k as agentActionRunKey, 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, v as evaluateDerived, w as backlinkRows, x as selectDynamicRecord, y as firstDateField } from "../promptSafety-B5KPkaNY.js";
2
3
  import { t as projectRecordFields } from "../project-bU98ycsy.js";
3
- import { A as fieldVisible, C as matchesWhere, D as rollupValue, E as projectBacklinkRow, O as actionVisible, S as itemIsDone, T as coerceNumeric, _ as resolveRowRefs, a as buildMonthGrid, b as resolveIcon, c as daySlice, d as parseIsoDateTime, f as parseTimeRange, g as deriveAll, h as ymdKey, i as bucketRecords, j as whenMatches, k as agentActionRunKey, 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, v as evaluateDerived, w as backlinkRows, x as selectDynamicRecord, y as firstDateField } from "../promptSafety-Boz5qOs-.js";
4
4
  import { t as errorMessage } from "../errorMessage-DIdhheHk.js";
5
5
  //#region src/collection/core/presentCollection.ts
6
6
  var TOOL_NAME = "presentCollection";
@@ -128,8 +128,9 @@ function enumColorClasses(index) {
128
128
  /** Index of `value` within an enum field's declared `values`, or -1 when the
129
129
  * value is empty / unknown (→ neutral). */
130
130
  function enumValueIndex(values, value) {
131
- if (value === void 0 || value === null || value === "") return -1;
132
- return values?.indexOf(String(value)) ?? -1;
131
+ const text = fieldTextOrNull(value);
132
+ if (text === null || text === "") return -1;
133
+ return values?.indexOf(text) ?? -1;
133
134
  }
134
135
  /** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the
135
136
  * "notification enum"); undefined for every other field. */
@@ -335,16 +336,17 @@ function numericSortValue(raw) {
335
336
  } : EMPTY;
336
337
  }
337
338
  function stringSortValue(raw) {
338
- if (raw == null) return EMPTY;
339
- const str = String(raw);
339
+ const str = fieldTextOrNull(raw);
340
+ if (str === null) return EMPTY;
340
341
  return str.trim() === "" ? EMPTY : {
341
342
  empty: false,
342
343
  str
343
344
  };
344
345
  }
345
346
  function dateSortValue(raw) {
346
- if (raw == null || raw === "") return EMPTY;
347
- const epoch = Date.parse(String(raw));
347
+ const text = fieldTextOrNull(raw);
348
+ if (text === null || text === "") return EMPTY;
349
+ const epoch = Date.parse(text);
348
350
  return Number.isNaN(epoch) ? stringSortValue(raw) : {
349
351
  empty: false,
350
352
  num: epoch
@@ -353,8 +355,9 @@ function dateSortValue(raw) {
353
355
  /** Enum sorts by the value's index in the declared `values` list. A value
354
356
  * outside the list (or unset) is treated as empty → last. */
355
357
  function enumSortValue(values, raw) {
356
- if (raw == null || raw === "") return EMPTY;
357
- const idx = values ? values.indexOf(String(raw)) : -1;
358
+ const text = fieldTextOrNull(raw);
359
+ if (text === null || text === "") return EMPTY;
360
+ const idx = values ? values.indexOf(text) : -1;
358
361
  return idx < 0 ? EMPTY : {
359
362
  empty: false,
360
363
  num: idx
@@ -437,6 +440,6 @@ function shortHexId() {
437
440
  return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
438
441
  }
439
442
  //#endregion
440
- 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, buildUpdatedRecord, coerceInlineValue, coerceNumeric, compareSortValues, compareYmd, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldVisible, firstDateField, firstMissingRequiredField, isFieldDrivenEvery, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSortableField, itemIdOf, itemIsDone, itemLabelOf, labelFieldFor, matchesWhere, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, selectDynamicRecord, shortHexId, sortItems, spanCoversDay, storageKindFor, stringSortValue, whenMatches, ymdKey };
443
+ 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, buildUpdatedRecord, coerceInlineValue, coerceNumeric, compareSortValues, compareYmd, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldText, fieldTextOrNull, fieldVisible, firstDateField, firstMissingRequiredField, isFieldDrivenEvery, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSortableField, itemIdOf, itemIsDone, itemLabelOf, labelFieldFor, matchesWhere, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, selectDynamicRecord, shortHexId, sortItems, spanCoversDay, storageKindFor, stringSortValue, whenMatches, ymdKey };
441
444
 
442
445
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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/itemLabel.ts","../../src/collection/core/shortHexId.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 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 if (value === undefined || value === null || value === \"\") return -1;\n return values?.indexOf(String(value)) ?? -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 = value === undefined || value === null ? \"\" : String(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 { 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/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): 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 if (subField.type === \"boolean\") {\n bool[subKey] = false;\n boolOriginallyPresent[subKey] = false; // brand-new row\n boolTouched[subKey] = false;\n } else {\n text[subKey] = \"\";\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 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 = item[subKey];\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n // `typeof raw === \"boolean\"` so an existing explicit `false` is\n // recorded as present and round-trips on a no-op save.\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 Boolean(state.boolOriginallyPresent[key] || state.boolTouched[key] || 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 = rowDraft.bool[subKey] === true;\n if (rowDraft.boolOriginallyPresent[subKey] || 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] = state.bool[key] === true;\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 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 if (raw == null) return EMPTY;\n const str = String(raw);\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const epoch = Date.parse(String(raw));\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 if (raw == null || raw === \"\") return EMPTY;\n const idx = values ? values.indexOf(String(raw)) : -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","// 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 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 String(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"],"mappings":";;;;;AAUA,IAAa,YAAY;AAazB,IAAa,kBAAkC;CAC7C,MAAM;CACN,MAAM;CACN,aACE;CACF,YAAY;EACV,MAAM;EACN,YAAY;GACV,gBAAgB;IACd,MAAM;IACN,aAAa;GACf;GACA,QAAQ;IACN,MAAM;IACN,aAAa;GACf;EACF;EACA,UAAU,CAAC,gBAAgB;CAC7B;CACA,QAAQ,0DAA0D,UAAU;AAC9E;AAEA,IAAa,2BAA2B,OACtC,UACA,SACsE;CACtE,MAAM,iBAAiB,OAAO,MAAM,mBAAmB,WAAW,KAAK,eAAe,KAAK,IAAI;CAC/F,IAAI,CAAC,gBACH,OAAO;EACL,SAAS;EACT,cAAc;CAChB;CAEF,MAAM,SAAS,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,IAAI,KAAA;CACvG,MAAM,OAA8B,SAAS;EAAE;EAAgB;CAAO,IAAI,EAAE,eAAe;CAE3F,OAAO;EACL,SAAS,wBAFI,SAAS,GAAG,eAAe,KAAK,WAAW;EAKxD;EACA,UAAU;EAMV,cACE;CACJ;AACF;;;ACtCA,IAAM,UAAuC;CAC3C;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EAAE,MAAM;EAA0D,KAAK;EAAc,OAAO;EAA2B,QAAQ;CAAiB;CAChJ;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EAAE,MAAM;EAA8D,KAAK;EAAe,OAAO;EAA6B,QAAQ;CAAkB;CACxJ;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;CACA;EACE,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;CACV;AACF;;;AAIA,IAAa,eAAiC;CAC5C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;AAIA,IAAa,aAA+B;CAC1C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;AAIA,IAAa,aAA+B;CAC1C,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;AACV;;;;AAKA,SAAgB,iBAAiB,OAAiC;CAChE,IAAI,QAAQ,GAAG,OAAO;CACtB,OAAO,QAAQ,QAAQ,QAAQ,WAAW;AAC5C;;;AAIA,SAAgB,eAAe,QAAuC,OAAwB;CAC5F,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO;CAClE,OAAO,QAAQ,QAAQ,OAAO,KAAK,CAAC,KAAK;AAC3C;;;AAIA,SAAS,gBAAgB,QAA0B,UAAiD;CAClG,MAAM,OAAO,OAAO;CACpB,OAAO,QAAQ,KAAK,UAAU,WAAW,KAAK,KAAK,KAAA;AACrD;;;;;;AAOA,SAAgB,iBAAiB,QAA0B,UAAkB,OAAkC;CAC7G,MAAM,eAAe,gBAAgB,QAAQ,QAAQ;CACrD,IAAI,cAAc;EAChB,MAAM,MAAM,UAAU,KAAA,KAAa,UAAU,OAAO,KAAK,OAAO,KAAK;EACrE,MAAM,OAAO,aAAa,QAAQ,GAAG;EACrC,IAAI,OAAO,GAAG,OAAO;EACrB,OAAO,SAAS,IAAI,aAAa;CACnC;CACA,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO,iBAAiB,eAAe,MAAM,SAAS,SAAS,KAAK,SAAS,KAAA,GAAW,KAAK,CAAC;AAChG;;;;ACtHA,SAAgB,SAAS,WAAqD;CAC5E,MAAM,OAA+B,CAAC;CACtC,MAAM,OAAgC,CAAC;CACvC,MAAM,wBAAiD,CAAC;CACxD,MAAM,cAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GACvD,IAAI,SAAS,SAAS,WAAW;EAC/B,KAAK,UAAU;EACf,sBAAsB,UAAU;EAChC,YAAY,UAAU;CACxB,OACE,KAAK,UAAU;CAGnB,OAAO;EAAE;EAAM;EAAM;EAAuB;CAAY;AAC1D;;AAGA,SAAgB,YAAY,MAA+B,WAAqD;CAC9G,MAAM,OAA+B,CAAC;CACtC,MAAM,OAAgC,CAAC;CACvC,MAAM,wBAAiD,CAAC;CACxD,MAAM,cAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GAAG;EAC1D,MAAM,MAAM,KAAK;EACjB,IAAI,SAAS,SAAS,WAAW;GAC/B,KAAK,UAAU,QAAQ;GAGvB,sBAAsB,UAAU,OAAO,QAAQ;GAC/C,YAAY,UAAU;EACxB,OACE,KAAK,UAAU,QAAQ,KAAA,KAAa,QAAQ,OAAO,KAAK,OAAO,GAAG;CAEtE;CACA,OAAO;EAAE;EAAM;EAAM;EAAuB;CAAY;AAC1D;;;AAIA,SAAS,kBAAkB,OAAkB,KAAa,OAA2B;CACnF,OAAO,QAAQ,MAAM,sBAAsB,QAAQ,MAAM,YAAY,QAAQ,MAAM,QAAQ;AAC7F;;AAGA,SAAS,mBAAmB,KAAyB,WAA+B;CAClF,IAAI,QAAQ,KAAA,KAAa,QAAQ,IAAI,OAAO,KAAA;CAC5C,IAAI,cAAc,YAAY,cAAc,SAAS;EACnD,MAAM,MAAM,OAAO,GAAG;EACtB,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;CACtC;CACA,OAAO;AACT;;AAGA,SAAS,iBAAiB,UAAyB,WAA+D;CAChH,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,SAAS,GAAG;EAC1D,IAAI,SAAS,SAAS,WAAW;GAC/B,MAAM,QAAQ,SAAS,KAAK,YAAY;GACxC,IAAI,SAAS,sBAAsB,WAAW,SAAS,YAAY,WAAW,SAAS,SAAS,UAAU,IAAI,UAAU;GACxH;EACF;EACA,MAAM,QAAQ,mBAAmB,SAAS,KAAK,SAAS,SAAS,IAAI;EACrE,IAAI,UAAU,KAAA,GAAW,IAAI,UAAU;CACzC;CACA,OAAO;AACT;;AAGA,SAAgB,cAAc,OAAkB,QAA0C;CACxF,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,GAAG;EACxD,IAAI,eAAe,IAAI,MAAM,IAAI,GAAG;EACpC,IAAI,MAAM,SAAS,WAAW;GAC5B,IAAI,kBAAkB,OAAO,KAAK,KAAK,GAAG,OAAO,OAAO,MAAM,KAAK,SAAS;GAC5E;EACF;EACA,IAAI,MAAM,SAAS,WAAW,MAAM,IAAI;GACtC,MAAM,YAAY,MAAM;GACxB,OAAO,QAAQ,MAAM,MAAM,QAAQ,CAAC,EAAA,CAAG,KAAK,aAAa,iBAAiB,UAAU,SAAS,CAAC;GAC9F;EACF;EACA,MAAM,QAAQ,mBAAmB,MAAM,KAAK,MAAM,MAAM,IAAI;EAC5D,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO;CACzC;CACA,OAAO;AACT;;;;AAKA,SAAgB,kBAAkB,OAAkB,KAAgC;CAClF,IAAI,MAAM,SAAS,WAAW,OAAO,QAAQ;CAC7C,OAAO,QAAQ,KAAK,KAAA,IAAY;AAClC;;;;AAKA,SAAgB,mBAAmB,MAAsB,KAAa,OAAgC;CACpG,IAAI,UAAU,KAAA,GAAW;EACvB,MAAM,GAAG,MAAM,QAAQ,GAAG,SAAS;EACnC,OAAO;CACT;CACA,OAAO;EAAE,GAAG;GAAO,MAAM;CAAM;AACjC;;;AAIA,SAAS,oBAAoB,OAAyB;CACpD,OAAO,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU;AAC5D;;AAGA,SAAS,0BAA0B,OAAkB,MAAkD;CACrG,IAAI,MAAM,SAAS,WAAW,CAAC,MAAM,OAAO;CAC5C,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU;EACnD,MAAM,MAAM,KAAK;EACjB,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,MAAM,EAAE,GAAG;GACzD,IAAI,CAAC,SAAS,YAAY,SAAS,SAAS,WAAW;GACvD,IAAI,oBAAoB,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,MAAM,MAAM,IAAI,SAAS,EAAE,IAAI,SAAS;EAC/F;CACF;CACA,OAAO;AACT;;;;;AAMA,IAAM,sCAA8C,IAAI,IAAe,CAAC,WAAW,GAAG,cAAc,CAAC;AAErG,SAAS,iBAAiB,KAAa,OAAkB,OAAkB,QAAuC;CAEhH,IAAI,CAAC,aAAa,OAAO,MAAM,GAAG,OAAO;CACzC,IAAI,MAAM,SAAS,WAAW,MAAM,IAAI;EACtC,MAAM,OAAO,MAAM,MAAM;EACzB,IAAI,MAAM,aAAa,CAAC,QAAQ,KAAK,WAAW,IAAI,OAAO,MAAM;EACjE,OAAO,0BAA0B,OAAO,IAAI;CAC9C;CACA,IAAI,CAAC,MAAM,UAAU,OAAO;CAC5B,IAAI,MAAM,SAAS,YAAY,MAAM,YAAY,MAAM,OAAO;CAC9D,IAAI,oBAAoB,IAAI,MAAM,IAAI,GAAG,OAAO;CAChD,OAAO,oBAAoB,MAAM,KAAK,IAAI,IAAI,MAAM,QAAQ;AAC9D;;AAGA,SAAgB,0BAA0B,OAAkB,QAAyC;CACnG,MAAM,SAAS,cAAc,OAAO,MAAM;CAC1C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,GAAG;EACxD,MAAM,UAAU,iBAAiB,KAAK,OAAO,OAAO,MAAM;EAC1D,IAAI,SAAS,OAAO;CACtB;CACA,OAAO;AACT;;;ACpIA,IAAM,QAAmB,EAAE,OAAO,KAAK;;AAGvC,IAAM,+BAAiD,IAAI,IAAyB;CAAC;CAAY;CAAS;CAAS;CAAQ;CAAS;CAAa;AAAQ,CAAC;AAE1J,SAAgB,gBAAgB,OAAqC;CACnE,OAAO,CAAC,aAAa,IAAI,MAAM,IAAI;AACrC;;AAGA,SAAgB,kBAAkB,SAAqD;CACrF,IAAI,YAAY,MAAM,OAAO;CAC7B,IAAI,YAAY,OAAO,OAAO;CAC9B,OAAO;AACT;AAIA,SAAgB,iBAAiB,KAAyB;CACxD,IAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO;CACtC,MAAM,MAAM,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;CACtD,OAAO,OAAO,SAAS,GAAG,IAAI;EAAE,OAAO;EAAO;CAAI,IAAI;AACxD;AAEA,SAAgB,gBAAgB,KAAyB;CACvD,IAAI,OAAO,MAAM,OAAO;CACxB,MAAM,MAAM,OAAO,GAAG;CACtB,OAAO,IAAI,KAAK,MAAM,KAAK,QAAQ;EAAE,OAAO;EAAO;CAAI;AACzD;AAEA,SAAgB,cAAc,KAAyB;CACrD,IAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO;CACtC,MAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,CAAC;CAEpC,OAAO,OAAO,MAAM,KAAK,IAAI,gBAAgB,GAAG,IAAI;EAAE,OAAO;EAAO,KAAK;CAAM;AACjF;;;AAIA,SAAgB,cAAc,QAAuC,KAAyB;CAC5F,IAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO;CACtC,MAAM,MAAM,SAAS,OAAO,QAAQ,OAAO,GAAG,CAAC,IAAI;CACnD,OAAO,MAAM,IAAI,QAAQ;EAAE,OAAO;EAAO,KAAK;CAAI;AACpD;;AAGA,SAAgB,cAAc,SAA6B;CACzD,OAAO;EAAE,OAAO;EAAO,KAAK,UAAU,IAAI;CAAE;AAC9C;AAIA,SAAgB,kBAAkB,MAAiB,OAA0B;CAC3E,IAAI,KAAK,QAAQ,KAAA,KAAa,MAAM,QAAQ,KAAA,GAAW,OAAO,KAAK,MAAM,MAAM;CAC/E,MAAM,UAAU,KAAK,OAAO,OAAO,KAAK,OAAO,EAAE;CACjD,MAAM,WAAW,MAAM,OAAO,OAAO,MAAM,OAAO,EAAE;CACpD,OAAO,QAAQ,cAAc,QAAQ;AACvC;;;AAIA,SAAgB,UAAU,OAAkC,WAA0B,SAAgE;CACpJ,MAAM,MAAM,cAAc,QAAQ,IAAI;CACtC,OAAO,MACJ,KAAK,MAAM,WAAW;EAAE;EAAM;EAAO,IAAI,QAAQ,IAAI;CAAE,EAAE,CAAC,CAC1D,MAAM,MAAM,UAAU;EACrB,IAAI,KAAK,GAAG,SAAS,MAAM,GAAG,OAAO;GACnC,IAAI,KAAK,GAAG,SAAS,MAAM,GAAG,OAAO,OAAO,KAAK,QAAQ,MAAM;GAC/D,OAAO,KAAK,GAAG,QAAQ,IAAI;EAC7B;EACA,MAAM,OAAO,kBAAkB,KAAK,IAAI,MAAM,EAAE;EAChD,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,MAAM;CACtD,CAAC,CAAC,CACD,KAAK,cAAc,UAAU,IAAI;AACtC;;;ACtGA,IAAM,oCAAoB,IAAI,IAAI;CAAC;CAAU;CAAQ;CAAY;AAAO,CAAC;;;;;AAMzE,SAAgB,cAAc,QAAyC;CACrE,IAAI,OAAO,cAAc,OAAO,OAAO;CACvC,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,OAAO,MAAM,GACpD,IAAI,QAAQ,OAAO,cAAc,kBAAkB,IAAI,KAAK,IAAI,GAAG,OAAO;CAE5E,OAAO;AACT;;AAGA,SAAgB,SAAS,MAAsB,QAAkC;CAC/E,OAAO,OAAO,KAAK,OAAO,eAAe,EAAE;AAC7C;;;;AAKA,SAAgB,YAAY,MAAsB,QAA0B,YAAmC;CAC7G,IAAI,YAAY;EACd,MAAM,QAAQ,KAAK;EAInB,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;GACxF,MAAM,OAAO,OAAO,KAAK;GACzB,IAAI,KAAK,SAAS,GAAG,OAAO;EAC9B;CACF;CACA,OAAO,SAAS,MAAM,MAAM;AAC9B;;;;;;;;;;AClCA,SAAgB,aAAqB;CACnC,OAAO,OAAO,WAAW,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;AACzD"}
1
+ {"version":3,"file":"index.js","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/itemLabel.ts","../../src/collection/core/shortHexId.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 { 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 = value === undefined || value === null ? \"\" : String(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 { 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/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): 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 if (subField.type === \"boolean\") {\n bool[subKey] = false;\n boolOriginallyPresent[subKey] = false; // brand-new row\n boolTouched[subKey] = false;\n } else {\n text[subKey] = \"\";\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 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 = item[subKey];\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n // `typeof raw === \"boolean\"` so an existing explicit `false` is\n // recorded as present and round-trips on a no-op save.\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\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 Boolean(state.boolOriginallyPresent[key] || state.boolTouched[key] || 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 = rowDraft.bool[subKey] === true;\n if (rowDraft.boolOriginallyPresent[subKey] || 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] = state.bool[key] === true;\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","// 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 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 String(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"],"mappings":"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"}
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../../../rolldown-runtime-D6vf50IK.cjs");
3
3
  const require_discovery = require("../../../discovery-cwtfyDxl.cjs");
4
4
  const require_templatePath = require("../../../templatePath-27vUZowm.cjs");
5
- const require_server = require("../../../server-CKdWENZ-.cjs");
5
+ const require_server = require("../../../server-B7mtx4Cd.cjs");
6
6
  const require_skill_bridge_index = require("../../../skill-bridge/index.cjs");
7
7
  const require_types = require("../../../types-CNqkLT4M.cjs");
8
8
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { B as writeFileAtomic, X as collectionsRegistriesConfigPath, et as log, o as CollectionSchemaZ, q as safeRecordId, r as loadCollection, t as acceptParsedSchema } from "../../../discovery-QUi_7aNG.js";
2
2
  import { t as isSafeActionTemplatePath } from "../../../templatePath-k_WNbL_Q.js";
3
- import { _ as errorMessage, g as ONE_SECOND_MS } from "../../../server-BI3wnTwl.js";
3
+ import { _ as errorMessage, g as ONE_SECOND_MS } from "../../../server-CNLZZONa.js";
4
4
  import { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from "../../../skill-bridge/index.js";
5
5
  import { n as parseRegistryIndex, r as isRecord, t as OFFICIAL_REGISTRY_NAME } from "../../../types-BKVZrtyc.js";
6
6
  import path from "node:path";
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_ids = require("../../ids-Dl9Ij63K.cjs");
3
3
  const require_discovery = require("../../discovery-cwtfyDxl.cjs");
4
4
  const require_templatePath = require("../../templatePath-27vUZowm.cjs");
5
- const require_server = require("../../server-CKdWENZ-.cjs");
5
+ const require_server = require("../../server-B7mtx4Cd.cjs");
6
6
  exports.COMPUTED_TYPES = require_ids.COMPUTED_TYPES;
7
7
  exports.CollectionQueryZ = require_discovery.CollectionQueryZ;
8
8
  exports.CollectionSchemaZ = require_discovery.CollectionSchemaZ;
@@ -1,5 +1,5 @@
1
1
  import { o as COMPUTED_TYPES } from "../../ids-P2T89Gou.js";
2
2
  import { A as generateItemId, C as compileJsonlQuery, D as buildActionSeedPrompt, E as MAX_QUERY_ROWS, F as readCustomViewI18n, G as resolveDataDir, H as isContainedInRoot, I as readItem, J as safeSlugName, K as resolveTemplatePath, L as readSkillTemplate, M as listItems, N as promptPathsFor, O as buildCollectionActionSeedPrompt, P as readCustomViewHtml, Q as getWorkspaceRoot, R as resolveCreateItemId, S as compileCsvQuery, T as DEFAULT_QUERY_ROWS, U as isContainedInWorkspace, V as SCHEMA_FILE, W as itemFilePath, Z as configureCollectionHost, _ as decodeCsvRecordId, a as toSummary, b as normalizeCsvValue, c as collectionWritable, et as log, f as pageFromFullRead, g as csvRowToItem, i as toDetail, j as isRegularFile, k as deleteItem, l as readOnlyRefusal, m as MAX_CSV_ROWS, n as discoverCollections, nt as setCollectionChangePublisher, o as CollectionSchemaZ, p as projectItemFields, q as safeRecordId, r as loadCollection, t as acceptParsedSchema, tt as publishCollectionChange, u as storeFor, v as dedupeByRecordId, w as CollectionQueryZ, y as encodeCsvRecordId, z as writeItem } from "../../discovery-QUi_7aNG.js";
3
3
  import { i as isSafeTemplatePath, n as isSafeCustomViewI18nPath, r as isSafeCustomViewPath, t as isSafeActionTemplatePath } from "../../templatePath-k_WNbL_Q.js";
4
- import { C as validateCollectionRecords, D as runCollectionQuery, E as recordFieldProblem, O as enrichItems, S as firstMutateParamProblem, T as compileRecordZ, a as deleteCollection, b as schemaRelations, c as computeSuccessor, d as isTriggerDue, f as maybeSpawnSuccessor, h as successorId, i as deleteCustomView, k as runQueryOverRows, l as daysInMonth, m as resolveEvery, n as MAX_UNSELECTIVE_ITEMS, o as deleteCollectionRefusalMessage, p as parseCivil, r as makeManageCollectionTool, s as advanceTriggerDate, t as MAX_SCHEMA_ISSUES, u as formatCivil, v as computeCollectionIcon, w as validateRecordObject, x as applyMutateAction, y as buildWorkspaceOntology } from "../../server-BI3wnTwl.js";
4
+ import { C as validateCollectionRecords, D as runCollectionQuery, E as recordFieldProblem, O as enrichItems, S as firstMutateParamProblem, T as compileRecordZ, a as deleteCollection, b as schemaRelations, c as computeSuccessor, d as isTriggerDue, f as maybeSpawnSuccessor, h as successorId, i as deleteCustomView, k as runQueryOverRows, l as daysInMonth, m as resolveEvery, n as MAX_UNSELECTIVE_ITEMS, o as deleteCollectionRefusalMessage, p as parseCivil, r as makeManageCollectionTool, s as advanceTriggerDate, t as MAX_SCHEMA_ISSUES, u as formatCivil, v as computeCollectionIcon, w as validateRecordObject, x as applyMutateAction, y as buildWorkspaceOntology } from "../../server-CNLZZONa.js";
5
5
  export { COMPUTED_TYPES, CollectionQueryZ, CollectionSchemaZ, DEFAULT_QUERY_ROWS, MAX_CSV_ROWS, MAX_QUERY_ROWS, MAX_SCHEMA_ISSUES, MAX_UNSELECTIVE_ITEMS, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, applyMutateAction, buildActionSeedPrompt, buildCollectionActionSeedPrompt, buildWorkspaceOntology, collectionWritable, compileCsvQuery, compileJsonlQuery, compileRecordZ, computeCollectionIcon, computeSuccessor, configureCollectionHost, csvRowToItem, daysInMonth, decodeCsvRecordId, dedupeByRecordId, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, encodeCsvRecordId, enrichItems, firstMutateParamProblem, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isRegularFile, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, makeManageCollectionTool, maybeSpawnSuccessor, normalizeCsvValue, pageFromFullRead, parseCivil, projectItemFields, promptPathsFor, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readOnlyRefusal, readSkillTemplate, recordFieldProblem, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, runCollectionQuery, runQueryOverRows, safeRecordId, safeSlugName, schemaRelations, setCollectionChangePublisher, storeFor, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };