@mulmoclaude/core 1.14.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/atomic-C_7YpMiM.cjs.map +1 -1
  2. package/dist/atomic-DPpdrJzO.js.map +1 -1
  3. package/dist/{calendarGrid-DivcNKkX.js → calendarGrid-Csy2rpjp.js} +58 -28
  4. package/dist/{calendarGrid-DivcNKkX.js.map → calendarGrid-Csy2rpjp.js.map} +1 -1
  5. package/dist/{calendarGrid-BQYqOCnF.cjs → calendarGrid-p8K-7cuB.cjs} +58 -28
  6. package/dist/{calendarGrid-BQYqOCnF.cjs.map → calendarGrid-p8K-7cuB.cjs.map} +1 -1
  7. package/dist/collection/core/actionVisible.d.ts +3 -3
  8. package/dist/collection/core/completion.d.ts +5 -5
  9. package/dist/collection/index.cjs +5 -8
  10. package/dist/collection/index.cjs.map +1 -1
  11. package/dist/collection/index.js +5 -8
  12. package/dist/collection/index.js.map +1 -1
  13. package/dist/collection/registry/registryIndex.d.ts +1 -1
  14. package/dist/collection/registry/server/index.cjs +14 -12
  15. package/dist/collection/registry/server/index.cjs.map +1 -1
  16. package/dist/collection/registry/server/index.js +14 -12
  17. package/dist/collection/registry/server/index.js.map +1 -1
  18. package/dist/collection/server/discovery.d.ts +2 -2
  19. package/dist/collection/server/index.cjs +3 -3
  20. package/dist/collection/server/index.js +3 -3
  21. package/dist/collection/server/io.d.ts +3 -3
  22. package/dist/collection/server/manageTool.d.ts +3 -3
  23. package/dist/collection/server/store.d.ts +3 -3
  24. package/dist/collection/server/validate.d.ts +1 -1
  25. package/dist/collection-watchers/index.cjs +5 -5
  26. package/dist/collection-watchers/index.js +5 -5
  27. package/dist/{discovery-D6p4ITNR.js → discovery-BSedPW8A.js} +14 -6
  28. package/dist/discovery-BSedPW8A.js.map +1 -0
  29. package/dist/{discovery-CRDx6TS4.cjs → discovery-DSLgRQhN.cjs} +14 -6
  30. package/dist/discovery-DSLgRQhN.cjs.map +1 -0
  31. package/dist/feeds/index.cjs +2 -2
  32. package/dist/feeds/index.js +2 -2
  33. package/dist/feeds/server/host.d.ts +2 -2
  34. package/dist/feeds/server/index.cjs +11 -11
  35. package/dist/feeds/server/index.cjs.map +1 -1
  36. package/dist/feeds/server/index.js +11 -11
  37. package/dist/feeds/server/index.js.map +1 -1
  38. package/dist/feeds/server/refreshResult.d.ts +2 -2
  39. package/dist/files/index.cjs +3 -2
  40. package/dist/files/index.cjs.map +1 -1
  41. package/dist/files/index.js +3 -2
  42. package/dist/files/index.js.map +1 -1
  43. package/dist/google/calendar.d.ts +24 -24
  44. package/dist/google/collectionSync.d.ts +2 -2
  45. package/dist/google/index.cjs +3 -3
  46. package/dist/google/index.cjs.map +1 -1
  47. package/dist/google/index.js +3 -3
  48. package/dist/google/index.js.map +1 -1
  49. package/dist/{graph-DjE1Az1C.cjs → graph-BZJxiv1E.cjs} +10 -12
  50. package/dist/graph-BZJxiv1E.cjs.map +1 -0
  51. package/dist/{graph-BcwqiSpR.js → graph-Ce7xsDTt.js} +10 -12
  52. package/dist/graph-Ce7xsDTt.js.map +1 -0
  53. package/dist/{ingestTypes-C7IJSqv4.cjs → ingestTypes-CPR-h-OZ.cjs} +2 -2
  54. package/dist/{ingestTypes-C7IJSqv4.cjs.map → ingestTypes-CPR-h-OZ.cjs.map} +1 -1
  55. package/dist/{ingestTypes-DmtDLRmt.js → ingestTypes-F2IJhOLJ.js} +2 -2
  56. package/dist/{ingestTypes-DmtDLRmt.js.map → ingestTypes-F2IJhOLJ.js.map} +1 -1
  57. package/dist/notifier/index.cjs +1 -1
  58. package/dist/notifier/index.js +1 -1
  59. package/dist/notifier/types.d.ts +4 -4
  60. package/dist/{notifier-Wc4M24Bi.cjs → notifier-CKFVVwDP.cjs} +35 -24
  61. package/dist/notifier-CKFVVwDP.cjs.map +1 -0
  62. package/dist/{notifier-Dxh8mEFh.js → notifier-CQD8NljP.js} +35 -24
  63. package/dist/notifier-CQD8NljP.js.map +1 -0
  64. package/dist/{promptSafety-B52GvaJc.cjs → promptSafety-DUZ5BiW5.cjs} +11 -7
  65. package/dist/promptSafety-DUZ5BiW5.cjs.map +1 -0
  66. package/dist/{promptSafety-DJgaDjM4.js → promptSafety-DgHMF4Pz.js} +11 -7
  67. package/dist/promptSafety-DgHMF4Pz.js.map +1 -0
  68. package/dist/remote-host/server/hostRunner.d.ts +7 -2
  69. package/dist/remote-host/server/index.cjs +12 -9
  70. package/dist/remote-host/server/index.cjs.map +1 -1
  71. package/dist/remote-host/server/index.js +13 -10
  72. package/dist/remote-host/server/index.js.map +1 -1
  73. package/dist/remote-view/index.cjs.map +1 -1
  74. package/dist/remote-view/index.d.ts +1 -1
  75. package/dist/remote-view/index.js.map +1 -1
  76. package/dist/scheduler/adapter.d.ts +4 -4
  77. package/dist/scheduler/index.cjs +34 -2
  78. package/dist/scheduler/index.cjs.map +1 -1
  79. package/dist/scheduler/index.js +34 -2
  80. package/dist/scheduler/index.js.map +1 -1
  81. package/dist/scheduler/task-manager.d.ts +2 -2
  82. package/dist/{server-D7_oRbTJ.cjs → server-1c6gB372.cjs} +12 -8
  83. package/dist/server-1c6gB372.cjs.map +1 -0
  84. package/dist/{server-D6EUf3BF.js → server-Dv_L7l0y.js} +12 -8
  85. package/dist/server-Dv_L7l0y.js.map +1 -0
  86. package/dist/skill-bridge/index.cjs +3 -4
  87. package/dist/skill-bridge/index.cjs.map +1 -1
  88. package/dist/skill-bridge/index.js +3 -4
  89. package/dist/skill-bridge/index.js.map +1 -1
  90. package/dist/types-BKVZrtyc.js.map +1 -1
  91. package/dist/types-CNqkLT4M.cjs.map +1 -1
  92. package/dist/utils/fetch.cjs.map +1 -1
  93. package/dist/utils/fetch.d.ts +1 -1
  94. package/dist/utils/fetch.js.map +1 -1
  95. package/dist/whisper/client.cjs.map +1 -1
  96. package/dist/whisper/client.d.ts +3 -3
  97. package/dist/whisper/client.js.map +1 -1
  98. package/dist/wiki/index.cjs +2 -2
  99. package/dist/wiki/index.cjs.map +1 -1
  100. package/dist/wiki/index.js +2 -2
  101. package/dist/wiki/index.js.map +1 -1
  102. package/dist/wiki/server/index.cjs +15 -12
  103. package/dist/wiki/server/index.cjs.map +1 -1
  104. package/dist/wiki/server/index.js +15 -12
  105. package/dist/wiki/server/index.js.map +1 -1
  106. package/package.json +6 -6
  107. package/dist/discovery-CRDx6TS4.cjs.map +0 -1
  108. package/dist/discovery-D6p4ITNR.js.map +0 -1
  109. package/dist/graph-BcwqiSpR.js.map +0 -1
  110. package/dist/graph-DjE1Az1C.cjs.map +0 -1
  111. package/dist/notifier-Dxh8mEFh.js.map +0 -1
  112. package/dist/notifier-Wc4M24Bi.cjs.map +0 -1
  113. package/dist/promptSafety-B52GvaJc.cjs.map +0 -1
  114. package/dist/promptSafety-DJgaDjM4.js.map +0 -1
  115. package/dist/server-D6EUf3BF.js.map +0 -1
  116. package/dist/server-D7_oRbTJ.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"calendarGrid-BQYqOCnF.cjs","names":[],"sources":["../src/collection/core/fieldText.ts","../src/collection/core/schema.ts","../src/collection/core/ids.ts","../src/collection/core/calendarGrid.ts"],"sourcesContent":["// Turning a record field into text.\n//\n// `CollectionItem` is `Record<string, unknown>`, so a field holds whatever the\n// record's JSON had — including arrays and objects (real workspace data has\n// plenty: weather `hourly`, GeoJSON `geometry`, `sites` lists). Bare\n// `String(value)` on one of those yields `\"[object Object]\"`, which then gets\n// compared, matched or displayed as if it were a value. Nothing throws; a\n// predicate just silently stops matching, or the UI shows `[object Object]`.\n//\n// These two helpers are the only sanctioned way to read a field as text. The\n// rule they encode was already in `itemLabelOf`: accept primitives, let\n// everything else fall through to the caller's fallback.\n\n/** A field value that has a meaningful text form. Dates are included because\n * a JSON record can carry one once it has been revived. */\nconst isTextable = (value: unknown): value is string | number | boolean | Date =>\n typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\" || value instanceof Date;\n\n/** The field's text, or `null` when it has no meaningful one (absent, or an\n * array/object that would stringify to `\"[object Object]\"`).\n *\n * Returning `null` rather than `\"\"` keeps \"the field is empty\" distinct from\n * \"the field can't be text\" — a matcher must not treat an object-valued field\n * as an empty string and match `\"\"`. */\nexport function fieldTextOrNull(value: unknown): string | null {\n if (value === undefined || value === null) return null;\n if (!isTextable(value)) return null;\n if (value instanceof Date) {\n // `new Date(\"nonsense\")` is still `instanceof Date`, and `toISOString()`\n // throws `RangeError` on it. This helper sits on the match, sort and\n // display paths, so one unparseable date in one record would take the whole\n // render down — the loud version of the bug this module exists to prevent.\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n return String(value);\n}\n\n/** The field's text, or `fallback` (default `\"\"`) when it has none. Use where\n * a string is required and an empty one is a safe stand-in — display, sort\n * keys, CSV cells. Where the distinction matters, use {@link fieldTextOrNull}. */\nexport function fieldText(value: unknown, fallback = \"\"): string {\n return fieldTextOrNull(value) ?? fallback;\n}\n","// Schema-driven collection types. A \"collection\" is a skill (under\n// .claude/skills/<slug>/) that also ships a sibling `schema.json`.\n// The host's <CollectionView> reads the schema + records and renders\n// a table/form; Claude reads SKILL.md and CRUDs the records as JSON\n// files.\n//\n// SINGLE SOURCE OF TRUTH: every type describing the schema.json contract is\n// derived (`z.infer`) from the zod definitions in `./schemaZ` — the shapes,\n// their doc comments, and the validation rules live THERE; this module only\n// re-derives the TypeScript names consumers import. The imports from\n// `./schemaZ` are type-only, so zod never reaches the browser bundle through\n// the isomorphic barrel; at runtime the dependency points the other way\n// (schemaZ imports this module's consts).\n//\n// Field specs are a DISCRIMINATED UNION on `type`: narrow with\n// `field.type === \"enum\"` (etc.) before reading a variant key like `values`,\n// `to`, `formula`, or `of`.\n\nimport { fieldText } from \"./fieldText\";\nimport type { z } from \"zod\";\nimport type {\n ActionSpecZ,\n CollectionSchemaZ,\n CustomViewZ,\n DataSourceZ,\n DynamicIconRuleZ,\n DynamicIconSourceZ,\n DynamicIconSpecZ,\n EveryFieldDrivenZ,\n EveryLiteralZ,\n EveryZ,\n FieldSpecZ,\n IngestZ,\n SpawnZ,\n StorageZ,\n SubFieldSpecZ,\n WhenZ,\n} from \"./schemaZ\";\n\n/** Minimal \"this collection is a feed\" descriptor carried on the schema.\n * Deliberately narrow — the canonical collection contract stays\n * independent of the host's feeds subsystem. The host's richer retrieval\n * spec (`IngestSpec` in `feeds/ingestTypes.ts`) is a subtype, so feed code\n * reads the extra fields by typing feed schemas with that subtype;\n * collection rendering only needs these three + the presence check. */\nexport interface CollectionIngest {\n kind: string;\n schedule: string;\n /** Optional time-of-day anchor for `schedule: \"daily\"` — the hour (0–23) to\n * refresh around (the host ticks hourly, so the run lands within that hour).\n * Ignored for non-daily schedules. Absent ⇒ elapsed-based daily (\"≥24 h since\n * the last run\"). NOTE: **UTC**, not local — compared via `getUTCHours()` for\n * an unambiguous, DST-free check (matching the rest of the scheduler), so\n * convert local times before writing (e.g. 07:00 JST → `atHour: 22`). */\n atHour?: number;\n /** Declarative retrievers (`rss`/`atom`/`http-json`) only — the host fetches\n * this URL on the schedule. Absent for `kind: \"agent\"`, where the agent owns\n * retrieval. */\n url?: string;\n /** `kind: \"agent\"` only: role id the scheduled hidden worker runs in. */\n role?: string;\n /** `kind: \"agent\"` only: skill-relative template path (under `templates/`)\n * whose prose tells the worker how to refresh the records. */\n template?: string;\n}\n\n/** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's\n * feeds engine dispatches on these; they live here (with the schema contract)\n * so the schema validator can enforce them. The host re-exports these from\n * `server/workspace/feeds/ingestTypes.ts`. */\nexport const INGEST_KINDS = [\"rss\", \"atom\", \"http-json\"] as const;\nexport type IngestKind = (typeof INGEST_KINDS)[number];\n\n/** The agent-performed ingest kind. Instead of a declarative fetch, the host\n * dispatches a hidden background chat (origin `system`) in `ingest.role`,\n * seeded with `ingest.template` + a summary of every record, on the\n * `ingest.schedule` cadence; the worker edits records via the collections io\n * layer. Kept separate from {@link INGEST_KINDS} (which the declarative\n * retriever registry keys on) so the schema validator can model `ingest` as a\n * discriminated union without the feeds engine gaining an \"agent\" retriever. */\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\n/** Refresh cadences a Feed's `ingest.schedule` may declare. */\nexport const FEED_SCHEDULES = [\"hourly\", \"daily\", \"weekly\", \"on-demand\"] as const;\nexport type FeedSchedule = (typeof FEED_SCHEDULES)[number];\n\n// \"feed\" collections live in the non-skill `<workspace>/feeds/` registry\n// and carry an `ingest` block; they reuse the same storage + rendering\n// as skill-backed collections but are never loaded into the agent prompt.\nexport type CollectionSource = \"user\" | \"project\" | \"feed\";\n\n/** One field of a record — a discriminated union on `type`; see the variant\n * docs in `./schemaZ` (`FieldSpecZ`). */\nexport type CollectionFieldSpec = z.infer<typeof FieldSpecZ>;\n\n/** A `table` field's row sub-schema entry — the field union minus `table` /\n * `derived` / display-only types (see `SubFieldSpecZ`). */\nexport type CollectionSubFieldSpec = z.infer<typeof SubFieldSpecZ>;\n\nexport type CollectionFieldType = CollectionFieldSpec[\"type\"];\n\n/** The computed-boolean variant — a `where` predicate bound to a field\n * name; see `FlagFieldZ`. */\nexport type CollectionFlagField = Extract<CollectionFieldSpec, { type: \"flag\" }>;\n\n/** derived/embed/backlinks/rollup/toggle/flag are host-computed or\n * projected — never written to the record JSON, so required / value\n * checks and edit-draft slots must not apply to them. THE single source\n * for \"computed\" — lives here (zod-free at runtime) so browser code\n * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports\n * it) share one set instead of drifting copies. */\nexport const COMPUTED_TYPES: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"derived\", \"embed\", \"backlinks\", \"rollup\", \"toggle\", \"flag\"]);\n\n/** Optional visibility predicate: the target (an action button or a\n * field) renders only when the open record's `field` (stringified) is\n * one of `in`. Generic and domain-free — the host evaluates it against\n * the record with no knowledge of what the field means. Absent ⇒\n * always shown. */\nexport type CollectionWhen = z.infer<typeof WhenZ>;\n\n/** @deprecated Name retained for back-compat; use {@link CollectionWhen}.\n * Both actions and fields share the same predicate shape. No in-repo\n * consumers, but the package is public API (MulmoTerminal). */\n// eslint-disable-next-line sonarjs/redundant-type-aliases -- deliberate deprecated back-compat export\nexport type CollectionActionWhen = CollectionWhen;\n\n/** A schema-declared, per-record action rendered as a button in the\n * read-only detail view. Pure UI/behaviour directive — never stored,\n * never validated against record data. All domain specifics (label,\n * role, template — or the declarative `set`) live in the schema / skill\n * folder, so the host stays generic. A discriminated union on `kind`;\n * see `ActionSpecZ`. */\nexport type CollectionAction = z.infer<typeof ActionSpecZ>;\n\n/** The kind of work an action kicks off: `\"chat\"` (visible LLM chat),\n * `\"agent\"` (hidden LLM worker), or `\"mutate\"` (declarative host write,\n * no LLM). */\nexport type CollectionActionKind = CollectionAction[\"kind\"];\n\n/** The LLM-seeded action variants (`role` + `template`). */\nexport type CollectionSeededAction = Extract<CollectionAction, { kind: \"chat\" | \"agent\" }>;\n\n/** The declarative host-write variant (`set` + optional `require`/`params`). */\nexport type CollectionMutateAction = Extract<CollectionAction, { kind: \"mutate\" }>;\n\n/** A custom (LLM-authored) HTML view for a collection. The host renders\n * `file` in a sandboxed iframe over the collection's records; the view\n * reaches its data only through a slug- and capability-scoped token (see\n * `server/api/auth/viewToken.ts`). Pure data — the host holds no\n * view-specific code; meaning lives in the HTML file + this registration.\n * See `CustomViewZ` for the per-key contracts. */\nexport type CollectionCustomView = z.infer<typeof CustomViewZ>;\n\n/** What a custom view's capability token is allowed to do against the\n * collection's data endpoint. `read` returns enriched records (getItems\n * semantics); `write` validates-and-stores rows (putItems semantics).\n * There is deliberately no `delete` — a view can never do more than the\n * agent's own `manageCollection` tool. */\nexport type CollectionViewCapability = NonNullable<CollectionCustomView[\"capabilities\"]>[number];\n\n/** How a `spawn` advances the source item's `triggerField` date to\n * produce the successor's. All arithmetic is done on the civil\n * (year, month, day) triple — never by adding milliseconds — so month\n * lengths and leap years are handled correctly. */\nexport type CollectionEvery = z.infer<typeof EveryLiteralZ>;\n\n/** Recurrence unit for a `spawn.every` advance. */\nexport type CollectionRecurUnit = CollectionEvery[\"unit\"];\n\n/** Field-driven recurrence: the advance interval is selected PER RECORD by\n * the value of an `enum` field (`fromField`), looked up in `map`. See\n * `EveryFieldDrivenZ`. */\nexport type CollectionEveryFieldDriven = z.infer<typeof EveryFieldDrivenZ>;\n\n/** The `every` of a `spawn`: either a single literal interval applied to\n * every record, or a per-record interval selected by an `enum` field. The\n * literal arm is what `advanceTriggerDate` consumes — the field-driven arm\n * is resolved down to one of its `map` values before the date math runs. */\nexport type CollectionSpawnEvery = z.infer<typeof EveryZ>;\n\n/** Narrowing guard: true when `every` is the field-driven arm. */\nexport function isFieldDrivenEvery(every: CollectionSpawnEvery): every is CollectionEveryFieldDriven {\n return \"fromField\" in every;\n}\n\n/** Host-driven recurrence. See `SpawnZ`. */\nexport type CollectionSpawn = z.infer<typeof SpawnZ>;\n\n/** One rule in a `dynamicIcon.rules` list: when the resolved source\n * record matches `where` (an AND of typed conditions, see `./where`),\n * the collection's effective launcher icon becomes `icon`. Evaluated top\n * to bottom — the first match wins. */\nexport type DynamicIconRule = z.infer<typeof DynamicIconRuleZ>;\n\n/** Where a {@link DynamicIconSpec}'s source record comes from: a (possibly\n * cross-collection) pool of records, optionally narrowed by `where` and\n * reduced to a single record by `from`. */\nexport type DynamicIconSource = z.infer<typeof DynamicIconSourceZ>;\n\n/** Declarative \"data state → icon\" mapping for a collection's launcher\n * shortcut icon (see `CollectionSchema.dynamicIcon`). When absent, the\n * launcher icon is the static `schema.icon`. */\nexport type DynamicIconSpec = z.infer<typeof DynamicIconSpecZ>;\n\n/** The `ingest` block as the schema validator accepts it — a discriminated\n * union on `kind` (declarative retrievers | agent worker). The feeds\n * subsystem's `IngestSpec` is the same union under its historical name. */\nexport type CollectionIngestSpec = z.infer<typeof IngestZ>;\n\n/** The `dataSource` block: this collection's records are the rows of an\n * external read-only data file (v1: CSV). See `DataSourceZ`. */\nexport type CollectionDataSource = z.infer<typeof DataSourceZ>;\n\n/** The `storage` block: an alternative WRITABLE record backend (v1:\n * sqlite). See `StorageZ`. */\nexport type CollectionStorage = z.infer<typeof StorageZ>;\n\n/** Every storage backend a schema can select. `file` is the implicit\n * default (`dataPath`); `csv` is implied by `dataSource`; other kinds are\n * named explicitly via `storage.type`. The server's store factory registry\n * (`server/store.ts`) is keyed by this. */\nexport type CollectionStorageKind = \"file\" | \"csv\" | \"sqlite\";\n\n/** Which storage backend serves this schema's records. Derived, not stored:\n * existing schemas carry no `storage` key and must keep resolving exactly\n * as before (`dataSource` ⇒ csv, else file). */\nexport function storageKindFor(schema: Pick<CollectionSchema, \"dataSource\" | \"storage\">): CollectionStorageKind {\n if (schema.dataSource !== undefined) return \"csv\";\n return schema.storage?.type ?? \"file\";\n}\n\n/** The whole `schema.json` contract. Key-level docs live on\n * `CollectionSchemaZ` in `./schemaZ`. */\nexport type CollectionSchema = z.infer<typeof CollectionSchemaZ>;\n\n/** True when `schema` declares an external `dataSource` — i.e. the\n * collection is READ-ONLY through every UI/tool write path (updates\n * happen by editing/replacing the data file itself). Isomorphic: both\n * the server write guards and the client's control hiding key off this\n * one predicate. */\nexport function isReadOnlySchema(schema: Pick<CollectionSchema, \"dataSource\">): boolean {\n return schema.dataSource !== undefined;\n}\n\nexport interface CollectionSummary {\n slug: string;\n title: string;\n icon: string;\n source: CollectionSource;\n /** Present (true) when the collection is backed by an external\n * `dataSource` and therefore read-only in every UI/tool write path.\n * Absent-when-writable, matching the other optional summary flags. */\n readonly?: true;\n /** Slugs of the source collection(s) a `dynamicIcon` icon was computed\n * from — present only when `schema.dynamicIcon` is set. Lets a client\n * know which collection change-channel(s) to watch for a live icon\n * update (see `useDynamicShortcutIcons`). */\n iconSources?: string[];\n}\n\nexport interface CollectionDetail extends CollectionSummary {\n schema: CollectionSchema;\n}\n\nexport type CollectionItem = Record<string, unknown>;\n\n/** Resolve an `embed` field's target record id: the fixed `id`, or the value\n * of the sibling `idField` on this record (empty string when neither applies\n * — the caller renders that as \"no record\"). Pure + isomorphic so the server\n * projection (`derive.ts`) and the client preview (`useCollectionRendering`)\n * resolve embeds identically. Non-`embed` fields resolve to \"no record\". */\nexport function embedTargetId(field: CollectionFieldSpec, record: CollectionItem | null): string {\n if (field.type !== \"embed\") return \"\";\n if (field.id) return field.id;\n if (field.idField && record) return fieldText(record[field.idField]);\n return \"\";\n}\n","// Pure slug / record-id character rules. Shared by the isomorphic schema\n// validator (`./schemaZ`) — which must stay node-free — and the server-side\n// path sanitisers (`../server/paths`), which wrap these patterns with the\n// `path.basename` round-trip CodeQL recognises as a `js/path-injection`\n// sanitiser. Both layers MUST gate on the same patterns; importing them from\n// here is what keeps them in sync.\n\n// The ONE slug pattern — `server/workspace/skills/catalog.ts` imports it\n// for its own sanitiser, so there is no second copy to keep in sync.\n// Bounded character classes, no nested quantifiers; ReDoS-safe.\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;\n\n// Record ids are a superset of slugs: they're only ever filename stems\n// (`<id>.json`), never directory names or URL segments, so they may carry\n// dots — natural keys like a Slack ts (`1718900000.123456`), a SemVer\n// (`1.2.3`), or a decimal timestamp. The interior class adds `.` to the slug\n// set; the explicit `..` reject in `isSafeRecordId` keeps a\n// parent-dir-looking segment out while still allowing repeated `-`/`_`\n// (`a--b`, `a__b`). Start/end stay alphanumeric so leading/trailing dots\n// (hidden files, the special `.`/`..` names) and `..`-only ids are all\n// excluded.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;\n\n/** True when `value` is a well-formed collection slug (alphanumeric /\n * hyphen / underscore, no path separators). The pattern admits no `/`,\n * `\\`, or `.`, so a passing value is trivially also a safe basename —\n * validation callers need no `path.basename` round-trip (path-building\n * callers use `../server/paths#safeSlugName`, which adds it). */\nexport function isSafeSlug(value: string): boolean {\n return typeof value === \"string\" && SAFE_SLUG_PATTERN.test(value);\n}\n\n/** True when `value` is a well-formed record id (slug charset plus interior\n * dots), with any `..` substring rejected explicitly. Validation-only\n * counterpart of `../server/paths#safeRecordId`. */\nexport function isSafeRecordId(value: string): boolean {\n if (typeof value !== \"string\" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;\n return !value.includes(\"..\");\n}\n\nconst DEFAULT_UNIQUE_ID_ATTEMPTS = 8;\n\n/** Pick an id not already in `existing`, re-rolling `generate()` up to\n * `maxAttempts` times before giving up and returning the last candidate.\n * Collisions on a wide id space are astronomically unlikely, so a caller's\n * own overwrite guard is the final backstop rather than an unbounded loop. */\nexport function generateUniqueId(existing: ReadonlySet<string>, generate: () => string, maxAttempts: number = DEFAULT_UNIQUE_ID_ATTEMPTS): string {\n let candidate = generate();\n for (let attempt = 0; attempt < maxAttempts && existing.has(candidate); attempt++) {\n candidate = generate();\n }\n return candidate;\n}\n\n/** An id not already used by any loaded record: collects the in-memory primary\n * keys and re-rolls `generate()` against them (see {@link generateUniqueId}).\n * The pure core of the view's create-form id prefill. */\nexport function nextUniqueItemId(\n items: readonly CollectionItem[],\n primaryKey: string,\n generate: () => string,\n maxAttempts: number = DEFAULT_UNIQUE_ID_ATTEMPTS,\n): string {\n const existing = new Set(items.map((item) => fieldText(item[primaryKey])));\n return generateUniqueId(existing, generate, maxAttempts);\n}\n","// Pure, deterministic helpers for the collection calendar view: parse\n// `date`-field values, build a month grid, and bucket records onto the\n// days they cover. No `Date.now()` / `new Date()` (argless) here — every\n// function takes its inputs explicitly so the logic is unit-testable\n// without faking the clock. All internal arithmetic runs in UTC (which\n// has no DST), so fixed 86_400_000 ms steps never skip or double a day.\n\nconst MS_PER_DAY = 86_400_000;\nconst ISO_DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n// A two-digit field (hours / minutes / seconds) of a clock value.\nconst TWO_DIGIT_RE = /^\\d{2}$/;\n// A single clock token inside a free-form `time` string field (e.g. the\n// \"14:00-17:00\" / \"17:00-\" / \"16:30\" / \"終日\" shapes seen in user data).\nconst CLOCK_RE = /(\\d{1,2}):(\\d{2})/g;\n// Range separators we tolerate between two clock tokens: ASCII hyphen, en/em\n// dash, tilde, and the Japanese wave dashes.\nconst RANGE_SEP_RE = /[-–—~〜~]/;\n\n/** Minutes in a full day — the timeline's vertical extent. */\nexport const MINUTES_PER_DAY = 1440;\n\n/** A civil date triple. `month` is 1-12 (NOT the 0-based `Date` month). */\nexport interface Ymd {\n year: number;\n month: number;\n day: number;\n}\n\n/** One cell of the 6×7 month grid. */\nexport interface DayCell {\n ymd: Ymd;\n /** False for the leading/trailing days that belong to the adjacent\n * month (rendered greyed). */\n inMonth: boolean;\n /** Canonical `YYYY-MM-DD` key for this cell. */\n key: string;\n}\n\n/** A record placed on the calendar: the inclusive `[start, end]` span of\n * days it covers. `end === start` for a single-day record. `startMin` /\n * `endMin` are minutes-of-day for the time-allocation (day) view, resolved\n * from either a `datetime` field's clock or a separate time-string field.\n * `null` means \"no clock\" — `startMin === null && endMin === null` is an\n * all-day record; a non-null `startMin` with a null `endMin` is a\n * point-in-time record (rendered as a single line). */\nexport interface RecordSpan<T> {\n item: T;\n start: Ymd;\n end: Ymd;\n startMin: number | null;\n endMin: number | null;\n}\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n\n/** Canonical `YYYY-MM-DD` string for a civil date. */\nexport function ymdKey(ymd: Ymd): string {\n return `${String(ymd.year).padStart(4, \"0\")}-${pad2(ymd.month)}-${pad2(ymd.day)}`;\n}\n\n/** Strictly parse a `YYYY-MM-DD` string into a civil date, rejecting\n * anything that isn't a real calendar day (e.g. `2026-02-30`, `2026-13-01`).\n * Returns null for non-strings and malformed values so callers can route\n * records with no usable date into the \"no date\" tray rather than crash. */\nexport function parseIsoDate(value: unknown): Ymd | null {\n if (typeof value !== \"string\") return null;\n const match = ISO_DATE_RE.exec(value.trim());\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n // Round-trip through a UTC Date to reject impossible days: a value the\n // Date constructor rolls over (Feb 30 → Mar 2) won't match back.\n const probe = new Date(Date.UTC(year, month - 1, day));\n if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null;\n return { year, month, day };\n}\n\n/** Minutes-of-day for an `HH:MM` pair, or null when out of range. */\nfunction clockToMinutes(hours: number, minutes: number): number | null {\n if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null;\n return hours * 60 + minutes;\n}\n\n/** Strictly parse a `YYYY-MM-DDTHH:MM` (optional `:SS`) datetime into its\n * civil date and minutes-of-day. Returns null for anything that isn't a real\n * calendar day or a valid 24h clock. */\nexport function parseIsoDateTime(value: unknown): { ymd: Ymd; minutes: number } | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n const tIndex = trimmed.indexOf(\"T\");\n if (tIndex === -1) return null;\n const ymd = parseIsoDate(trimmed.slice(0, tIndex));\n if (!ymd) return null;\n // `HH:MM` with an optional `:SS` the browser appends for non-zero seconds.\n const parts = trimmed.slice(tIndex + 1).split(\":\");\n if (parts.length < 2 || parts.length > 3 || !parts.every((part) => TWO_DIGIT_RE.test(part))) return null;\n const minutes = clockToMinutes(Number(parts[0]), Number(parts[1]));\n if (minutes === null) return null;\n return { ymd, minutes };\n}\n\n/** Civil date from either a `YYYY-MM-DD` or a `YYYY-MM-DDTHH:MM` value, so the\n * month grid buckets date-only and datetime anchors alike. */\nexport function dateOf(value: unknown): Ymd | null {\n return parseIsoDate(value) ?? parseIsoDateTime(value)?.ymd ?? null;\n}\n\n/** Minutes-of-day from a datetime value, or null for date-only / invalid. */\nfunction timeOf(value: unknown): number | null {\n return parseIsoDateTime(value)?.minutes ?? null;\n}\n\n/** Parse a free-form time-string field into start/end minutes-of-day.\n * Handles the common shapes in user data:\n * \"14:00-17:00\" → { start: 840, end: 1020 } (range → block)\n * \"17:00-\" → { start: 1020, end: null } (open end → single line)\n * \"16:30\" → { start: 990, end: null } (point in time → single line)\n * \"終日\" / \"\" → null (no clock → all-day)\n * Returns null when no clock token is parseable. */\nexport function parseTimeRange(value: unknown): { startMin: number | null; endMin: number | null } | null {\n if (typeof value !== \"string\") return null;\n const text = value.trim();\n if (!text) return null;\n const tokens = [...text.matchAll(CLOCK_RE)];\n if (tokens.length === 0) return null;\n const minutesOf = (match: RegExpMatchArray): number | null => clockToMinutes(Number(match[1]), Number(match[2]));\n // No separator → a single point in time (start only).\n if (!RANGE_SEP_RE.test(text)) {\n const startMin = minutesOf(tokens[0]);\n return startMin === null ? null : { startMin, endMin: null };\n }\n // Separator present → assign each token to the side of the first separator.\n const sepIndex = text.search(RANGE_SEP_RE);\n let startMin: number | null = null;\n let endMin: number | null = null;\n for (const token of tokens) {\n if ((token.index ?? 0) < sepIndex) startMin = minutesOf(token);\n else endMin = minutesOf(token);\n }\n // A start-less range (\"-09:00\") has no anchor on the timeline → treat as\n // unparseable so the record falls back to the all-day strip.\n if (startMin === null) return null;\n return { startMin, endMin };\n}\n\nfunction ymdToUtcMs(ymd: Ymd): number {\n return Date.UTC(ymd.year, ymd.month - 1, ymd.day);\n}\n\nfunction utcMsToYmd(epochMs: number): Ymd {\n const date = new Date(epochMs);\n return { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() };\n}\n\n/** Chronological comparison: negative if `left` precedes `right`, 0 if the\n * same day, positive if after. */\nexport function compareYmd(left: Ymd, right: Ymd): number {\n return ymdToUtcMs(left) - ymdToUtcMs(right);\n}\n\n/** True iff `day` falls within the inclusive span `[span.start, span.end]`. */\nexport function spanCoversDay<T>(span: RecordSpan<T>, day: Ymd): boolean {\n return compareYmd(span.start, day) <= 0 && compareYmd(day, span.end) <= 0;\n}\n\n/** Build the 6×7 (42-cell) grid for the given month, including the\n * leading/trailing days of the adjacent months so every week is full.\n * `month` is 1-12. `weekStartsOn` is 0 (Sunday) … 6 (Saturday). */\nexport function buildMonthGrid(year: number, month: number, weekStartsOn = 0): DayCell[] {\n const firstWeekday = new Date(Date.UTC(year, month - 1, 1)).getUTCDay();\n const lead = (firstWeekday - weekStartsOn + 7) % 7;\n const startMs = Date.UTC(year, month - 1, 1) - lead * MS_PER_DAY;\n const cells: DayCell[] = [];\n for (let i = 0; i < 42; i++) {\n const ymd = utcMsToYmd(startMs + i * MS_PER_DAY);\n cells.push({ ymd, inMonth: ymd.year === year && ymd.month === month, key: ymdKey(ymd) });\n }\n return cells;\n}\n\n/** Resolve a record's calendar span from its date/datetime fields. Returns\n * null when the anchor date is missing/invalid (→ the caller's \"no date\"\n * tray). An end date that is missing, invalid, or earlier than the start\n * collapses to a single-day span — never an inverted range.\n *\n * Times for the day (time-allocation) view come from, in priority order:\n * 1. the clock on a `datetime` anchor/end value, else\n * 2. `timeField` — a separate free-form time-string column (e.g. \"14:00-17:00\").\n * A record with no resolvable clock has `startMin === endMin === null`. */\nexport function recordSpan<T extends Record<string, unknown>>(item: T, anchorField: string, endField?: string, timeField?: string): RecordSpan<T> | null {\n const startRaw = item[anchorField];\n const start = dateOf(startRaw);\n if (!start) return null;\n let end = start;\n let startMin = timeOf(startRaw);\n let endMin: number | null = null;\n if (endField) {\n const endRaw = item[endField];\n const parsedEnd = dateOf(endRaw);\n if (parsedEnd && compareYmd(parsedEnd, start) >= 0) {\n end = parsedEnd;\n endMin = timeOf(endRaw);\n }\n }\n // Fall back to a separate time-string field only when the date fields\n // carried no clock (the date-only anchor + `time` column shape).\n if (timeField && startMin === null && endMin === null) {\n const range = parseTimeRange(item[timeField]);\n if (range) {\n ({ startMin, endMin } = range);\n }\n }\n return { item, start, end, startMin, endMin };\n}\n\n/** Split records into those that land on the calendar (with their spans)\n * and those with no usable anchor date (the \"no date\" tray). Spans are\n * sorted by start day so same-day stacking is stable across renders. */\nexport function bucketRecords<T extends Record<string, unknown>>(\n items: readonly T[],\n anchorField: string,\n endField?: string,\n timeField?: string,\n): { spans: RecordSpan<T>[]; noDate: T[] } {\n const spans: RecordSpan<T>[] = [];\n const noDate: T[] = [];\n for (const item of items) {\n const span = recordSpan(item, anchorField, endField, timeField);\n if (span) spans.push(span);\n else noDate.push(item);\n }\n spans.sort((left, right) => compareYmd(left.start, right.start));\n return { spans, noDate };\n}\n\n/** Geometry for one record on one day of the time-allocation view.\n * `kind`:\n * \"allDay\" — no clock anywhere → render in the bottom all-day strip.\n * \"line\" — a single point in time → a 1px marker at `startMin`.\n * \"block\" — a [startMin, endMin) span, clamped to this day's [0, 1440).\n * `bleedsBefore` / `bleedsAfter` flag a multi-day span that began on an\n * earlier day or continues onto a later one (so the view can show arrows). */\nexport interface DaySlice {\n kind: \"allDay\" | \"line\" | \"block\";\n startMin: number;\n endMin: number;\n bleedsBefore: boolean;\n bleedsAfter: boolean;\n}\n\n/** Project a record's span onto a single day for the time-allocation view, or\n * null when the span doesn't cover that day. */\nexport function daySlice<T>(span: RecordSpan<T>, day: Ymd): DaySlice | null {\n if (!spanCoversDay(span, day)) return null;\n // Destructured so the `!== null` tests narrow the clocks where they are used;\n // reading them back off `span` each time would not.\n const { startMin: spanStart, endMin: spanEnd } = span;\n if (spanStart === null && spanEnd === null) {\n return { kind: \"allDay\", startMin: 0, endMin: MINUTES_PER_DAY, bleedsBefore: false, bleedsAfter: false };\n }\n const singleDay = compareYmd(span.start, span.end) === 0;\n const isStartDay = compareYmd(day, span.start) === 0;\n const isEndDay = compareYmd(day, span.end) === 0;\n // A point in time: a start clock with no end, all on one day.\n if (singleDay && spanStart !== null && spanEnd === null) {\n return { kind: \"line\", startMin: spanStart, endMin: spanStart, bleedsBefore: false, bleedsAfter: false };\n }\n const startMin = isStartDay && spanStart !== null ? spanStart : 0;\n const endMin = isEndDay && spanEnd !== null ? spanEnd : MINUTES_PER_DAY;\n // Zero-length or inverted same-day range → degrade to a line.\n if (singleDay && endMin <= startMin) {\n return { kind: \"line\", startMin, endMin: startMin, bleedsBefore: false, bleedsAfter: false };\n }\n return { kind: \"block\", startMin, endMin, bleedsBefore: !isStartDay, bleedsAfter: !isEndDay };\n}\n\n/** Side-by-side lane assignment for overlapping timeline blocks. Each input\n * is an `[startMin, endMin)` interval; the result (parallel to the input)\n * gives each item its `lane` (column index) and the `lanes` total of its\n * overlap cluster, so a renderer can size every block to `1 / lanes` width\n * and offset it by `lane / lanes`. Non-overlapping items get `lanes === 1`. */\nexport interface LaneSpan {\n startMin: number;\n endMin: number;\n}\nexport interface LaneAssignment {\n lane: number;\n lanes: number;\n}\n\nexport function assignLanes(blocks: readonly LaneSpan[]): LaneAssignment[] {\n const order = [...blocks.keys()].sort((left, right) => blocks[left].startMin - blocks[right].startMin || blocks[left].endMin - blocks[right].endMin);\n const result: LaneAssignment[] = blocks.map(() => ({ lane: 0, lanes: 1 }));\n let cluster: number[] = [];\n let clusterEnd = Number.NEGATIVE_INFINITY;\n const laneEnds: number[] = [];\n const flush = (): void => {\n for (const index of cluster) result[index].lanes = laneEnds.length;\n cluster = [];\n laneEnds.length = 0;\n clusterEnd = Number.NEGATIVE_INFINITY;\n };\n for (const index of order) {\n const block = blocks[index];\n if (cluster.length > 0 && block.startMin >= clusterEnd) flush();\n let lane = laneEnds.findIndex((end) => end <= block.startMin);\n if (lane === -1) {\n lane = laneEnds.length;\n laneEnds.push(block.endMin);\n } else {\n laneEnds[lane] = block.endMin;\n }\n result[index].lane = lane;\n cluster.push(index);\n clusterEnd = Math.max(clusterEnd, block.endMin);\n }\n flush();\n return result;\n}\n\n/** Month label key inputs — returns the 1st of the month as a `Date` so the\n * component can feed it to `Intl.DateTimeFormat(locale, …)` for a localized\n * \"April 2026\" header without this module taking a locale dependency. */\nexport function monthAnchorDate(year: number, month: number): Date {\n return new Date(Date.UTC(year, month - 1, 1));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"calendarGrid-p8K-7cuB.cjs","names":[],"sources":["../src/collection/core/fieldText.ts","../src/collection/core/schema.ts","../src/collection/core/ids.ts","../src/collection/core/calendarGrid.ts"],"sourcesContent":["// Turning a record field into text.\n//\n// `CollectionItem` is `Record<string, unknown>`, so a field holds whatever the\n// record's JSON had — including arrays and objects (real workspace data has\n// plenty: weather `hourly`, GeoJSON `geometry`, `sites` lists). Bare\n// `String(value)` on one of those yields `\"[object Object]\"`, which then gets\n// compared, matched or displayed as if it were a value. Nothing throws; a\n// predicate just silently stops matching, or the UI shows `[object Object]`.\n//\n// These two helpers are the only sanctioned way to read a field as text. The\n// rule they encode was already in `itemLabelOf`: accept primitives, let\n// everything else fall through to the caller's fallback.\n\n/** A field value that has a meaningful text form. Dates are included because\n * a JSON record can carry one once it has been revived. */\nconst isTextable = (value: unknown): value is string | number | boolean | Date =>\n typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\" || value instanceof Date;\n\n/** The field's text, or `null` when it has no meaningful one (absent, or an\n * array/object that would stringify to `\"[object Object]\"`).\n *\n * Returning `null` rather than `\"\"` keeps \"the field is empty\" distinct from\n * \"the field can't be text\" — a matcher must not treat an object-valued field\n * as an empty string and match `\"\"`. */\nexport function fieldTextOrNull(value: unknown): string | null {\n if (value === undefined || value === null) return null;\n if (!isTextable(value)) return null;\n if (value instanceof Date) {\n // `new Date(\"nonsense\")` is still `instanceof Date`, and `toISOString()`\n // throws `RangeError` on it. This helper sits on the match, sort and\n // display paths, so one unparseable date in one record would take the whole\n // render down — the loud version of the bug this module exists to prevent.\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n return String(value);\n}\n\n/** The field's text, or `fallback` (default `\"\"`) when it has none. Use where\n * a string is required and an empty one is a safe stand-in — display, sort\n * keys, CSV cells. Where the distinction matters, use {@link fieldTextOrNull}. */\nexport function fieldText(value: unknown, fallback = \"\"): string {\n return fieldTextOrNull(value) ?? fallback;\n}\n","// Schema-driven collection types. A \"collection\" is a skill (under\n// .claude/skills/<slug>/) that also ships a sibling `schema.json`.\n// The host's <CollectionView> reads the schema + records and renders\n// a table/form; Claude reads SKILL.md and CRUDs the records as JSON\n// files.\n//\n// SINGLE SOURCE OF TRUTH: every type describing the schema.json contract is\n// derived (`z.infer`) from the zod definitions in `./schemaZ` — the shapes,\n// their doc comments, and the validation rules live THERE; this module only\n// re-derives the TypeScript names consumers import. The imports from\n// `./schemaZ` are type-only, so zod never reaches the browser bundle through\n// the isomorphic barrel; at runtime the dependency points the other way\n// (schemaZ imports this module's consts).\n//\n// Field specs are a DISCRIMINATED UNION on `type`: narrow with\n// `field.type === \"enum\"` (etc.) before reading a variant key like `values`,\n// `to`, `formula`, or `of`.\n\nimport { fieldText } from \"./fieldText\";\nimport type { z } from \"zod\";\nimport type {\n ActionSpecZ,\n CollectionSchemaZ,\n CustomViewZ,\n DataSourceZ,\n DynamicIconRuleZ,\n DynamicIconSourceZ,\n DynamicIconSpecZ,\n EveryFieldDrivenZ,\n EveryLiteralZ,\n EveryZ,\n FieldSpecZ,\n IngestZ,\n SpawnZ,\n StorageZ,\n SubFieldSpecZ,\n WhenZ,\n} from \"./schemaZ\";\n\n/** Minimal \"this collection is a feed\" descriptor carried on the schema.\n * Deliberately narrow — the canonical collection contract stays\n * independent of the host's feeds subsystem. The host's richer retrieval\n * spec (`IngestSpec` in `feeds/ingestTypes.ts`) is a subtype, so feed code\n * reads the extra fields by typing feed schemas with that subtype;\n * collection rendering only needs these three + the presence check. */\nexport interface CollectionIngest {\n kind: string;\n schedule: string;\n /** Optional time-of-day anchor for `schedule: \"daily\"` — the hour (0–23) to\n * refresh around (the host ticks hourly, so the run lands within that hour).\n * Ignored for non-daily schedules. Absent ⇒ elapsed-based daily (\"≥24 h since\n * the last run\"). NOTE: **UTC**, not local — compared via `getUTCHours()` for\n * an unambiguous, DST-free check (matching the rest of the scheduler), so\n * convert local times before writing (e.g. 07:00 JST → `atHour: 22`). */\n atHour?: number;\n /** Declarative retrievers (`rss`/`atom`/`http-json`) only — the host fetches\n * this URL on the schedule. Absent for `kind: \"agent\"`, where the agent owns\n * retrieval. */\n url?: string;\n /** `kind: \"agent\"` only: role id the scheduled hidden worker runs in. */\n role?: string;\n /** `kind: \"agent\"` only: skill-relative template path (under `templates/`)\n * whose prose tells the worker how to refresh the records. */\n template?: string;\n}\n\n/** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's\n * feeds engine dispatches on these; they live here (with the schema contract)\n * so the schema validator can enforce them. The host re-exports these from\n * `server/workspace/feeds/ingestTypes.ts`. */\nexport const INGEST_KINDS = [\"rss\", \"atom\", \"http-json\"] as const;\nexport type IngestKind = (typeof INGEST_KINDS)[number];\n\n/** The agent-performed ingest kind. Instead of a declarative fetch, the host\n * dispatches a hidden background chat (origin `system`) in `ingest.role`,\n * seeded with `ingest.template` + a summary of every record, on the\n * `ingest.schedule` cadence; the worker edits records via the collections io\n * layer. Kept separate from {@link INGEST_KINDS} (which the declarative\n * retriever registry keys on) so the schema validator can model `ingest` as a\n * discriminated union without the feeds engine gaining an \"agent\" retriever. */\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\n/** Refresh cadences a Feed's `ingest.schedule` may declare. */\nexport const FEED_SCHEDULES = [\"hourly\", \"daily\", \"weekly\", \"on-demand\"] as const;\nexport type FeedSchedule = (typeof FEED_SCHEDULES)[number];\n\n// \"feed\" collections live in the non-skill `<workspace>/feeds/` registry\n// and carry an `ingest` block; they reuse the same storage + rendering\n// as skill-backed collections but are never loaded into the agent prompt.\nexport type CollectionSource = \"user\" | \"project\" | \"feed\";\n\n/** One field of a record — a discriminated union on `type`; see the variant\n * docs in `./schemaZ` (`FieldSpecZ`). */\nexport type CollectionFieldSpec = z.infer<typeof FieldSpecZ>;\n\n/** A `table` field's row sub-schema entry — the field union minus `table` /\n * `derived` / display-only types (see `SubFieldSpecZ`). */\nexport type CollectionSubFieldSpec = z.infer<typeof SubFieldSpecZ>;\n\nexport type CollectionFieldType = CollectionFieldSpec[\"type\"];\n\n/** The computed-boolean variant — a `where` predicate bound to a field\n * name; see `FlagFieldZ`. */\nexport type CollectionFlagField = Extract<CollectionFieldSpec, { type: \"flag\" }>;\n\n/** derived/embed/backlinks/rollup/toggle/flag are host-computed or\n * projected — never written to the record JSON, so required / value\n * checks and edit-draft slots must not apply to them. THE single source\n * for \"computed\" — lives here (zod-free at runtime) so browser code\n * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports\n * it) share one set instead of drifting copies. */\nexport const COMPUTED_TYPES: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"derived\", \"embed\", \"backlinks\", \"rollup\", \"toggle\", \"flag\"]);\n\n/** Optional visibility predicate: the target (an action button or a\n * field) renders only when the open record's `field` (stringified) is\n * one of `in`. Generic and domain-free — the host evaluates it against\n * the record with no knowledge of what the field means. Absent ⇒\n * always shown. */\nexport type CollectionWhen = z.infer<typeof WhenZ>;\n\n/** @deprecated Name retained for back-compat; use {@link CollectionWhen}.\n * Both actions and fields share the same predicate shape. No in-repo\n * consumers, but the package is public API (MulmoTerminal). */\n// eslint-disable-next-line sonarjs/redundant-type-aliases -- deliberate deprecated back-compat export\nexport type CollectionActionWhen = CollectionWhen;\n\n/** A schema-declared, per-record action rendered as a button in the\n * read-only detail view. Pure UI/behaviour directive — never stored,\n * never validated against record data. All domain specifics (label,\n * role, template — or the declarative `set`) live in the schema / skill\n * folder, so the host stays generic. A discriminated union on `kind`;\n * see `ActionSpecZ`. */\nexport type CollectionAction = z.infer<typeof ActionSpecZ>;\n\n/** The kind of work an action kicks off: `\"chat\"` (visible LLM chat),\n * `\"agent\"` (hidden LLM worker), or `\"mutate\"` (declarative host write,\n * no LLM). */\nexport type CollectionActionKind = CollectionAction[\"kind\"];\n\n/** The LLM-seeded action variants (`role` + `template`). */\nexport type CollectionSeededAction = Extract<CollectionAction, { kind: \"chat\" | \"agent\" }>;\n\n/** The declarative host-write variant (`set` + optional `require`/`params`). */\nexport type CollectionMutateAction = Extract<CollectionAction, { kind: \"mutate\" }>;\n\n/** A custom (LLM-authored) HTML view for a collection. The host renders\n * `file` in a sandboxed iframe over the collection's records; the view\n * reaches its data only through a slug- and capability-scoped token (see\n * `server/api/auth/viewToken.ts`). Pure data — the host holds no\n * view-specific code; meaning lives in the HTML file + this registration.\n * See `CustomViewZ` for the per-key contracts. */\nexport type CollectionCustomView = z.infer<typeof CustomViewZ>;\n\n/** What a custom view's capability token is allowed to do against the\n * collection's data endpoint. `read` returns enriched records (getItems\n * semantics); `write` validates-and-stores rows (putItems semantics).\n * There is deliberately no `delete` — a view can never do more than the\n * agent's own `manageCollection` tool. */\nexport type CollectionViewCapability = NonNullable<CollectionCustomView[\"capabilities\"]>[number];\n\n/** How a `spawn` advances the source item's `triggerField` date to\n * produce the successor's. All arithmetic is done on the civil\n * (year, month, day) triple — never by adding milliseconds — so month\n * lengths and leap years are handled correctly. */\nexport type CollectionEvery = z.infer<typeof EveryLiteralZ>;\n\n/** Recurrence unit for a `spawn.every` advance. */\nexport type CollectionRecurUnit = CollectionEvery[\"unit\"];\n\n/** Field-driven recurrence: the advance interval is selected PER RECORD by\n * the value of an `enum` field (`fromField`), looked up in `map`. See\n * `EveryFieldDrivenZ`. */\nexport type CollectionEveryFieldDriven = z.infer<typeof EveryFieldDrivenZ>;\n\n/** The `every` of a `spawn`: either a single literal interval applied to\n * every record, or a per-record interval selected by an `enum` field. The\n * literal arm is what `advanceTriggerDate` consumes — the field-driven arm\n * is resolved down to one of its `map` values before the date math runs. */\nexport type CollectionSpawnEvery = z.infer<typeof EveryZ>;\n\n/** Narrowing guard: true when `every` is the field-driven arm. */\nexport function isFieldDrivenEvery(every: CollectionSpawnEvery): every is CollectionEveryFieldDriven {\n return \"fromField\" in every;\n}\n\n/** Host-driven recurrence. See `SpawnZ`. */\nexport type CollectionSpawn = z.infer<typeof SpawnZ>;\n\n/** One rule in a `dynamicIcon.rules` list: when the resolved source\n * record matches `where` (an AND of typed conditions, see `./where`),\n * the collection's effective launcher icon becomes `icon`. Evaluated top\n * to bottom — the first match wins. */\nexport type DynamicIconRule = z.infer<typeof DynamicIconRuleZ>;\n\n/** Where a {@link DynamicIconSpec}'s source record comes from: a (possibly\n * cross-collection) pool of records, optionally narrowed by `where` and\n * reduced to a single record by `from`. */\nexport type DynamicIconSource = z.infer<typeof DynamicIconSourceZ>;\n\n/** Declarative \"data state → icon\" mapping for a collection's launcher\n * shortcut icon (see `CollectionSchema.dynamicIcon`). When absent, the\n * launcher icon is the static `schema.icon`. */\nexport type DynamicIconSpec = z.infer<typeof DynamicIconSpecZ>;\n\n/** The `ingest` block as the schema validator accepts it — a discriminated\n * union on `kind` (declarative retrievers | agent worker). The feeds\n * subsystem's `IngestSpec` is the same union under its historical name. */\nexport type CollectionIngestSpec = z.infer<typeof IngestZ>;\n\n/** The `dataSource` block: this collection's records are the rows of an\n * external read-only data file (v1: CSV). See `DataSourceZ`. */\nexport type CollectionDataSource = z.infer<typeof DataSourceZ>;\n\n/** The `storage` block: an alternative WRITABLE record backend (v1:\n * sqlite). See `StorageZ`. */\nexport type CollectionStorage = z.infer<typeof StorageZ>;\n\n/** Every storage backend a schema can select. `file` is the implicit\n * default (`dataPath`); `csv` is implied by `dataSource`; other kinds are\n * named explicitly via `storage.type`. The server's store factory registry\n * (`server/store.ts`) is keyed by this. */\nexport type CollectionStorageKind = \"file\" | \"csv\" | \"sqlite\";\n\n/** Which storage backend serves this schema's records. Derived, not stored:\n * existing schemas carry no `storage` key and must keep resolving exactly\n * as before (`dataSource` ⇒ csv, else file). */\nexport function storageKindFor(schema: Pick<CollectionSchema, \"dataSource\" | \"storage\">): CollectionStorageKind {\n if (schema.dataSource !== undefined) return \"csv\";\n return schema.storage?.type ?? \"file\";\n}\n\n/** The whole `schema.json` contract. Key-level docs live on\n * `CollectionSchemaZ` in `./schemaZ`. */\nexport type CollectionSchema = z.infer<typeof CollectionSchemaZ>;\n\n/** True when `schema` declares an external `dataSource` — i.e. the\n * collection is READ-ONLY through every UI/tool write path (updates\n * happen by editing/replacing the data file itself). Isomorphic: both\n * the server write guards and the client's control hiding key off this\n * one predicate. */\nexport function isReadOnlySchema(schema: Pick<CollectionSchema, \"dataSource\">): boolean {\n return schema.dataSource !== undefined;\n}\n\nexport interface CollectionSummary {\n slug: string;\n title: string;\n icon: string;\n source: CollectionSource;\n /** Present (true) when the collection is backed by an external\n * `dataSource` and therefore read-only in every UI/tool write path.\n * Absent-when-writable, matching the other optional summary flags. */\n readonly?: true;\n /** Slugs of the source collection(s) a `dynamicIcon` icon was computed\n * from — present only when `schema.dynamicIcon` is set. Lets a client\n * know which collection change-channel(s) to watch for a live icon\n * update (see `useDynamicShortcutIcons`). */\n iconSources?: string[];\n}\n\nexport interface CollectionDetail extends CollectionSummary {\n schema: CollectionSchema;\n}\n\nexport type CollectionItem = Record<string, unknown>;\n\n/** Resolve an `embed` field's target record id: the fixed `id`, or the value\n * of the sibling `idField` on this record (empty string when neither applies\n * — the caller renders that as \"no record\"). Pure + isomorphic so the server\n * projection (`derive.ts`) and the client preview (`useCollectionRendering`)\n * resolve embeds identically. Non-`embed` fields resolve to \"no record\". */\nexport function embedTargetId(field: CollectionFieldSpec, record: CollectionItem | null): string {\n if (field.type !== \"embed\") return \"\";\n if (field.id) return field.id;\n if (field.idField && record) return fieldText(record[field.idField]);\n return \"\";\n}\n","// Pure slug / record-id character rules. Shared by the isomorphic schema\n// validator (`./schemaZ`) — which must stay node-free — and the server-side\n// path sanitisers (`../server/paths`), which wrap these patterns with the\n// `path.basename` round-trip CodeQL recognises as a `js/path-injection`\n// sanitiser. Both layers MUST gate on the same patterns; importing them from\n// here is what keeps them in sync.\n\n// The ONE slug pattern — `server/workspace/skills/catalog.ts` imports it\n// for its own sanitiser, so there is no second copy to keep in sync.\n// Bounded character classes, no nested quantifiers; ReDoS-safe.\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;\n\n// Record ids are a superset of slugs: they're only ever filename stems\n// (`<id>.json`), never directory names or URL segments, so they may carry\n// dots — natural keys like a Slack ts (`1718900000.123456`), a SemVer\n// (`1.2.3`), or a decimal timestamp. The interior class adds `.` to the slug\n// set; the explicit `..` reject in `isSafeRecordId` keeps a\n// parent-dir-looking segment out while still allowing repeated `-`/`_`\n// (`a--b`, `a__b`). Start/end stay alphanumeric so leading/trailing dots\n// (hidden files, the special `.`/`..` names) and `..`-only ids are all\n// excluded.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;\n\n/** True when `value` is a well-formed collection slug (alphanumeric /\n * hyphen / underscore, no path separators). The pattern admits no `/`,\n * `\\`, or `.`, so a passing value is trivially also a safe basename —\n * validation callers need no `path.basename` round-trip (path-building\n * callers use `../server/paths#safeSlugName`, which adds it). */\nexport function isSafeSlug(value: string): boolean {\n return typeof value === \"string\" && SAFE_SLUG_PATTERN.test(value);\n}\n\n/** True when `value` is a well-formed record id (slug charset plus interior\n * dots), with any `..` substring rejected explicitly. Validation-only\n * counterpart of `../server/paths#safeRecordId`. */\nexport function isSafeRecordId(value: string): boolean {\n if (typeof value !== \"string\" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;\n return !value.includes(\"..\");\n}\n\nconst DEFAULT_UNIQUE_ID_ATTEMPTS = 8;\n\n/** Pick an id not already in `existing`, re-rolling `generate()` up to\n * `maxAttempts` times before giving up and returning the last candidate.\n * Collisions on a wide id space are astronomically unlikely, so a caller's\n * own overwrite guard is the final backstop rather than an unbounded loop. */\nexport function generateUniqueId(existing: ReadonlySet<string>, generate: () => string, maxAttempts: number = DEFAULT_UNIQUE_ID_ATTEMPTS): string {\n let candidate = generate();\n for (let attempt = 0; attempt < maxAttempts && existing.has(candidate); attempt++) {\n candidate = generate();\n }\n return candidate;\n}\n\n/** An id not already used by any loaded record: collects the in-memory primary\n * keys and re-rolls `generate()` against them (see {@link generateUniqueId}).\n * The pure core of the view's create-form id prefill. */\nexport function nextUniqueItemId(\n items: readonly CollectionItem[],\n primaryKey: string,\n generate: () => string,\n maxAttempts: number = DEFAULT_UNIQUE_ID_ATTEMPTS,\n): string {\n const existing = new Set(items.map((item) => fieldText(item[primaryKey])));\n return generateUniqueId(existing, generate, maxAttempts);\n}\n","// Pure, deterministic helpers for the collection calendar view: parse\n// `date`-field values, build a month grid, and bucket records onto the\n// days they cover. No `Date.now()` / `new Date()` (argless) here — every\n// function takes its inputs explicitly so the logic is unit-testable\n// without faking the clock. All internal arithmetic runs in UTC (which\n// has no DST), so fixed 86_400_000 ms steps never skip or double a day.\n\nconst MS_PER_DAY = 86_400_000;\nconst ISO_DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n// A two-digit field (hours / minutes / seconds) of a clock value.\nconst TWO_DIGIT_RE = /^\\d{2}$/;\n// A single clock token inside a free-form `time` string field (e.g. the\n// \"14:00-17:00\" / \"17:00-\" / \"16:30\" / \"終日\" shapes seen in user data).\nconst CLOCK_RE = /(\\d{1,2}):(\\d{2})/g;\n// Range separators we tolerate between two clock tokens: ASCII hyphen, en/em\n// dash, tilde, and the Japanese wave dashes.\nconst RANGE_SEP_RE = /[-–—~〜~]/;\n\n/** Minutes in a full day — the timeline's vertical extent. */\nexport const MINUTES_PER_DAY = 1440;\n\n/** A civil date triple. `month` is 1-12 (NOT the 0-based `Date` month). */\nexport interface Ymd {\n year: number;\n month: number;\n day: number;\n}\n\n/** One cell of the 6×7 month grid. */\nexport interface DayCell {\n ymd: Ymd;\n /** False for the leading/trailing days that belong to the adjacent\n * month (rendered greyed). */\n inMonth: boolean;\n /** Canonical `YYYY-MM-DD` key for this cell. */\n key: string;\n}\n\n/** A record placed on the calendar: the inclusive `[start, end]` span of\n * days it covers. `end === start` for a single-day record. `startMin` /\n * `endMin` are minutes-of-day for the time-allocation (day) view, resolved\n * from either a `datetime` field's clock or a separate time-string field.\n * `null` means \"no clock\" — `startMin === null && endMin === null` is an\n * all-day record; a non-null `startMin` with a null `endMin` is a\n * point-in-time record (rendered as a single line). */\nexport interface RecordSpan<T> {\n item: T;\n start: Ymd;\n end: Ymd;\n startMin: number | null;\n endMin: number | null;\n}\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n\n/** Canonical `YYYY-MM-DD` string for a civil date. */\nexport function ymdKey(ymd: Ymd): string {\n return `${String(ymd.year).padStart(4, \"0\")}-${pad2(ymd.month)}-${pad2(ymd.day)}`;\n}\n\n/** Strictly parse a `YYYY-MM-DD` string into a civil date, rejecting\n * anything that isn't a real calendar day (e.g. `2026-02-30`, `2026-13-01`).\n * Returns null for non-strings and malformed values so callers can route\n * records with no usable date into the \"no date\" tray rather than crash. */\nexport function parseIsoDate(value: unknown): Ymd | null {\n if (typeof value !== \"string\") return null;\n const match = ISO_DATE_RE.exec(value.trim());\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n // Round-trip through a UTC Date to reject impossible days: a value the\n // Date constructor rolls over (Feb 30 → Mar 2) won't match back.\n const probe = new Date(Date.UTC(year, month - 1, day));\n if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null;\n return { year, month, day };\n}\n\n/** Minutes-of-day for an `HH:MM` pair, or null when out of range. */\nfunction clockToMinutes(hours: number, minutes: number): number | null {\n if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null;\n return hours * 60 + minutes;\n}\n\n/** Strictly parse a `YYYY-MM-DDTHH:MM` (optional `:SS`) datetime into its\n * civil date and minutes-of-day. Returns null for anything that isn't a real\n * calendar day or a valid 24h clock. */\nexport function parseIsoDateTime(value: unknown): { ymd: Ymd; minutes: number } | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n const tIndex = trimmed.indexOf(\"T\");\n if (tIndex === -1) return null;\n const ymd = parseIsoDate(trimmed.slice(0, tIndex));\n if (!ymd) return null;\n // `HH:MM` with an optional `:SS` the browser appends for non-zero seconds.\n const parts = trimmed.slice(tIndex + 1).split(\":\");\n if (parts.length < 2 || parts.length > 3 || !parts.every((part) => TWO_DIGIT_RE.test(part))) return null;\n const minutes = clockToMinutes(Number(parts[0]), Number(parts[1]));\n if (minutes === null) return null;\n return { ymd, minutes };\n}\n\n/** Civil date from either a `YYYY-MM-DD` or a `YYYY-MM-DDTHH:MM` value, so the\n * month grid buckets date-only and datetime anchors alike. */\nexport function dateOf(value: unknown): Ymd | null {\n return parseIsoDate(value) ?? parseIsoDateTime(value)?.ymd ?? null;\n}\n\n/** Minutes-of-day from a datetime value, or null for date-only / invalid. */\nfunction timeOf(value: unknown): number | null {\n return parseIsoDateTime(value)?.minutes ?? null;\n}\n\n/** Parse a free-form time-string field into start/end minutes-of-day.\n * Handles the common shapes in user data:\n * \"14:00-17:00\" → { start: 840, end: 1020 } (range → block)\n * \"17:00-\" → { start: 1020, end: null } (open end → single line)\n * \"16:30\" → { start: 990, end: null } (point in time → single line)\n * \"終日\" / \"\" → null (no clock → all-day)\n * Returns null when no clock token is parseable. */\nexport function parseTimeRange(value: unknown): { startMin: number | null; endMin: number | null } | null {\n if (typeof value !== \"string\") return null;\n const text = value.trim();\n if (!text) return null;\n const tokens = [...text.matchAll(CLOCK_RE)];\n if (tokens.length === 0) return null;\n const minutesOf = (match: RegExpMatchArray): number | null => clockToMinutes(Number(match[1]), Number(match[2]));\n // No separator → a single point in time (start only).\n if (!RANGE_SEP_RE.test(text)) {\n const [firstToken] = tokens;\n const startMin = firstToken ? minutesOf(firstToken) : null;\n return startMin === null ? null : { startMin, endMin: null };\n }\n // Separator present → assign each token to the side of the first separator.\n const sepIndex = text.search(RANGE_SEP_RE);\n let startMin: number | null = null;\n let endMin: number | null = null;\n for (const token of tokens) {\n if ((token.index ?? 0) < sepIndex) startMin = minutesOf(token);\n else endMin = minutesOf(token);\n }\n // A start-less range (\"-09:00\") has no anchor on the timeline → treat as\n // unparseable so the record falls back to the all-day strip.\n if (startMin === null) return null;\n return { startMin, endMin };\n}\n\nfunction ymdToUtcMs(ymd: Ymd): number {\n return Date.UTC(ymd.year, ymd.month - 1, ymd.day);\n}\n\nfunction utcMsToYmd(epochMs: number): Ymd {\n const date = new Date(epochMs);\n return { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() };\n}\n\n/** Chronological comparison: negative if `left` precedes `right`, 0 if the\n * same day, positive if after. */\nexport function compareYmd(left: Ymd, right: Ymd): number {\n return ymdToUtcMs(left) - ymdToUtcMs(right);\n}\n\n/** True iff `day` falls within the inclusive span `[span.start, span.end]`. */\nexport function spanCoversDay<T>(span: RecordSpan<T>, day: Ymd): boolean {\n return compareYmd(span.start, day) <= 0 && compareYmd(day, span.end) <= 0;\n}\n\n/** Build the 6×7 (42-cell) grid for the given month, including the\n * leading/trailing days of the adjacent months so every week is full.\n * `month` is 1-12. `weekStartsOn` is 0 (Sunday) … 6 (Saturday). */\nexport function buildMonthGrid(year: number, month: number, weekStartsOn = 0): DayCell[] {\n const firstWeekday = new Date(Date.UTC(year, month - 1, 1)).getUTCDay();\n const lead = (firstWeekday - weekStartsOn + 7) % 7;\n const startMs = Date.UTC(year, month - 1, 1) - lead * MS_PER_DAY;\n const cells: DayCell[] = [];\n for (let i = 0; i < 42; i++) {\n const ymd = utcMsToYmd(startMs + i * MS_PER_DAY);\n cells.push({ ymd, inMonth: ymd.year === year && ymd.month === month, key: ymdKey(ymd) });\n }\n return cells;\n}\n\n/** Resolve a record's calendar span from its date/datetime fields. Returns\n * null when the anchor date is missing/invalid (→ the caller's \"no date\"\n * tray). An end date that is missing, invalid, or earlier than the start\n * collapses to a single-day span — never an inverted range.\n *\n * Times for the day (time-allocation) view come from, in priority order:\n * 1. the clock on a `datetime` anchor/end value, else\n * 2. `timeField` — a separate free-form time-string column (e.g. \"14:00-17:00\").\n * A record with no resolvable clock has `startMin === endMin === null`. */\nexport function recordSpan<T extends Record<string, unknown>>(item: T, anchorField: string, endField?: string, timeField?: string): RecordSpan<T> | null {\n const startRaw = item[anchorField];\n const start = dateOf(startRaw);\n if (!start) return null;\n let end = start;\n let startMin = timeOf(startRaw);\n let endMin: number | null = null;\n if (endField) {\n const endRaw = item[endField];\n const parsedEnd = dateOf(endRaw);\n if (parsedEnd && compareYmd(parsedEnd, start) >= 0) {\n end = parsedEnd;\n endMin = timeOf(endRaw);\n }\n }\n // Fall back to a separate time-string field only when the date fields\n // carried no clock (the date-only anchor + `time` column shape).\n if (timeField && startMin === null && endMin === null) {\n const range = parseTimeRange(item[timeField]);\n if (range) {\n ({ startMin, endMin } = range);\n }\n }\n return { item, start, end, startMin, endMin };\n}\n\n/** Split records into those that land on the calendar (with their spans)\n * and those with no usable anchor date (the \"no date\" tray). Spans are\n * sorted by start day so same-day stacking is stable across renders. */\nexport function bucketRecords<T extends Record<string, unknown>>(\n items: readonly T[],\n anchorField: string,\n endField?: string,\n timeField?: string,\n): { spans: RecordSpan<T>[]; noDate: T[] } {\n const spans: RecordSpan<T>[] = [];\n const noDate: T[] = [];\n for (const item of items) {\n const span = recordSpan(item, anchorField, endField, timeField);\n if (span) spans.push(span);\n else noDate.push(item);\n }\n spans.sort((left, right) => compareYmd(left.start, right.start));\n return { spans, noDate };\n}\n\n/** Geometry for one record on one day of the time-allocation view.\n * `kind`:\n * \"allDay\" — no clock anywhere → render in the bottom all-day strip.\n * \"line\" — a single point in time → a 1px marker at `startMin`.\n * \"block\" — a [startMin, endMin) span, clamped to this day's [0, 1440).\n * `bleedsBefore` / `bleedsAfter` flag a multi-day span that began on an\n * earlier day or continues onto a later one (so the view can show arrows). */\nexport interface DaySlice {\n kind: \"allDay\" | \"line\" | \"block\";\n startMin: number;\n endMin: number;\n bleedsBefore: boolean;\n bleedsAfter: boolean;\n}\n\n/** Project a record's span onto a single day for the time-allocation view, or\n * null when the span doesn't cover that day. */\nexport function daySlice<T>(span: RecordSpan<T>, day: Ymd): DaySlice | null {\n if (!spanCoversDay(span, day)) return null;\n // Destructured so the `!== null` tests narrow the clocks where they are used;\n // reading them back off `span` each time would not.\n const { startMin: spanStart, endMin: spanEnd } = span;\n if (spanStart === null && spanEnd === null) {\n return { kind: \"allDay\", startMin: 0, endMin: MINUTES_PER_DAY, bleedsBefore: false, bleedsAfter: false };\n }\n const singleDay = compareYmd(span.start, span.end) === 0;\n const isStartDay = compareYmd(day, span.start) === 0;\n const isEndDay = compareYmd(day, span.end) === 0;\n // A point in time: a start clock with no end, all on one day.\n if (singleDay && spanStart !== null && spanEnd === null) {\n return { kind: \"line\", startMin: spanStart, endMin: spanStart, bleedsBefore: false, bleedsAfter: false };\n }\n const startMin = isStartDay && spanStart !== null ? spanStart : 0;\n const endMin = isEndDay && spanEnd !== null ? spanEnd : MINUTES_PER_DAY;\n // Zero-length or inverted same-day range → degrade to a line.\n if (singleDay && endMin <= startMin) {\n return { kind: \"line\", startMin, endMin: startMin, bleedsBefore: false, bleedsAfter: false };\n }\n return { kind: \"block\", startMin, endMin, bleedsBefore: !isStartDay, bleedsAfter: !isEndDay };\n}\n\n/** Side-by-side lane assignment for overlapping timeline blocks. Each input\n * is an `[startMin, endMin)` interval; the result (parallel to the input)\n * gives each item its `lane` (column index) and the `lanes` total of its\n * overlap cluster, so a renderer can size every block to `1 / lanes` width\n * and offset it by `lane / lanes`. Non-overlapping items get `lanes === 1`. */\nexport interface LaneSpan {\n startMin: number;\n endMin: number;\n}\nexport interface LaneAssignment {\n lane: number;\n lanes: number;\n}\n\n/** One input block paired with its position in the caller's array, so lane\n * assignment can sort freely and still report back in input order. */\ninterface PositionedSpan {\n index: number;\n span: LaneSpan;\n}\n\nfunction sortByStart(blocks: readonly LaneSpan[]): PositionedSpan[] {\n return [...blocks.entries()]\n .map(([index, span]) => ({ index, span }))\n .sort((left, right) => left.span.startMin - right.span.startMin || left.span.endMin - right.span.endMin);\n}\n\ninterface ClusterState {\n /** Clusters already closed. */\n done: PositionedSpan[][];\n /** The cluster still accepting blocks. */\n current: PositionedSpan[];\n /** Latest end minute seen in `current` — the cutoff for the next block. */\n end: number;\n}\n\n/** Cut the start-ordered blocks into overlap clusters: a new cluster begins at\n * the first block that starts at or after every earlier block has ended. */\nfunction splitClusters(ordered: readonly PositionedSpan[]): PositionedSpan[][] {\n // Appends into the accumulator's arrays rather than rebuilding them. The\n // spread form read well but copied both arrays on every block, so a day\n // whose blocks all overlap cost O(n²) allocations to lay out (#2765).\n const initial: ClusterState = { done: [], current: [], end: Number.NEGATIVE_INFINITY };\n const state = ordered.reduce<ClusterState>((acc, block) => {\n if (acc.current.length > 0 && block.span.startMin >= acc.end) {\n acc.done.push(acc.current);\n return { done: acc.done, current: [block], end: block.span.endMin };\n }\n acc.current.push(block);\n return { done: acc.done, current: acc.current, end: Math.max(acc.end, block.span.endMin) };\n }, initial);\n if (state.current.length > 0) state.done.push(state.current);\n return state.done;\n}\n\n/** Greedy lane packing inside one cluster: reuse the first lane already free at\n * this block's start, else open a new one. Every member reports the cluster's\n * final lane count so a renderer can size each block to `1 / lanes`. */\nfunction packCluster(cluster: readonly PositionedSpan[]): [number, LaneAssignment][] {\n const laneEnds: number[] = [];\n const placed = cluster.map(({ index, span }) => {\n const reusable = laneEnds.findIndex((end) => end <= span.startMin);\n const lane = reusable === -1 ? laneEnds.length : reusable;\n laneEnds[lane] = span.endMin;\n return { index, lane };\n });\n return placed.map(({ index, lane }) => [index, { lane, lanes: laneEnds.length }]);\n}\n\nexport function assignLanes(blocks: readonly LaneSpan[]): LaneAssignment[] {\n const clusters = splitClusters(sortByStart(blocks));\n const assignments = new Map<number, LaneAssignment>(clusters.flatMap(packCluster));\n return blocks.map((_, index) => assignments.get(index) ?? { lane: 0, lanes: 1 });\n}\n\n/** Month label key inputs — returns the 1st of the month as a `Date` so the\n * component can feed it to `Intl.DateTimeFormat(locale, …)` for a localized\n * \"April 2026\" header without this module taking a locale dependency. */\nexport function monthAnchorDate(year: number, month: number): Date {\n return new Date(Date.UTC(year, month - 1, 1));\n}\n"],"mappings":"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"}
@@ -18,8 +18,8 @@ export declare function whenMatches(when: WhenPredicate | undefined, record: Rec
18
18
  * (chat/agent), `require` on mutate. Accepts the full CollectionAction
19
19
  * union (each variant declares at most one of the two). */
20
20
  export interface ActionWithWhen {
21
- when?: WhenPredicate;
22
- require?: WhenPredicate;
21
+ when?: WhenPredicate | undefined;
22
+ require?: WhenPredicate | undefined;
23
23
  }
24
24
  /** True when the action's button should render against `record` — and,
25
25
  * server-side, whether it may RUN (visibility is the authorization
@@ -35,7 +35,7 @@ export declare function agentActionRunKey(actionId: string, itemId?: string): st
35
35
  /** Minimal shape this helper needs from a field spec — just its
36
36
  * optional `when` predicate. Accepts the full FieldSpec too. */
37
37
  export interface FieldWithWhen {
38
- when?: WhenPredicate;
38
+ when?: WhenPredicate | undefined;
39
39
  }
40
40
  /** True when the field should render against `record`. A field with
41
41
  * no `when` is always shown; otherwise it's shown only when the
@@ -10,9 +10,9 @@ export interface CompletionSchemaView {
10
10
  fields?: Record<string, {
11
11
  type: string;
12
12
  where?: Where;
13
- }>;
14
- completionField?: string;
15
- completionDoneValues?: readonly string[];
13
+ }> | undefined;
14
+ completionField?: string | undefined;
15
+ completionDoneValues?: readonly string[] | undefined;
16
16
  }
17
17
  /** True iff the schema declares completion tracking AND `item` is done
18
18
  * under whichever completion form the schema uses (see module doc). */
@@ -27,8 +27,8 @@ export interface CompletionChipSchemaView {
27
27
  field?: string;
28
28
  onValue?: string;
29
29
  }>;
30
- completionField?: string;
31
- completionDoneValues?: readonly string[];
30
+ completionField?: string | undefined;
31
+ completionDoneValues?: readonly string[] | undefined;
32
32
  }
33
33
  /** True when an existing FIELD chip already expresses the legacy completion
34
34
  * predicate exactly, so a synthesized "done" chip would be a duplicate: a
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dist = require("../dist-CsgSfWwR.cjs");
3
- const require_calendarGrid = require("../calendarGrid-BQYqOCnF.cjs");
3
+ const require_calendarGrid = require("../calendarGrid-p8K-7cuB.cjs");
4
4
  const require_project = require("../project-C1ep9pvo.cjs");
5
- const require_promptSafety = require("../promptSafety-B52GvaJc.cjs");
5
+ const require_promptSafety = require("../promptSafety-DUZ5BiW5.cjs");
6
6
  //#region src/collection/core/presentCollection.ts
7
7
  var TOOL_NAME = "presentCollection";
8
8
  var TOOL_DEFINITION = {
@@ -271,12 +271,9 @@ function isMissingDraftValue(value) {
271
271
  /** Label of the first required table sub-field empty in any row, else null. */
272
272
  function firstMissingTableSubField(field, rows) {
273
273
  if (field.type !== "table" || !rows) return null;
274
- for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
275
- const row = rows[rowIdx];
276
- for (const [subKey, subField] of Object.entries(field.of)) {
277
- if (!subField.required || subField.type === "boolean") continue;
278
- if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;
279
- }
274
+ for (const [rowIdx, row] of rows.entries()) for (const [subKey, subField] of Object.entries(field.of)) {
275
+ if (!subField.required || subField.type === "boolean") continue;
276
+ if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;
280
277
  }
281
278
  return null;
282
279
  }
@@ -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/chatSeed.ts","../../src/collection/core/sortItems.ts","../../src/collection/core/sortValueOf.ts","../../src/collection/core/textSearch.ts","../../src/collection/core/recordKeys.ts","../../src/collection/core/itemLabel.ts","../../src/collection/core/shortHexId.ts","../../src/collection/core/ontologyGraph.ts"],"sourcesContent":["// presentCollection tool — definition + pure server-side executor.\n//\n// Isomorphic (no Vue, no Node): bundled to the browser as the plugin's\n// `execute`, and run server-side via the host's plugin dispatch route. The\n// executor only validates + echoes the addressing; the live schema + items\n// are fetched client-side by the View through the host's /api/collections\n// routes, so a bad slug surfaces as the View's \"not found\" state.\n\nimport type { ToolContext, ToolDefinition, ToolResult } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentCollection\";\n\n/** Render payload carried in the tool result's `data` field; the View mounts\n * off these. Same shape as the tool args. */\nexport interface PresentCollectionData {\n /** Slug of the collection to display (e.g. \"clients\", \"invoices\"). */\n collectionSlug: string;\n /** Optional primary-key value of a single item to open on mount. */\n itemId?: string;\n}\n\nexport type PresentCollectionArgs = PresentCollectionData;\n\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description:\n \"Display a schema-driven collection inline in the chat as an interactive, editable card. Shows the collection's list of records. Pass `itemId` to open one specific record on mount.\",\n parameters: {\n type: \"object\",\n properties: {\n collectionSlug: {\n type: \"string\",\n description: \"The slug of the collection to display (e.g. 'clients', 'invoices', 'contacts').\",\n },\n itemId: {\n type: \"string\",\n description: \"Optional primary-key value of a single record to open in detail view on mount. Omit to show the full list.\",\n },\n },\n required: [\"collectionSlug\"],\n },\n prompt: `After making changes to schema-driven collections, use ${TOOL_NAME} to present either the collection or the item`,\n};\n\nexport const executePresentCollection = async (\n _context: ToolContext,\n args: PresentCollectionArgs,\n): Promise<ToolResult<PresentCollectionData, PresentCollectionData>> => {\n const collectionSlug = typeof args?.collectionSlug === \"string\" ? args.collectionSlug.trim() : \"\";\n if (!collectionSlug) {\n return {\n message: \"presentCollection error: collectionSlug is required\",\n instructions: \"Tell the user you couldn't display the collection because no collection was specified, and ask which collection they mean.\",\n };\n }\n const itemId = typeof args.itemId === \"string\" && args.itemId.trim().length > 0 ? args.itemId.trim() : undefined;\n const data: PresentCollectionData = itemId ? { collectionSlug, itemId } : { collectionSlug };\n const target = itemId ? `${collectionSlug} / ${itemId}` : collectionSlug;\n return {\n message: `Presented collection ${target}`,\n // `data` is the view's source (also the host's render-eligibility signal);\n // `jsonData` is what the LLM sees. Same payload, two audiences.\n data,\n jsonData: data,\n // Phrased to stay correct for BOTH storage kinds without loading the\n // schema (this executor is deliberately pure/isomorphic): a writable\n // collection offers edit/create/delete in the card; a read-only\n // `dataSource` collection hides those and changes flow through its\n // data file instead.\n instructions:\n \"The collection has been presented to the user as an interactive card. They can browse and open records directly; on a writable collection they can also edit, create, and delete (a read-only dataSource collection shows no edit controls — its records change by editing the backing data file). No further action is needed unless they ask.\",\n };\n};\n","// A standard, ordered colour palette for `enum` field values, shared by every\n// collection surface (list, calendar, kanban). Each value in an\n// enum's `values` array is assigned the palette entry at its index — cycling\n// when an enum declares more values than the palette holds — so colouring is\n// automatic and consistent without any per-value schema config.\n//\n// Tailwind only detects class names that appear as complete string literals,\n// so every surface's classes are spelled out per colour below rather than\n// built from a colour name at runtime.\n//\n// One enum is special: the field a schema's `notifyWhen` targets (the\n// \"notification enum\"). Its flagged values read the notification severity\n// colours — the first flagged value (most urgent) red, the rest amber — and\n// every other value reads neutral grey, mirroring the notification bell\n// (red = urgent, amber = nudge) rather than the rotating palette.\n// `resolveEnumColor` encapsulates that rule.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionSchema } from \"./schema\";\n\nexport interface EnumColorClasses {\n /** Stat-card style: border + fill + text + hover. */\n card: string;\n /** Small status dot (kanban column header). */\n dot: string;\n /** Pill / badge / inline `<select>` fill + text (no border width). */\n badge: string;\n /** Border colour, paired with a `border` width class by the caller. */\n border: string;\n}\n\n// The palette deliberately EXCLUDES the warm warning band (red / orange /\n// amber): those are reserved for the notification-enum severity colours\n// (`ENUM_ALERT` red, `ENUM_NUDGE` amber), so a normal enum value can never\n// draw a colour that reads like a notification. Eight well-separated cool /\n// green / magenta hues keep enough variety for wide enums.\nconst PALETTE: readonly EnumColorClasses[] = [\n {\n card: \"border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100\",\n dot: \"bg-indigo-500\",\n badge: \"bg-indigo-100 text-indigo-700\",\n border: \"border-indigo-200\",\n },\n { card: \"border-sky-200 bg-sky-50 text-sky-600 hover:bg-sky-100\", dot: \"bg-sky-500\", badge: \"bg-sky-100 text-sky-700\", border: \"border-sky-200\" },\n { card: \"border-cyan-200 bg-cyan-50 text-cyan-600 hover:bg-cyan-100\", dot: \"bg-cyan-500\", badge: \"bg-cyan-100 text-cyan-700\", border: \"border-cyan-200\" },\n { card: \"border-teal-200 bg-teal-50 text-teal-600 hover:bg-teal-100\", dot: \"bg-teal-500\", badge: \"bg-teal-100 text-teal-700\", border: \"border-teal-200\" },\n {\n card: \"border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100\",\n dot: \"bg-emerald-500\",\n badge: \"bg-emerald-100 text-emerald-700\",\n border: \"border-emerald-200\",\n },\n { card: \"border-lime-200 bg-lime-50 text-lime-600 hover:bg-lime-100\", dot: \"bg-lime-500\", badge: \"bg-lime-100 text-lime-700\", border: \"border-lime-200\" },\n {\n card: \"border-violet-200 bg-violet-50 text-violet-600 hover:bg-violet-100\",\n dot: \"bg-violet-500\",\n badge: \"bg-violet-100 text-violet-700\",\n border: \"border-violet-200\",\n },\n {\n card: \"border-fuchsia-200 bg-fuchsia-50 text-fuchsia-600 hover:bg-fuchsia-100\",\n dot: \"bg-fuchsia-500\",\n badge: \"bg-fuchsia-100 text-fuchsia-700\",\n border: \"border-fuchsia-200\",\n },\n];\n\n/** Neutral styling for the empty / Uncategorized bucket — never a palette\n * colour, so an unset or unknown value reads grey across every surface. */\nexport const ENUM_NEUTRAL: EnumColorClasses = {\n card: \"border-slate-200 bg-slate-50 text-slate-500 hover:bg-slate-100\",\n dot: \"bg-slate-300\",\n badge: \"bg-slate-100 text-slate-500\",\n border: \"border-slate-200\",\n};\n\n/** The urgent notification colour (red), matching the bell's `urgent`\n * severity. The first value a schema's `notifyWhen` flags reads this. */\nexport const ENUM_ALERT: EnumColorClasses = {\n card: \"border-red-200 bg-red-50 text-red-600 hover:bg-red-100\",\n dot: \"bg-red-500\",\n badge: \"bg-red-100 text-red-700\",\n border: \"border-red-200\",\n};\n\n/** The nudge notification colour (amber), matching the bell's `nudge`\n * severity. Flagged `notifyWhen` values after the first read this. */\nexport const ENUM_NUDGE: EnumColorClasses = {\n card: \"border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100\",\n dot: \"bg-amber-500\",\n badge: \"bg-amber-100 text-amber-700\",\n border: \"border-amber-200\",\n};\n\n/** Classes for the enum value at `index` in its field's `values` array. A\n * negative index (value unset or not among the declared values) reads\n * neutral. */\nexport function enumColorClasses(index: number): EnumColorClasses {\n if (index < 0) return ENUM_NEUTRAL;\n return PALETTE[index % PALETTE.length] ?? ENUM_NEUTRAL;\n}\n\n/** Index of `value` within an enum field's declared `values`, or -1 when the\n * value is empty / unknown (→ neutral). */\nexport function enumValueIndex(values: readonly string[] | undefined, value: unknown): number {\n // No text form (array/object) means no enum member — \"[object Object]\" must\n // not index into the palette.\n const text = fieldTextOrNull(value);\n if (text === null || text === \"\") return -1;\n return values?.indexOf(text) ?? -1;\n}\n\n/** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the\n * \"notification enum\"); undefined for every other field. */\nfunction notifyValuesFor(schema: CollectionSchema, fieldKey: string): readonly string[] | undefined {\n const spec = schema.notifyWhen;\n return spec && spec.field === fieldKey ? spec.in : undefined;\n}\n\n/** Resolve a value's colour for enum field `fieldKey`:\n * - Notification enum (`notifyWhen` targets it): the first flagged value (in\n * `notifyWhen.in` order, the most urgent) reads notification red, the rest\n * amber, and every non-flagged value neutral grey.\n * - Any other enum: the standard palette by the value's declared index. */\nexport function resolveEnumColor(schema: CollectionSchema, fieldKey: string, value: unknown): EnumColorClasses {\n const notifyValues = notifyValuesFor(schema, fieldKey);\n if (notifyValues) {\n const str = fieldText(value);\n const rank = notifyValues.indexOf(str);\n if (rank < 0) return ENUM_NEUTRAL;\n return rank === 0 ? ENUM_ALERT : ENUM_NUDGE;\n }\n const spec = schema.fields[fieldKey];\n return enumColorClasses(enumValueIndex(spec?.type === \"enum\" ? spec.values : undefined, value));\n}\n","// Pure edit-draft logic for the collection record form, extracted from\n// CollectionView.vue so the parent (build draft / save / validate) and\n// the extracted CollectionRecordPanel (per-row table mutators) share one\n// implementation. No Vue, no I/O — every function maps a draft + schema\n// to a value, so the omission/validation semantics are unit-testable.\n\nimport { fieldText } from \"./fieldText\";\nimport { fieldVisible } from \"./actionVisible\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType, CollectionItem, CollectionSchema } from \"./schema\";\nimport type { EditState, TableRowDraft } from \"./uiTypes\";\n\n/** Build a row draft, reading each sub-field's persisted value via\n * `readSub`. An empty row passes a reader that always returns\n * `undefined`; a row-from-item reads the source record. Boolean\n * presence uses `typeof raw === \"boolean\"` so an existing explicit\n * `false` is recorded as present and round-trips on a no-op save. */\nfunction buildTableRowDraft(subFields: Record<string, FieldSpec>, readSub: (subKey: string) => unknown): TableRowDraft {\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n const raw = readSub(subKey);\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = fieldText(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\n}\n\n/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, () => undefined);\n}\n\n/** Build a row draft from an existing persisted row. */\nexport function rowFromItem(item: Record<string, unknown>, subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, (subKey) => item[subKey]);\n}\n\n/** Own-property boolean read: a field named `toString` / `constructor` /\n * `__proto__` must read `false` here, not inherit a truthy Object.prototype\n * member and force an untouched boolean into every saved record. */\nfunction ownFlag(flags: Record<string, boolean>, key: string): boolean {\n return Object.hasOwn(flags, key) && flags[key] === true;\n}\n\n/** Decide whether a boolean field's draft value should be emitted (vs.\n * omitted so a downstream default applies). */\nfunction shouldEmitBoolean(state: EditState, key: string, field: FieldSpec): boolean {\n return ownFlag(state.boolOriginallyPresent, key) || ownFlag(state.boolTouched, key) || Boolean(field.required);\n}\n\n/** Convert a scalar draft slot to its persisted form. `undefined` = omit. */\nfunction scalarDraftToValue(raw: string | undefined, fieldType: FieldType): unknown {\n if (raw === undefined || raw === \"\") return undefined;\n if (fieldType === \"number\" || fieldType === \"money\") {\n const num = Number(raw);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\n/** Convert one table row draft to its persisted record. */\nfunction rowDraftToRecord(rowDraft: TableRowDraft, subFields: Record<string, FieldSpec>): Record<string, unknown> {\n const row: Record<string, unknown> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n if (subField.type === \"boolean\") {\n const value = ownFlag(rowDraft.bool, subKey);\n if (ownFlag(rowDraft.boolOriginallyPresent, subKey) || ownFlag(rowDraft.boolTouched, subKey) || value || subField.required) row[subKey] = value;\n continue;\n }\n const value = scalarDraftToValue(rowDraft.text[subKey], subField.type);\n if (value !== undefined) row[subKey] = value;\n }\n return row;\n}\n\n/** Convert a full edit draft to the record to persist. */\nexport function draftToRecord(state: EditState, schema: CollectionSchema): CollectionItem {\n const record: CollectionItem = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (COMPUTED_TYPES.has(field.type)) continue; // never persisted (toggle projects an enum field)\n if (field.type === \"boolean\") {\n if (shouldEmitBoolean(state, key, field)) record[key] = ownFlag(state.bool, key);\n continue;\n }\n if (field.type === \"table\" && field.of) {\n const subFields = field.of;\n record[key] = (state.table[key] ?? []).map((rowDraft) => rowDraftToRecord(rowDraft, subFields));\n continue;\n }\n const value = scalarDraftToValue(state.text[key], field.type);\n if (value !== undefined) record[key] = value;\n }\n return record;\n}\n\n/** Normalise a raw inline-edit input (table-cell checkbox/select) to its\n * persisted form. Mirrors `draftToRecord`'s boolean strictness; an empty\n * enum selection (the placeholder option) clears the field via `undefined`. */\nexport function coerceInlineValue(field: FieldSpec, raw: boolean | string): unknown {\n if (field.type === \"boolean\") return raw === true;\n return raw === \"\" ? undefined : raw;\n}\n\n/** Build the full record to PUT for a single-cell inline edit, without\n * mutating `item`. A `undefined` value omits the key (enum cleared),\n * matching `draftToRecord`'s omission semantics. */\nexport function buildUpdatedRecord(item: CollectionItem, key: string, value: unknown): CollectionItem {\n if (value === undefined) {\n const { [key]: __omit, ...rest } = item;\n return rest;\n }\n return { ...item, [key]: value };\n}\n\n/** Empty for required-field validation — NOT a truthiness check (a\n * numeric `0` is a filled value). */\nfunction isMissingDraftValue(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/** Label of the first required table sub-field empty in any row, else null. */\nfunction firstMissingTableSubField(field: FieldSpec, rows: TableRowDraft[] | undefined): string | null {\n if (field.type !== \"table\" || !rows) return null;\n for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {\n const row = rows[rowIdx];\n for (const [subKey, subField] of Object.entries(field.of)) {\n if (!subField.required || subField.type === \"boolean\") continue;\n if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;\n }\n }\n return null;\n}\n\n/** Field types the required check never flags: booleans always hold a\n * value, and the computed/projected kinds (COMPUTED_TYPES — the shared\n * set, so a future computed type can't drift past this check) have no\n * draft input at all. */\nconst NEVER_MISSING_TYPES: ReadonlySet<FieldType> = new Set<FieldType>([\"boolean\", ...COMPUTED_TYPES]);\n\nfunction validateOneField(key: string, field: FieldSpec, draft: EditState, record: CollectionItem): string | null {\n // A `when`-hidden field has no input the user can fill — never missing.\n if (!fieldVisible(field, record)) return null;\n if (field.type === \"table\" && field.of) {\n const rows = draft.table[key];\n if (field.required && (!rows || rows.length === 0)) return field.label;\n return firstMissingTableSubField(field, rows);\n }\n if (!field.required) return null;\n if (draft.mode === \"create\" && field.primary === true) return null; // server auto-generates id\n if (NEVER_MISSING_TYPES.has(field.type)) return null;\n return isMissingDraftValue(draft.text[key]) ? field.label : null;\n}\n\n/** Human-readable label of the first missing required field, or null. */\nexport function firstMissingRequiredField(draft: EditState, schema: CollectionSchema): string | null {\n const record = draftToRecord(draft, schema);\n for (const [key, field] of Object.entries(schema.fields)) {\n const missing = validateOneField(key, field, draft, record);\n if (missing) return missing;\n }\n return null;\n}\n","// The slash-command chat seed a collection view sends to the agent when the\n// user starts a chat from a skill-backed collection or one of its records.\n// Skill-backed collections have a `/<slug>` command, so seed that. (Feeds are\n// skill-less and seeded differently; that branch stays in the view because it\n// is i18n-driven.)\n\n/** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific\n * record is addressed. The `id=` selector goes BEFORE the free-text message so\n * the skill's argument parser reads it as a flag rather than as prose. */\nexport function skillCommandSeed(slug: string, message: string, itemId?: string): string {\n return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;\n}\n","// Sorting for the collection list table. The header sort toggle (next to\n// each field title) cycles a single active column through none → asc →\n// desc → none; this module turns that state plus a per-field value\n// extractor into an ordered copy of the rows.\n//\n// Two invariants the comparator guarantees:\n// 1. Rows with an empty/missing value for the sorted field always sink\n// to the bottom, regardless of direction.\n// 2. Ties (and the empty group) keep their original order — the sort is\n// stable via the captured source index.\n//\n// Field-type → comparable mapping (see `isSortableField`):\n// string/text/email → string · number/money → numeric ·\n// date/datetime → epoch-ms · enum → declared-index · boolean/toggle/flag →\n// false<true · ref → display label · derived → its display type.\n// markdown/table/image/file/embed get no sort button.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem, CollectionFieldSpec, CollectionFieldType } from \"./schema\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface SortState {\n /** Field key of the single active sort column. */\n field: string;\n direction: SortDirection;\n}\n\n/** A row's comparable value for the active field. Exactly one of `num` /\n * `str` is set when not empty; `empty` rows always sort last. */\nexport interface SortValue {\n empty: boolean;\n num?: number;\n str?: string;\n}\n\nconst EMPTY: SortValue = { empty: true };\n\n/** Field types that render no value text in the table, so offer no sort. */\nconst NON_SORTABLE: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"markdown\", \"table\", \"image\", \"file\", \"embed\", \"backlinks\", \"rollup\"]);\n\nexport function isSortableField(field: CollectionFieldSpec): boolean {\n return !NON_SORTABLE.has(field.type);\n}\n\n/** Cycle one column's state: none → asc → desc → none. */\nexport function nextSortDirection(current: SortDirection | null): SortDirection | null {\n if (current === null) return \"asc\";\n if (current === \"asc\") return \"desc\";\n return null;\n}\n\n// ── SortValue constructors (one per comparable kind) ────────────────\n\nexport function numericSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) ? { empty: false, num } : EMPTY;\n}\n\nexport function stringSortValue(raw: unknown): SortValue {\n // An array/object has no sort key — treat it as empty (sorts last) instead of\n // grouping every such record together under \"[object Object]\".\n const str = fieldTextOrNull(raw);\n if (str === null) return EMPTY;\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const epoch = Date.parse(text);\n // Unparseable dates fall back to a lexical compare rather than vanishing.\n return Number.isNaN(epoch) ? stringSortValue(raw) : { empty: false, num: epoch };\n}\n\n/** Enum sorts by the value's index in the declared `values` list. A value\n * outside the list (or unset) is treated as empty → last. */\nexport function enumSortValue(values: readonly string[] | undefined, raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const idx = values ? values.indexOf(text) : -1;\n return idx < 0 ? EMPTY : { empty: false, num: idx };\n}\n\n/** Boolean / toggle: false < true, never empty (unset reads as false). */\nexport function boolSortValue(checked: boolean): SortValue {\n return { empty: false, num: checked ? 1 : 0 };\n}\n\n// ── Comparator + driver ─────────────────────────────────────────────\n\nexport function compareSortValues(left: SortValue, right: SortValue): number {\n if (left.num !== undefined && right.num !== undefined) return left.num - right.num;\n const leftStr = left.str ?? String(left.num ?? \"\");\n const rightStr = right.str ?? String(right.num ?? \"\");\n return leftStr.localeCompare(rightStr);\n}\n\n/** Stable sort of `items` by `valueOf`. Empties always last; ties hold\n * source order. Returns a new array (does not mutate `items`). */\nexport function sortItems(items: readonly CollectionItem[], direction: SortDirection, valueOf: (item: CollectionItem) => SortValue): CollectionItem[] {\n const dir = direction === \"asc\" ? 1 : -1;\n return items\n .map((item, index) => ({ item, index, sv: valueOf(item) }))\n .sort((left, right) => {\n if (left.sv.empty || right.sv.empty) {\n if (left.sv.empty && right.sv.empty) return left.index - right.index;\n return left.sv.empty ? 1 : -1;\n }\n const base = compareSortValues(left.sv, right.sv);\n return base !== 0 ? base * dir : left.index - right.index;\n })\n .map((decorated) => decorated.item);\n}\n","// Field-type → comparable value for the collection list-table sort. Splits\n// the header sort's per-row value extraction out of the Vue view so the\n// dispatch (which field type maps to which SortValue constructor) is testable\n// on its own. The comparators live in `./sortItems`; this module only routes.\n//\n// A few field kinds can't be sorted from the raw cell alone — `toggle` and\n// `flag` are projected booleans, `derived` is a formula, and `ref` sorts by\n// its resolved display label. Those readers live in the view's rendering\n// composable, so they are passed in (dependency injection) rather than\n// imported, keeping this module pure and framework-free.\n\nimport { numericSortValue, stringSortValue, dateSortValue, enumSortValue, boolSortValue, type SortValue } from \"./sortItems\";\nimport type { CollectionItem, CollectionFieldSpec as FieldSpec } from \"./schema\";\n\n/** Resolve a `ref` cell to its human display label (target slug + item id). */\nexport type RefDisplayResolver = (targetSlug: string, itemSlug: string) => string;\n\n/** The row readers the view supplies for the field kinds that can't sort off\n * the raw cell value. All come from the rendering composable. */\nexport interface SortValueDeps {\n toggleChecked: (item: CollectionItem, field: FieldSpec) => boolean;\n flagValueOf: (key: string, item: CollectionItem) => boolean;\n evaluateDerived: (field: FieldSpec, key: string, item: CollectionItem) => number | null;\n deriveRecord: (item: CollectionItem) => CollectionItem;\n resolveRefDisplay: RefDisplayResolver;\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nexport function scalarSortValue(field: FieldSpec, raw: unknown, resolveRefDisplay: RefDisplayResolver): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(resolveRefDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date → epoch, anything else → the enriched value as a string. */\nexport function derivedSortValue(\n field: FieldSpec,\n key: string,\n item: CollectionItem,\n deps: Pick<SortValueDeps, \"evaluateDerived\" | \"deriveRecord\">,\n): SortValue {\n const display = field.type === \"derived\" ? field.display : undefined;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(deps.evaluateDerived(field, key, item));\n }\n const enriched = deps.deriveRecord(item);\n if (display === \"date\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\n/** Comparable value for one row under the active field. Toggle, flag, and\n * derived need the whole record; every other type keys off the raw cell. */\nexport function sortValueOf(field: FieldSpec, key: string, item: CollectionItem, deps: SortValueDeps): SortValue {\n if (field.type === \"toggle\") return boolSortValue(deps.toggleChecked(item, field));\n if (field.type === \"flag\") return boolSortValue(deps.flagValueOf(key, item));\n if (field.type === \"derived\") return derivedSortValue(field, key, item, deps);\n return scalarSortValue(field, item[key], deps.resolveRefDisplay);\n}\n","// Free-text record matching for the collection list search box. Pure and\n// framework-free so the view's `filteredItems` computed just calls it.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object/array-valued fields (table rows, nested records) have no text\n * form (`fieldTextOrNull` → `null`) and are skipped — they don't render as\n * searchable text in the list table.\n *\n * `query` is matched against each cell's lower-cased text but is NOT\n * lower-cased here: callers pass an already-normalised (trimmed +\n * lower-cased) query, so an upper-case `query` intentionally matches\n * nothing. Keeping the normalisation at the call site avoids re-lowering\n * the same query once per row. */\nexport function itemMatchesQuery(item: CollectionItem, query: string): boolean {\n // An empty query matches every record — otherwise a row whose only fields are\n // object/array-valued (no scalar cell reaches `includes(\"\")`) would vanish\n // when the search box is cleared. The list caller already short-circuits the\n // empty case, so this only guards a future caller that doesn't.\n if (query === \"\") return true;\n return Object.values(item).some((value) => {\n const text = fieldTextOrNull(value);\n return text !== null && text.toLowerCase().includes(query);\n });\n}\n","// Composite cell keys for the list table's per-cell state maps, plus the\n// load-time \"which enum cells were empty\" snapshot the inline enum dropdown\n// reads. Pure and framework-free so the view keeps only the reactive refs.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** The schema slice `snapshotEmptyEnums` reads — minimal structural shape so\n * both the client and server collection-schema types satisfy it as-is. */\nexport interface EnumSnapshotSchema {\n fields: Record<string, { type: string }>;\n primaryKey: string;\n}\n\n/** Stable key for one cell (`<rowId>:<fieldKey>`) in the per-cell state maps. */\nexport function cellKey(rowId: string, fieldKey: string): string {\n return `${rowId}:${fieldKey}`;\n}\n\n/** A row's stable identity: the primary-key value as text (`\"\"` when the schema\n * has no primary key, or the value is absent). Every per-row / per-cell state\n * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses\n * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the\n * empty-enum snapshot always agree. A real primary key is a scalar, so this\n * matches its plain string form. */\nexport function rowIdOf(primaryKey: string | undefined, item: CollectionItem): string {\n return primaryKey ? fieldText(item[primaryKey]) : \"\";\n}\n\n/** The set of enum cells that were empty in the freshly-fetched records — the\n * only cells whose inline dropdown offers an empty option. */\nexport function snapshotEmptyEnums(schema: EnumSnapshotSchema, records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = fieldText(record[schema.primaryKey]);\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n","// Shared chip/label resolution for the collection calendar surfaces (month\n// grid + day view), so both label a record identically. Pure, type-only\n// dependency on the schema shape.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem, CollectionSchema } from \"./schema\";\n\n// Text-like field types that make a sensible human-readable label.\nconst LABEL_FIELD_TYPES = new Set([\"string\", \"text\", \"markdown\", \"email\"]);\n\n/** Which field labels a record: the schema's explicit `displayField`, else the\n * first non-primary text-like field (so a collection without a displayField\n * shows e.g. the title rather than the opaque primaryKey), else null → the\n * caller falls back to the primary-key value. */\nexport function labelFieldFor(schema: CollectionSchema): string | null {\n if (schema.displayField) return schema.displayField;\n for (const [key, spec] of Object.entries(schema.fields)) {\n if (key !== schema.primaryKey && LABEL_FIELD_TYPES.has(spec.type)) return key;\n }\n return null;\n}\n\n/** A record's primary-key value as a string. */\nexport function itemIdOf(item: CollectionItem, schema: CollectionSchema): string {\n return fieldText(item[schema.primaryKey]);\n}\n\n/** A record's display label: the resolved `labelField` value, else the\n * primary key. Pass the result of `labelFieldFor(schema)` as `labelField`\n * (compute it once per render rather than per item). */\nexport function itemLabelOf(item: CollectionItem, schema: CollectionSchema, labelField: string | null): string {\n if (labelField) {\n const value = item[labelField];\n // Accept any primitive (string / number / boolean) so a numeric or boolean\n // display field still labels the record; objects/arrays fall through to the\n // id rather than rendering as \"[object Object]\".\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n const text = String(value);\n if (text.length > 0) return text;\n }\n }\n return itemIdOf(item, schema);\n}\n","/**\n * 8-char hex id — short, slug-safe, and editable. Produces the same id *shape*\n * as the server's `generateItemId()` (8 hex chars) so a UI-created collection\n * record looks like one the server would have generated for a form submitted\n * with a blank primary key. The source of randomness differs (UUID-derived here\n * vs `randomBytes` on the server); only the shape is intentionally shared.\n */\nexport function shortHexId(): string {\n return crypto.randomUUID().replace(/-/g, \"\").slice(0, 8);\n}\n","// Pure builder for the workspace ontology graph — the `/collections` Map\n// panel (phase 2 of plans/done/collection-ontology.md step ①; plan:\n// plans/done/feat-collection-ontology-graph.md). Consumes the per-collection\n// ontology entries `buildWorkspaceOntology` derives server-side and turns\n// them into a node/edge structure a graph view can render directly.\n// Browser-safe (no `node:*` imports) so the Vue plugin builds the same\n// graph from the raw entries — same parity discipline as `deriveAll` and\n// the sibling `wiki/graph.ts`.\n\n/** One relationship a schema declares: a `ref` (the record stores the\n * target's primaryKey slug) or `embed` (display-only pull) pointing at\n * collection `to`, or `backlinks` / `rollup` (display-only REVERSE\n * refs — `to` is the relation's source collection, i.e. its `from`,\n * and `via` names the exact ref field there being reversed).\n * A `ref` column inside a `table` field is reported with a dotted path\n * (`lines.clientId`). Whether `to` exists is NOT checked — resolution\n * is fail-soft at render. */\nexport interface OntologyRelation {\n field: string;\n kind: \"ref\" | \"embed\" | \"backlinks\" | \"rollup\";\n to: string;\n via?: string;\n}\n\nexport interface CollectionOntologyEntry {\n slug: string;\n title: string;\n icon: string;\n primaryKey: string;\n /** The effective display field: the schema's `displayField`, falling\n * back to the primaryKey exactly as render-time labelling does. */\n displayField: string;\n /** Records in the collection, or `null` when the backend couldn't be\n * reached to count them (an engine that failed to load, a closed\n * session). Deliberately not 0: an unreachable collection reported as\n * empty invites \"restoring\" data that is intact but out of reach. */\n recordCount: number | null;\n relations: OntologyRelation[];\n}\n\nexport interface OntologyGraphNode {\n slug: string;\n title: string;\n icon: string;\n recordCount: number | null;\n /** No discovered collection has this slug — the node exists only\n * because a relation points at it. Rendered as a ghost so the graph\n * doubles as a broken-ref lint surface (lint, not lock). */\n missing?: boolean;\n}\n\nexport interface OntologyGraphEdge {\n /** Tail: the collection whose records store (`ref`) or pull (`embed`)\n * the link — or, for an uncollapsed reverse relation, the collection\n * whose refs the `backlinks`/`rollup` field aggregates. */\n from: string;\n /** Head: the collection the link points at. */\n to: string;\n /** Field declaring the relation — on `from` for `ref`/`embed`, on\n * `to` for an uncollapsed `backlinks`/`rollup`. */\n field: string;\n kind: OntologyRelation[\"kind\"];\n /** `backlinks`/`rollup` field names on `to` that declare the reverse\n * of this `ref` edge, collapsed here so one link never draws twice. */\n reverseFields?: string[];\n}\n\nexport interface OntologyGraph {\n nodes: OntologyGraphNode[];\n edges: OntologyGraphEdge[];\n}\n\nconst isForwardKind = (kind: OntologyRelation[\"kind\"]): boolean => kind === \"ref\" || kind === \"embed\";\n\n/** Fold a reverse relation (`backlinks`/`rollup` declared on `owner`)\n * into the edge list: collapse onto the forward `ref` edge whose field\n * is the relation's declared `via` (several refs can link the same\n * pair — matches.homeTeam vs matches.awayTeam — so the pair alone is\n * ambiguous), else emit its own edge oriented in true data direction\n * (refs' source → owner). A `via` naming no forward edge stays\n * uncollapsed — fail-soft, like every other dangling reference. */\nconst addReverseRelation = (edges: OntologyGraphEdge[], owner: string, rel: OntologyRelation): void => {\n const forward = edges.find((edge) => edge.kind === \"ref\" && edge.from === rel.to && edge.to === owner && (rel.via === undefined || edge.field === rel.via));\n if (forward) {\n forward.reverseFields = [...(forward.reverseFields ?? []), rel.field];\n return;\n }\n edges.push({ from: rel.to, to: owner, field: rel.field, kind: rel.kind });\n};\n\n/** Two passes so every forward `ref` edge exists before any reverse\n * relation tries to collapse onto one — entry order must not matter. */\nconst collectEdges = (entries: CollectionOntologyEntry[]): OntologyGraphEdge[] => {\n const edges: OntologyGraphEdge[] = [];\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (isForwardKind(rel.kind)) edges.push({ from: entry.slug, to: rel.to, field: rel.field, kind: rel.kind });\n }\n }\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (!isForwardKind(rel.kind)) addReverseRelation(edges, entry.slug, rel);\n }\n }\n return edges;\n};\n\n/** Slugs referenced by an edge endpoint but backed by no discovered\n * collection, slug-sorted for deterministic node order. */\nconst ghostSlugs = (edges: OntologyGraphEdge[], known: ReadonlySet<string>): string[] => {\n const ghosts = new Set<string>();\n for (const edge of edges) {\n if (!known.has(edge.from)) ghosts.add(edge.from);\n if (!known.has(edge.to)) ghosts.add(edge.to);\n }\n return [...ghosts].sort();\n};\n\n/** Build the renderable graph: one node per discovered collection (in\n * the entries' slug-sorted order) plus ghost nodes for dangling\n * targets, one edge per underlying link. */\nexport const buildOntologyGraph = (entries: CollectionOntologyEntry[]): OntologyGraph => {\n const edges = collectEdges(entries);\n const known = new Set(entries.map((entry) => entry.slug));\n const nodes: OntologyGraphNode[] = entries.map(({ slug, title, icon, recordCount }) => ({ slug, title, icon, recordCount }));\n for (const slug of ghostSlugs(edges, known)) {\n // A ghost node has no collection behind it at all, so it genuinely holds\n // no records — 0, not the \"couldn't count\" null.\n nodes.push({ slug, title: slug, icon: \"\", recordCount: 0, missing: true });\n }\n return { nodes, edges };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/collection/core/presentCollection.ts","../../src/collection/core/enumColors.ts","../../src/collection/core/draft.ts","../../src/collection/core/chatSeed.ts","../../src/collection/core/sortItems.ts","../../src/collection/core/sortValueOf.ts","../../src/collection/core/textSearch.ts","../../src/collection/core/recordKeys.ts","../../src/collection/core/itemLabel.ts","../../src/collection/core/shortHexId.ts","../../src/collection/core/ontologyGraph.ts"],"sourcesContent":["// presentCollection tool — definition + pure server-side executor.\n//\n// Isomorphic (no Vue, no Node): bundled to the browser as the plugin's\n// `execute`, and run server-side via the host's plugin dispatch route. The\n// executor only validates + echoes the addressing; the live schema + items\n// are fetched client-side by the View through the host's /api/collections\n// routes, so a bad slug surfaces as the View's \"not found\" state.\n\nimport type { ToolContext, ToolDefinition, ToolResult } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentCollection\";\n\n/** Render payload carried in the tool result's `data` field; the View mounts\n * off these. Same shape as the tool args. */\nexport interface PresentCollectionData {\n /** Slug of the collection to display (e.g. \"clients\", \"invoices\"). */\n collectionSlug: string;\n /** Optional primary-key value of a single item to open on mount. */\n itemId?: string;\n}\n\nexport type PresentCollectionArgs = PresentCollectionData;\n\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description:\n \"Display a schema-driven collection inline in the chat as an interactive, editable card. Shows the collection's list of records. Pass `itemId` to open one specific record on mount.\",\n parameters: {\n type: \"object\",\n properties: {\n collectionSlug: {\n type: \"string\",\n description: \"The slug of the collection to display (e.g. 'clients', 'invoices', 'contacts').\",\n },\n itemId: {\n type: \"string\",\n description: \"Optional primary-key value of a single record to open in detail view on mount. Omit to show the full list.\",\n },\n },\n required: [\"collectionSlug\"],\n },\n prompt: `After making changes to schema-driven collections, use ${TOOL_NAME} to present either the collection or the item`,\n};\n\nexport const executePresentCollection = async (\n _context: ToolContext,\n args: PresentCollectionArgs,\n): Promise<ToolResult<PresentCollectionData, PresentCollectionData>> => {\n const collectionSlug = typeof args?.collectionSlug === \"string\" ? args.collectionSlug.trim() : \"\";\n if (!collectionSlug) {\n return {\n message: \"presentCollection error: collectionSlug is required\",\n instructions: \"Tell the user you couldn't display the collection because no collection was specified, and ask which collection they mean.\",\n };\n }\n const itemId = typeof args.itemId === \"string\" && args.itemId.trim().length > 0 ? args.itemId.trim() : undefined;\n const data: PresentCollectionData = itemId ? { collectionSlug, itemId } : { collectionSlug };\n const target = itemId ? `${collectionSlug} / ${itemId}` : collectionSlug;\n return {\n message: `Presented collection ${target}`,\n // `data` is the view's source (also the host's render-eligibility signal);\n // `jsonData` is what the LLM sees. Same payload, two audiences.\n data,\n jsonData: data,\n // Phrased to stay correct for BOTH storage kinds without loading the\n // schema (this executor is deliberately pure/isomorphic): a writable\n // collection offers edit/create/delete in the card; a read-only\n // `dataSource` collection hides those and changes flow through its\n // data file instead.\n instructions:\n \"The collection has been presented to the user as an interactive card. They can browse and open records directly; on a writable collection they can also edit, create, and delete (a read-only dataSource collection shows no edit controls — its records change by editing the backing data file). No further action is needed unless they ask.\",\n };\n};\n","// A standard, ordered colour palette for `enum` field values, shared by every\n// collection surface (list, calendar, kanban). Each value in an\n// enum's `values` array is assigned the palette entry at its index — cycling\n// when an enum declares more values than the palette holds — so colouring is\n// automatic and consistent without any per-value schema config.\n//\n// Tailwind only detects class names that appear as complete string literals,\n// so every surface's classes are spelled out per colour below rather than\n// built from a colour name at runtime.\n//\n// One enum is special: the field a schema's `notifyWhen` targets (the\n// \"notification enum\"). Its flagged values read the notification severity\n// colours — the first flagged value (most urgent) red, the rest amber — and\n// every other value reads neutral grey, mirroring the notification bell\n// (red = urgent, amber = nudge) rather than the rotating palette.\n// `resolveEnumColor` encapsulates that rule.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionSchema } from \"./schema\";\n\nexport interface EnumColorClasses {\n /** Stat-card style: border + fill + text + hover. */\n card: string;\n /** Small status dot (kanban column header). */\n dot: string;\n /** Pill / badge / inline `<select>` fill + text (no border width). */\n badge: string;\n /** Border colour, paired with a `border` width class by the caller. */\n border: string;\n}\n\n// The palette deliberately EXCLUDES the warm warning band (red / orange /\n// amber): those are reserved for the notification-enum severity colours\n// (`ENUM_ALERT` red, `ENUM_NUDGE` amber), so a normal enum value can never\n// draw a colour that reads like a notification. Eight well-separated cool /\n// green / magenta hues keep enough variety for wide enums.\nconst PALETTE: readonly EnumColorClasses[] = [\n {\n card: \"border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100\",\n dot: \"bg-indigo-500\",\n badge: \"bg-indigo-100 text-indigo-700\",\n border: \"border-indigo-200\",\n },\n { card: \"border-sky-200 bg-sky-50 text-sky-600 hover:bg-sky-100\", dot: \"bg-sky-500\", badge: \"bg-sky-100 text-sky-700\", border: \"border-sky-200\" },\n { card: \"border-cyan-200 bg-cyan-50 text-cyan-600 hover:bg-cyan-100\", dot: \"bg-cyan-500\", badge: \"bg-cyan-100 text-cyan-700\", border: \"border-cyan-200\" },\n { card: \"border-teal-200 bg-teal-50 text-teal-600 hover:bg-teal-100\", dot: \"bg-teal-500\", badge: \"bg-teal-100 text-teal-700\", border: \"border-teal-200\" },\n {\n card: \"border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100\",\n dot: \"bg-emerald-500\",\n badge: \"bg-emerald-100 text-emerald-700\",\n border: \"border-emerald-200\",\n },\n { card: \"border-lime-200 bg-lime-50 text-lime-600 hover:bg-lime-100\", dot: \"bg-lime-500\", badge: \"bg-lime-100 text-lime-700\", border: \"border-lime-200\" },\n {\n card: \"border-violet-200 bg-violet-50 text-violet-600 hover:bg-violet-100\",\n dot: \"bg-violet-500\",\n badge: \"bg-violet-100 text-violet-700\",\n border: \"border-violet-200\",\n },\n {\n card: \"border-fuchsia-200 bg-fuchsia-50 text-fuchsia-600 hover:bg-fuchsia-100\",\n dot: \"bg-fuchsia-500\",\n badge: \"bg-fuchsia-100 text-fuchsia-700\",\n border: \"border-fuchsia-200\",\n },\n];\n\n/** Neutral styling for the empty / Uncategorized bucket — never a palette\n * colour, so an unset or unknown value reads grey across every surface. */\nexport const ENUM_NEUTRAL: EnumColorClasses = {\n card: \"border-slate-200 bg-slate-50 text-slate-500 hover:bg-slate-100\",\n dot: \"bg-slate-300\",\n badge: \"bg-slate-100 text-slate-500\",\n border: \"border-slate-200\",\n};\n\n/** The urgent notification colour (red), matching the bell's `urgent`\n * severity. The first value a schema's `notifyWhen` flags reads this. */\nexport const ENUM_ALERT: EnumColorClasses = {\n card: \"border-red-200 bg-red-50 text-red-600 hover:bg-red-100\",\n dot: \"bg-red-500\",\n badge: \"bg-red-100 text-red-700\",\n border: \"border-red-200\",\n};\n\n/** The nudge notification colour (amber), matching the bell's `nudge`\n * severity. Flagged `notifyWhen` values after the first read this. */\nexport const ENUM_NUDGE: EnumColorClasses = {\n card: \"border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100\",\n dot: \"bg-amber-500\",\n badge: \"bg-amber-100 text-amber-700\",\n border: \"border-amber-200\",\n};\n\n/** Classes for the enum value at `index` in its field's `values` array. A\n * negative index (value unset or not among the declared values) reads\n * neutral. */\nexport function enumColorClasses(index: number): EnumColorClasses {\n if (index < 0) return ENUM_NEUTRAL;\n return PALETTE[index % PALETTE.length] ?? ENUM_NEUTRAL;\n}\n\n/** Index of `value` within an enum field's declared `values`, or -1 when the\n * value is empty / unknown (→ neutral). */\nexport function enumValueIndex(values: readonly string[] | undefined, value: unknown): number {\n // No text form (array/object) means no enum member — \"[object Object]\" must\n // not index into the palette.\n const text = fieldTextOrNull(value);\n if (text === null || text === \"\") return -1;\n return values?.indexOf(text) ?? -1;\n}\n\n/** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the\n * \"notification enum\"); undefined for every other field. */\nfunction notifyValuesFor(schema: CollectionSchema, fieldKey: string): readonly string[] | undefined {\n const spec = schema.notifyWhen;\n return spec && spec.field === fieldKey ? spec.in : undefined;\n}\n\n/** Resolve a value's colour for enum field `fieldKey`:\n * - Notification enum (`notifyWhen` targets it): the first flagged value (in\n * `notifyWhen.in` order, the most urgent) reads notification red, the rest\n * amber, and every non-flagged value neutral grey.\n * - Any other enum: the standard palette by the value's declared index. */\nexport function resolveEnumColor(schema: CollectionSchema, fieldKey: string, value: unknown): EnumColorClasses {\n const notifyValues = notifyValuesFor(schema, fieldKey);\n if (notifyValues) {\n const str = fieldText(value);\n const rank = notifyValues.indexOf(str);\n if (rank < 0) return ENUM_NEUTRAL;\n return rank === 0 ? ENUM_ALERT : ENUM_NUDGE;\n }\n const spec = schema.fields[fieldKey];\n return enumColorClasses(enumValueIndex(spec?.type === \"enum\" ? spec.values : undefined, value));\n}\n","// Pure edit-draft logic for the collection record form, extracted from\n// CollectionView.vue so the parent (build draft / save / validate) and\n// the extracted CollectionRecordPanel (per-row table mutators) share one\n// implementation. No Vue, no I/O — every function maps a draft + schema\n// to a value, so the omission/validation semantics are unit-testable.\n\nimport { fieldText } from \"./fieldText\";\nimport { fieldVisible } from \"./actionVisible\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType, CollectionItem, CollectionSchema } from \"./schema\";\nimport type { EditState, TableRowDraft } from \"./uiTypes\";\n\n/** Build a row draft, reading each sub-field's persisted value via\n * `readSub`. An empty row passes a reader that always returns\n * `undefined`; a row-from-item reads the source record. Boolean\n * presence uses `typeof raw === \"boolean\"` so an existing explicit\n * `false` is recorded as present and round-trips on a no-op save. */\nfunction buildTableRowDraft(subFields: Record<string, FieldSpec>, readSub: (subKey: string) => unknown): TableRowDraft {\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n const raw = readSub(subKey);\n if (subField.type === \"boolean\") {\n bool[subKey] = raw === true;\n boolOriginallyPresent[subKey] = typeof raw === \"boolean\";\n boolTouched[subKey] = false;\n } else {\n text[subKey] = fieldText(raw);\n }\n }\n return { text, bool, boolOriginallyPresent, boolTouched };\n}\n\n/** A fresh, empty row draft for a `table` field's sub-schema. */\nexport function emptyRow(subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, () => undefined);\n}\n\n/** Build a row draft from an existing persisted row. */\nexport function rowFromItem(item: Record<string, unknown>, subFields: Record<string, FieldSpec>): TableRowDraft {\n return buildTableRowDraft(subFields, (subKey) => item[subKey]);\n}\n\n/** Own-property boolean read: a field named `toString` / `constructor` /\n * `__proto__` must read `false` here, not inherit a truthy Object.prototype\n * member and force an untouched boolean into every saved record. */\nfunction ownFlag(flags: Record<string, boolean>, key: string): boolean {\n return Object.hasOwn(flags, key) && flags[key] === true;\n}\n\n/** Decide whether a boolean field's draft value should be emitted (vs.\n * omitted so a downstream default applies). */\nfunction shouldEmitBoolean(state: EditState, key: string, field: FieldSpec): boolean {\n return ownFlag(state.boolOriginallyPresent, key) || ownFlag(state.boolTouched, key) || Boolean(field.required);\n}\n\n/** Convert a scalar draft slot to its persisted form. `undefined` = omit. */\nfunction scalarDraftToValue(raw: string | undefined, fieldType: FieldType): unknown {\n if (raw === undefined || raw === \"\") return undefined;\n if (fieldType === \"number\" || fieldType === \"money\") {\n const num = Number(raw);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\n/** Convert one table row draft to its persisted record. */\nfunction rowDraftToRecord(rowDraft: TableRowDraft, subFields: Record<string, FieldSpec>): Record<string, unknown> {\n const row: Record<string, unknown> = {};\n for (const [subKey, subField] of Object.entries(subFields)) {\n if (subField.type === \"boolean\") {\n const value = ownFlag(rowDraft.bool, subKey);\n if (ownFlag(rowDraft.boolOriginallyPresent, subKey) || ownFlag(rowDraft.boolTouched, subKey) || value || subField.required) row[subKey] = value;\n continue;\n }\n const value = scalarDraftToValue(rowDraft.text[subKey], subField.type);\n if (value !== undefined) row[subKey] = value;\n }\n return row;\n}\n\n/** Convert a full edit draft to the record to persist. */\nexport function draftToRecord(state: EditState, schema: CollectionSchema): CollectionItem {\n const record: CollectionItem = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (COMPUTED_TYPES.has(field.type)) continue; // never persisted (toggle projects an enum field)\n if (field.type === \"boolean\") {\n if (shouldEmitBoolean(state, key, field)) record[key] = ownFlag(state.bool, key);\n continue;\n }\n if (field.type === \"table\" && field.of) {\n const subFields = field.of;\n record[key] = (state.table[key] ?? []).map((rowDraft) => rowDraftToRecord(rowDraft, subFields));\n continue;\n }\n const value = scalarDraftToValue(state.text[key], field.type);\n if (value !== undefined) record[key] = value;\n }\n return record;\n}\n\n/** Normalise a raw inline-edit input (table-cell checkbox/select) to its\n * persisted form. Mirrors `draftToRecord`'s boolean strictness; an empty\n * enum selection (the placeholder option) clears the field via `undefined`. */\nexport function coerceInlineValue(field: FieldSpec, raw: boolean | string): unknown {\n if (field.type === \"boolean\") return raw === true;\n return raw === \"\" ? undefined : raw;\n}\n\n/** Build the full record to PUT for a single-cell inline edit, without\n * mutating `item`. A `undefined` value omits the key (enum cleared),\n * matching `draftToRecord`'s omission semantics. */\nexport function buildUpdatedRecord(item: CollectionItem, key: string, value: unknown): CollectionItem {\n if (value === undefined) {\n const { [key]: __omit, ...rest } = item;\n return rest;\n }\n return { ...item, [key]: value };\n}\n\n/** Empty for required-field validation — NOT a truthiness check (a\n * numeric `0` is a filled value). */\nfunction isMissingDraftValue(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/** Label of the first required table sub-field empty in any row, else null. */\nfunction firstMissingTableSubField(field: FieldSpec, rows: TableRowDraft[] | undefined): string | null {\n if (field.type !== \"table\" || !rows) return null;\n for (const [rowIdx, row] of rows.entries()) {\n for (const [subKey, subField] of Object.entries(field.of)) {\n if (!subField.required || subField.type === \"boolean\") continue;\n if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;\n }\n }\n return null;\n}\n\n/** Field types the required check never flags: booleans always hold a\n * value, and the computed/projected kinds (COMPUTED_TYPES — the shared\n * set, so a future computed type can't drift past this check) have no\n * draft input at all. */\nconst NEVER_MISSING_TYPES: ReadonlySet<FieldType> = new Set<FieldType>([\"boolean\", ...COMPUTED_TYPES]);\n\nfunction validateOneField(key: string, field: FieldSpec, draft: EditState, record: CollectionItem): string | null {\n // A `when`-hidden field has no input the user can fill — never missing.\n if (!fieldVisible(field, record)) return null;\n if (field.type === \"table\" && field.of) {\n const rows = draft.table[key];\n if (field.required && (!rows || rows.length === 0)) return field.label;\n return firstMissingTableSubField(field, rows);\n }\n if (!field.required) return null;\n if (draft.mode === \"create\" && field.primary === true) return null; // server auto-generates id\n if (NEVER_MISSING_TYPES.has(field.type)) return null;\n return isMissingDraftValue(draft.text[key]) ? field.label : null;\n}\n\n/** Human-readable label of the first missing required field, or null. */\nexport function firstMissingRequiredField(draft: EditState, schema: CollectionSchema): string | null {\n const record = draftToRecord(draft, schema);\n for (const [key, field] of Object.entries(schema.fields)) {\n const missing = validateOneField(key, field, draft, record);\n if (missing) return missing;\n }\n return null;\n}\n","// The slash-command chat seed a collection view sends to the agent when the\n// user starts a chat from a skill-backed collection or one of its records.\n// Skill-backed collections have a `/<slug>` command, so seed that. (Feeds are\n// skill-less and seeded differently; that branch stays in the view because it\n// is i18n-driven.)\n\n/** `/<slug> <message>`, or `/<slug> id=<itemId> <message>` when a specific\n * record is addressed. The `id=` selector goes BEFORE the free-text message so\n * the skill's argument parser reads it as a flag rather than as prose. */\nexport function skillCommandSeed(slug: string, message: string, itemId?: string): string {\n return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;\n}\n","// Sorting for the collection list table. The header sort toggle (next to\n// each field title) cycles a single active column through none → asc →\n// desc → none; this module turns that state plus a per-field value\n// extractor into an ordered copy of the rows.\n//\n// Two invariants the comparator guarantees:\n// 1. Rows with an empty/missing value for the sorted field always sink\n// to the bottom, regardless of direction.\n// 2. Ties (and the empty group) keep their original order — the sort is\n// stable via the captured source index.\n//\n// Field-type → comparable mapping (see `isSortableField`):\n// string/text/email → string · number/money → numeric ·\n// date/datetime → epoch-ms · enum → declared-index · boolean/toggle/flag →\n// false<true · ref → display label · derived → its display type.\n// markdown/table/image/file/embed get no sort button.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem, CollectionFieldSpec, CollectionFieldType } from \"./schema\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface SortState {\n /** Field key of the single active sort column. */\n field: string;\n direction: SortDirection;\n}\n\n/** A row's comparable value for the active field. Exactly one of `num` /\n * `str` is set when not empty; `empty` rows always sort last. */\nexport interface SortValue {\n empty: boolean;\n num?: number;\n str?: string;\n}\n\nconst EMPTY: SortValue = { empty: true };\n\n/** Field types that render no value text in the table, so offer no sort. */\nconst NON_SORTABLE: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"markdown\", \"table\", \"image\", \"file\", \"embed\", \"backlinks\", \"rollup\"]);\n\nexport function isSortableField(field: CollectionFieldSpec): boolean {\n return !NON_SORTABLE.has(field.type);\n}\n\n/** Cycle one column's state: none → asc → desc → none. */\nexport function nextSortDirection(current: SortDirection | null): SortDirection | null {\n if (current === null) return \"asc\";\n if (current === \"asc\") return \"desc\";\n return null;\n}\n\n// ── SortValue constructors (one per comparable kind) ────────────────\n\nexport function numericSortValue(raw: unknown): SortValue {\n if (raw == null || raw === \"\") return EMPTY;\n const num = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(num) ? { empty: false, num } : EMPTY;\n}\n\nexport function stringSortValue(raw: unknown): SortValue {\n // An array/object has no sort key — treat it as empty (sorts last) instead of\n // grouping every such record together under \"[object Object]\".\n const str = fieldTextOrNull(raw);\n if (str === null) return EMPTY;\n return str.trim() === \"\" ? EMPTY : { empty: false, str };\n}\n\nexport function dateSortValue(raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const epoch = Date.parse(text);\n // Unparseable dates fall back to a lexical compare rather than vanishing.\n return Number.isNaN(epoch) ? stringSortValue(raw) : { empty: false, num: epoch };\n}\n\n/** Enum sorts by the value's index in the declared `values` list. A value\n * outside the list (or unset) is treated as empty → last. */\nexport function enumSortValue(values: readonly string[] | undefined, raw: unknown): SortValue {\n const text = fieldTextOrNull(raw);\n if (text === null || text === \"\") return EMPTY;\n const idx = values ? values.indexOf(text) : -1;\n return idx < 0 ? EMPTY : { empty: false, num: idx };\n}\n\n/** Boolean / toggle: false < true, never empty (unset reads as false). */\nexport function boolSortValue(checked: boolean): SortValue {\n return { empty: false, num: checked ? 1 : 0 };\n}\n\n// ── Comparator + driver ─────────────────────────────────────────────\n\nexport function compareSortValues(left: SortValue, right: SortValue): number {\n if (left.num !== undefined && right.num !== undefined) return left.num - right.num;\n const leftStr = left.str ?? String(left.num ?? \"\");\n const rightStr = right.str ?? String(right.num ?? \"\");\n return leftStr.localeCompare(rightStr);\n}\n\n/** Stable sort of `items` by `valueOf`. Empties always last; ties hold\n * source order. Returns a new array (does not mutate `items`). */\nexport function sortItems(items: readonly CollectionItem[], direction: SortDirection, valueOf: (item: CollectionItem) => SortValue): CollectionItem[] {\n const dir = direction === \"asc\" ? 1 : -1;\n return items\n .map((item, index) => ({ item, index, sv: valueOf(item) }))\n .sort((left, right) => {\n if (left.sv.empty || right.sv.empty) {\n if (left.sv.empty && right.sv.empty) return left.index - right.index;\n return left.sv.empty ? 1 : -1;\n }\n const base = compareSortValues(left.sv, right.sv);\n return base !== 0 ? base * dir : left.index - right.index;\n })\n .map((decorated) => decorated.item);\n}\n","// Field-type → comparable value for the collection list-table sort. Splits\n// the header sort's per-row value extraction out of the Vue view so the\n// dispatch (which field type maps to which SortValue constructor) is testable\n// on its own. The comparators live in `./sortItems`; this module only routes.\n//\n// A few field kinds can't be sorted from the raw cell alone — `toggle` and\n// `flag` are projected booleans, `derived` is a formula, and `ref` sorts by\n// its resolved display label. Those readers live in the view's rendering\n// composable, so they are passed in (dependency injection) rather than\n// imported, keeping this module pure and framework-free.\n\nimport { numericSortValue, stringSortValue, dateSortValue, enumSortValue, boolSortValue, type SortValue } from \"./sortItems\";\nimport type { CollectionItem, CollectionFieldSpec as FieldSpec } from \"./schema\";\n\n/** Resolve a `ref` cell to its human display label (target slug + item id). */\nexport type RefDisplayResolver = (targetSlug: string, itemSlug: string) => string;\n\n/** The row readers the view supplies for the field kinds that can't sort off\n * the raw cell value. All come from the rendering composable. */\nexport interface SortValueDeps {\n toggleChecked: (item: CollectionItem, field: FieldSpec) => boolean;\n flagValueOf: (key: string, item: CollectionItem) => boolean;\n evaluateDerived: (field: FieldSpec, key: string, item: CollectionItem) => number | null;\n deriveRecord: (item: CollectionItem) => CollectionItem;\n resolveRefDisplay: RefDisplayResolver;\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nexport function scalarSortValue(field: FieldSpec, raw: unknown, resolveRefDisplay: RefDisplayResolver): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(resolveRefDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date → epoch, anything else → the enriched value as a string. */\nexport function derivedSortValue(\n field: FieldSpec,\n key: string,\n item: CollectionItem,\n deps: Pick<SortValueDeps, \"evaluateDerived\" | \"deriveRecord\">,\n): SortValue {\n const display = field.type === \"derived\" ? field.display : undefined;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(deps.evaluateDerived(field, key, item));\n }\n const enriched = deps.deriveRecord(item);\n if (display === \"date\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\n/** Comparable value for one row under the active field. Toggle, flag, and\n * derived need the whole record; every other type keys off the raw cell. */\nexport function sortValueOf(field: FieldSpec, key: string, item: CollectionItem, deps: SortValueDeps): SortValue {\n if (field.type === \"toggle\") return boolSortValue(deps.toggleChecked(item, field));\n if (field.type === \"flag\") return boolSortValue(deps.flagValueOf(key, item));\n if (field.type === \"derived\") return derivedSortValue(field, key, item, deps);\n return scalarSortValue(field, item[key], deps.resolveRefDisplay);\n}\n","// Free-text record matching for the collection list search box. Pure and\n// framework-free so the view's `filteredItems` computed just calls it.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object/array-valued fields (table rows, nested records) have no text\n * form (`fieldTextOrNull` → `null`) and are skipped — they don't render as\n * searchable text in the list table.\n *\n * `query` is matched against each cell's lower-cased text but is NOT\n * lower-cased here: callers pass an already-normalised (trimmed +\n * lower-cased) query, so an upper-case `query` intentionally matches\n * nothing. Keeping the normalisation at the call site avoids re-lowering\n * the same query once per row. */\nexport function itemMatchesQuery(item: CollectionItem, query: string): boolean {\n // An empty query matches every record — otherwise a row whose only fields are\n // object/array-valued (no scalar cell reaches `includes(\"\")`) would vanish\n // when the search box is cleared. The list caller already short-circuits the\n // empty case, so this only guards a future caller that doesn't.\n if (query === \"\") return true;\n return Object.values(item).some((value) => {\n const text = fieldTextOrNull(value);\n return text !== null && text.toLowerCase().includes(query);\n });\n}\n","// Composite cell keys for the list table's per-cell state maps, plus the\n// load-time \"which enum cells were empty\" snapshot the inline enum dropdown\n// reads. Pure and framework-free so the view keeps only the reactive refs.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem } from \"./schema\";\n\n/** The schema slice `snapshotEmptyEnums` reads — minimal structural shape so\n * both the client and server collection-schema types satisfy it as-is. */\nexport interface EnumSnapshotSchema {\n fields: Record<string, { type: string }>;\n primaryKey: string;\n}\n\n/** Stable key for one cell (`<rowId>:<fieldKey>`) in the per-cell state maps. */\nexport function cellKey(rowId: string, fieldKey: string): string {\n return `${rowId}:${fieldKey}`;\n}\n\n/** A row's stable identity: the primary-key value as text (`\"\"` when the schema\n * has no primary key, or the value is absent). Every per-row / per-cell state\n * map keys off this, and it is the SAME derivation `snapshotEmptyEnums` uses\n * for its record ids (`fieldText`), so `cellKey(rowIdOf(...), field)` and the\n * empty-enum snapshot always agree. A real primary key is a scalar, so this\n * matches its plain string form. */\nexport function rowIdOf(primaryKey: string | undefined, item: CollectionItem): string {\n return primaryKey ? fieldText(item[primaryKey]) : \"\";\n}\n\n/** The set of enum cells that were empty in the freshly-fetched records — the\n * only cells whose inline dropdown offers an empty option. */\nexport function snapshotEmptyEnums(schema: EnumSnapshotSchema, records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = fieldText(record[schema.primaryKey]);\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n","// Shared chip/label resolution for the collection calendar surfaces (month\n// grid + day view), so both label a record identically. Pure, type-only\n// dependency on the schema shape.\n\nimport { fieldText } from \"./fieldText\";\nimport type { CollectionItem, CollectionSchema } from \"./schema\";\n\n// Text-like field types that make a sensible human-readable label.\nconst LABEL_FIELD_TYPES = new Set([\"string\", \"text\", \"markdown\", \"email\"]);\n\n/** Which field labels a record: the schema's explicit `displayField`, else the\n * first non-primary text-like field (so a collection without a displayField\n * shows e.g. the title rather than the opaque primaryKey), else null → the\n * caller falls back to the primary-key value. */\nexport function labelFieldFor(schema: CollectionSchema): string | null {\n if (schema.displayField) return schema.displayField;\n for (const [key, spec] of Object.entries(schema.fields)) {\n if (key !== schema.primaryKey && LABEL_FIELD_TYPES.has(spec.type)) return key;\n }\n return null;\n}\n\n/** A record's primary-key value as a string. */\nexport function itemIdOf(item: CollectionItem, schema: CollectionSchema): string {\n return fieldText(item[schema.primaryKey]);\n}\n\n/** A record's display label: the resolved `labelField` value, else the\n * primary key. Pass the result of `labelFieldFor(schema)` as `labelField`\n * (compute it once per render rather than per item). */\nexport function itemLabelOf(item: CollectionItem, schema: CollectionSchema, labelField: string | null): string {\n if (labelField) {\n const value = item[labelField];\n // Accept any primitive (string / number / boolean) so a numeric or boolean\n // display field still labels the record; objects/arrays fall through to the\n // id rather than rendering as \"[object Object]\".\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n const text = String(value);\n if (text.length > 0) return text;\n }\n }\n return itemIdOf(item, schema);\n}\n","/**\n * 8-char hex id — short, slug-safe, and editable. Produces the same id *shape*\n * as the server's `generateItemId()` (8 hex chars) so a UI-created collection\n * record looks like one the server would have generated for a form submitted\n * with a blank primary key. The source of randomness differs (UUID-derived here\n * vs `randomBytes` on the server); only the shape is intentionally shared.\n */\nexport function shortHexId(): string {\n return crypto.randomUUID().replace(/-/g, \"\").slice(0, 8);\n}\n","// Pure builder for the workspace ontology graph — the `/collections` Map\n// panel (phase 2 of plans/done/collection-ontology.md step ①; plan:\n// plans/done/feat-collection-ontology-graph.md). Consumes the per-collection\n// ontology entries `buildWorkspaceOntology` derives server-side and turns\n// them into a node/edge structure a graph view can render directly.\n// Browser-safe (no `node:*` imports) so the Vue plugin builds the same\n// graph from the raw entries — same parity discipline as `deriveAll` and\n// the sibling `wiki/graph.ts`.\n\n/** One relationship a schema declares: a `ref` (the record stores the\n * target's primaryKey slug) or `embed` (display-only pull) pointing at\n * collection `to`, or `backlinks` / `rollup` (display-only REVERSE\n * refs — `to` is the relation's source collection, i.e. its `from`,\n * and `via` names the exact ref field there being reversed).\n * A `ref` column inside a `table` field is reported with a dotted path\n * (`lines.clientId`). Whether `to` exists is NOT checked — resolution\n * is fail-soft at render. */\nexport interface OntologyRelation {\n field: string;\n kind: \"ref\" | \"embed\" | \"backlinks\" | \"rollup\";\n to: string;\n via?: string;\n}\n\nexport interface CollectionOntologyEntry {\n slug: string;\n title: string;\n icon: string;\n primaryKey: string;\n /** The effective display field: the schema's `displayField`, falling\n * back to the primaryKey exactly as render-time labelling does. */\n displayField: string;\n /** Records in the collection, or `null` when the backend couldn't be\n * reached to count them (an engine that failed to load, a closed\n * session). Deliberately not 0: an unreachable collection reported as\n * empty invites \"restoring\" data that is intact but out of reach. */\n recordCount: number | null;\n relations: OntologyRelation[];\n}\n\nexport interface OntologyGraphNode {\n slug: string;\n title: string;\n icon: string;\n recordCount: number | null;\n /** No discovered collection has this slug — the node exists only\n * because a relation points at it. Rendered as a ghost so the graph\n * doubles as a broken-ref lint surface (lint, not lock). */\n missing?: boolean;\n}\n\nexport interface OntologyGraphEdge {\n /** Tail: the collection whose records store (`ref`) or pull (`embed`)\n * the link — or, for an uncollapsed reverse relation, the collection\n * whose refs the `backlinks`/`rollup` field aggregates. */\n from: string;\n /** Head: the collection the link points at. */\n to: string;\n /** Field declaring the relation — on `from` for `ref`/`embed`, on\n * `to` for an uncollapsed `backlinks`/`rollup`. */\n field: string;\n kind: OntologyRelation[\"kind\"];\n /** `backlinks`/`rollup` field names on `to` that declare the reverse\n * of this `ref` edge, collapsed here so one link never draws twice. */\n reverseFields?: string[];\n}\n\nexport interface OntologyGraph {\n nodes: OntologyGraphNode[];\n edges: OntologyGraphEdge[];\n}\n\nconst isForwardKind = (kind: OntologyRelation[\"kind\"]): boolean => kind === \"ref\" || kind === \"embed\";\n\n/** Fold a reverse relation (`backlinks`/`rollup` declared on `owner`)\n * into the edge list: collapse onto the forward `ref` edge whose field\n * is the relation's declared `via` (several refs can link the same\n * pair — matches.homeTeam vs matches.awayTeam — so the pair alone is\n * ambiguous), else emit its own edge oriented in true data direction\n * (refs' source → owner). A `via` naming no forward edge stays\n * uncollapsed — fail-soft, like every other dangling reference. */\nconst addReverseRelation = (edges: OntologyGraphEdge[], owner: string, rel: OntologyRelation): void => {\n const forward = edges.find((edge) => edge.kind === \"ref\" && edge.from === rel.to && edge.to === owner && (rel.via === undefined || edge.field === rel.via));\n if (forward) {\n forward.reverseFields = [...(forward.reverseFields ?? []), rel.field];\n return;\n }\n edges.push({ from: rel.to, to: owner, field: rel.field, kind: rel.kind });\n};\n\n/** Two passes so every forward `ref` edge exists before any reverse\n * relation tries to collapse onto one — entry order must not matter. */\nconst collectEdges = (entries: CollectionOntologyEntry[]): OntologyGraphEdge[] => {\n const edges: OntologyGraphEdge[] = [];\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (isForwardKind(rel.kind)) edges.push({ from: entry.slug, to: rel.to, field: rel.field, kind: rel.kind });\n }\n }\n for (const entry of entries) {\n for (const rel of entry.relations) {\n if (!isForwardKind(rel.kind)) addReverseRelation(edges, entry.slug, rel);\n }\n }\n return edges;\n};\n\n/** Slugs referenced by an edge endpoint but backed by no discovered\n * collection, slug-sorted for deterministic node order. */\nconst ghostSlugs = (edges: OntologyGraphEdge[], known: ReadonlySet<string>): string[] => {\n const ghosts = new Set<string>();\n for (const edge of edges) {\n if (!known.has(edge.from)) ghosts.add(edge.from);\n if (!known.has(edge.to)) ghosts.add(edge.to);\n }\n return [...ghosts].sort();\n};\n\n/** Build the renderable graph: one node per discovered collection (in\n * the entries' slug-sorted order) plus ghost nodes for dangling\n * targets, one edge per underlying link. */\nexport const buildOntologyGraph = (entries: CollectionOntologyEntry[]): OntologyGraph => {\n const edges = collectEdges(entries);\n const known = new Set(entries.map((entry) => entry.slug));\n const nodes: OntologyGraphNode[] = entries.map(({ slug, title, icon, recordCount }) => ({ slug, title, icon, recordCount }));\n for (const slug of ghostSlugs(edges, known)) {\n // A ghost node has no collection behind it at all, so it genuinely holds\n // no records — 0, not the \"couldn't count\" null.\n nodes.push({ slug, title: slug, icon: \"\", recordCount: 0, missing: true });\n }\n return { nodes, edges };\n};\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { t as errorMessage } from "../dist-D8zokgGo.js";
2
- import { A as fieldTextOrNull, C as FEED_SCHEDULES, D as isReadOnlySchema, E as isFieldDrivenEvery, O as storageKindFor, S as COMPUTED_TYPES, T as embedTargetId, _ as generateUniqueId, a as compareYmd, b as nextUniqueItemId, c as monthAnchorDate, d as parseTimeRange, f as recordSpan, g as SAFE_SLUG_PATTERN, h as SAFE_RECORD_ID_PATTERN, i as buildMonthGrid, k as fieldText, l as parseIsoDate, m as ymdKey, n as assignLanes, o as dateOf, p as spanCoversDay, r as bucketRecords, s as daySlice, t as MINUTES_PER_DAY, u as parseIsoDateTime, v as isSafeRecordId, w as INGEST_KINDS, x as AGENT_INGEST_KIND, y as isSafeSlug } from "../calendarGrid-DivcNKkX.js";
2
+ import { A as fieldTextOrNull, C as FEED_SCHEDULES, D as isReadOnlySchema, E as isFieldDrivenEvery, O as storageKindFor, S as COMPUTED_TYPES, T as embedTargetId, _ as generateUniqueId, a as compareYmd, b as nextUniqueItemId, c as monthAnchorDate, d as parseTimeRange, f as recordSpan, g as SAFE_SLUG_PATTERN, h as SAFE_RECORD_ID_PATTERN, i as buildMonthGrid, k as fieldText, l as parseIsoDate, m as ymdKey, n as assignLanes, o as dateOf, p as spanCoversDay, r as bucketRecords, s as daySlice, t as MINUTES_PER_DAY, u as parseIsoDateTime, v as isSafeRecordId, w as INGEST_KINDS, x as AGENT_INGEST_KIND, y as isSafeSlug } from "../calendarGrid-Csy2rpjp.js";
3
3
  import { n as projectRecordFields, t as mergeIntoExisting } from "../project-B0j_Vf_Q.js";
4
- import { C as actionVisible, E as whenMatches, S as viaMatches, T as fieldVisible, _ as uniqueRefTargets, a as evaluateDerived, b as projectBacklinkRow, c as selectDynamicRecord, d as flagFieldValue, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, l as chipMatches, m as matchesWhere, n as deriveAll, o as firstDateField, p as toggleChecked, r as resolveRowRefs, s as resolveIcon, t as defangForPrompt, u as completionCoveredByFieldChip, v as backlinkRows, w as agentActionRunKey, x as rollupValue, y as coerceNumeric } from "../promptSafety-DJgaDjM4.js";
4
+ import { C as actionVisible, E as whenMatches, S as viaMatches, T as fieldVisible, _ as uniqueRefTargets, a as evaluateDerived, b as projectBacklinkRow, c as selectDynamicRecord, d as flagFieldValue, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, l as chipMatches, m as matchesWhere, n as deriveAll, o as firstDateField, p as toggleChecked, r as resolveRowRefs, s as resolveIcon, t as defangForPrompt, u as completionCoveredByFieldChip, v as backlinkRows, w as agentActionRunKey, x as rollupValue, y as coerceNumeric } from "../promptSafety-DgHMF4Pz.js";
5
5
  //#region src/collection/core/presentCollection.ts
6
6
  var TOOL_NAME = "presentCollection";
7
7
  var TOOL_DEFINITION = {
@@ -270,12 +270,9 @@ function isMissingDraftValue(value) {
270
270
  /** Label of the first required table sub-field empty in any row, else null. */
271
271
  function firstMissingTableSubField(field, rows) {
272
272
  if (field.type !== "table" || !rows) return null;
273
- for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
274
- const row = rows[rowIdx];
275
- for (const [subKey, subField] of Object.entries(field.of)) {
276
- if (!subField.required || subField.type === "boolean") continue;
277
- if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;
278
- }
273
+ for (const [rowIdx, row] of rows.entries()) for (const [subKey, subField] of Object.entries(field.of)) {
274
+ if (!subField.required || subField.type === "boolean") continue;
275
+ if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;
279
276
  }
280
277
  return null;
281
278
  }