@mulmoclaude/core 0.8.0 → 0.8.2
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.
- package/assets/helps/billing-clients-worklog.md +73 -5
- package/dist/collection/core/dynamicIcon.d.ts +24 -0
- package/dist/collection/core/schema.d.ts +55 -0
- package/dist/collection/core/where.d.ts +32 -0
- package/dist/collection/index.cjs +5 -1
- package/dist/collection/index.d.ts +2 -0
- package/dist/collection/index.js +2 -2
- package/dist/collection/registry/server/index.cjs +1 -1
- package/dist/collection/registry/server/index.js +1 -1
- package/dist/collection/server/discovery.d.ts +51 -0
- package/dist/collection/server/dynamicIcon.d.ts +10 -0
- package/dist/collection/server/index.cjs +2 -1
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +2 -2
- package/dist/collection-watchers/index.cjs +1 -1
- package/dist/collection-watchers/index.js +1 -1
- package/dist/{deriveAll-Cb9rWjan.cjs → deriveAll-BQ_p5HSB.cjs} +149 -1
- package/dist/deriveAll-BQ_p5HSB.cjs.map +1 -0
- package/dist/{deriveAll-D3wFH4Tw.js → deriveAll-CMFXDQ_G.js} +126 -2
- package/dist/deriveAll-CMFXDQ_G.js.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +3 -3
- package/dist/feeds/server/index.js +3 -3
- package/dist/{ingestTypes-DhJ63Ogd.cjs → ingestTypes-C7EheYZX.cjs} +2 -2
- package/dist/{ingestTypes-DhJ63Ogd.cjs.map → ingestTypes-C7EheYZX.cjs.map} +1 -1
- package/dist/{ingestTypes-BtMZogMX.js → ingestTypes-CmJeOUJc.js} +2 -2
- package/dist/{ingestTypes-BtMZogMX.js.map → ingestTypes-CmJeOUJc.js.map} +1 -1
- package/dist/{server-BDxrLT41.js → server-DKXXFTbw.js} +96 -4
- package/dist/server-DKXXFTbw.js.map +1 -0
- package/dist/{server-U2d7Fb1h.cjs → server-G6GtOdaW.cjs} +101 -3
- package/dist/server-G6GtOdaW.cjs.map +1 -0
- package/package.json +4 -4
- package/dist/deriveAll-Cb9rWjan.cjs.map +0 -1
- package/dist/deriveAll-D3wFH4Tw.js.map +0 -1
- package/dist/server-BDxrLT41.js.map +0 -1
- package/dist/server-U2d7Fb1h.cjs.map +0 -1
|
@@ -26,8 +26,10 @@ user opens it at `/collections/<slug>`). **Do not use the `mc-` prefix.**
|
|
|
26
26
|
> mimic other collections in the workspace. The whole point is a reproducible
|
|
27
27
|
> billing suite. (If the user wants a *custom* collection instead, that's a
|
|
28
28
|
> different task — use `config/helps/collection-skills.md`.) Existing records
|
|
29
|
-
> under `data/clients/items` / `data/worklog/items`
|
|
30
|
-
>
|
|
29
|
+
> under `data/clients/items` / `data/worklog/items` will render as-is **only
|
|
30
|
+
> when `worklog.clientId` values are already slugs AND a matching client
|
|
31
|
+
> record exists for each of them**. If either invariant is violated, the
|
|
32
|
+
> `ref` link breaks — the reconcile step below (§3) fixes both.
|
|
31
33
|
|
|
32
34
|
## Slug contract (do not change these)
|
|
33
35
|
|
|
@@ -206,10 +208,76 @@ If the user gives a clear "log N hours for X today", just write it. Use
|
|
|
206
208
|
|
|
207
209
|
---
|
|
208
210
|
|
|
211
|
+
## 3. Reconcile pre-existing worklog data (MANDATORY before "Done")
|
|
212
|
+
|
|
213
|
+
Both skills are now authored. Before telling the user setup is complete,
|
|
214
|
+
audit any records that were already on disk under `data/worklog/items/` —
|
|
215
|
+
they may have been written before the `clients` collection existed, so
|
|
216
|
+
`clientId` values can be either display names (`"Singularity Society"`) or
|
|
217
|
+
missing-slug references. Both break the `ref` link at render time.
|
|
218
|
+
|
|
219
|
+
Run this reconcile every time — even when the setup looks fine — because
|
|
220
|
+
"looks fine" from the LLM side (schemas written, files present) is
|
|
221
|
+
distinct from "the ref actually resolves" (clientId is a valid slug AND
|
|
222
|
+
`data/clients/items/<slug>.json` exists).
|
|
223
|
+
|
|
224
|
+
**Steps** (idempotent; safe to re-run):
|
|
225
|
+
|
|
226
|
+
1. **Inventory the existing worklog.** List `data/worklog/items/`. Read
|
|
227
|
+
every record and collect the distinct `clientId` values seen.
|
|
228
|
+
*Skip this step only when the directory does not exist at all.*
|
|
229
|
+
2. **For each distinct `clientId` value**, classify it:
|
|
230
|
+
- **valid slug + matching client file exists** → no action.
|
|
231
|
+
- **valid slug but no matching `data/clients/items/<slug>.json`** →
|
|
232
|
+
create a stub client record: `id: <slug>`, `name` derived by
|
|
233
|
+
title-casing the slug (`acme-corp` → "Acme Corp"), other fields blank
|
|
234
|
+
so the user can fill them in later.
|
|
235
|
+
- **not a valid slug** (contains uppercase, spaces, `&`, `.`, etc.) →
|
|
236
|
+
(a) generate the slug (lowercase, spaces → `-`, drop punctuation,
|
|
237
|
+
collapse repeat hyphens, strip leading/trailing hyphens); (b)
|
|
238
|
+
**validate the generated slug against the `clients.id` contract**
|
|
239
|
+
(see § 1 record shape: `[a-z0-9-]`, 1–48 chars): if it is empty
|
|
240
|
+
(punctuation-only or all-whitespace input like `"!!!"`), or longer
|
|
241
|
+
than 48 chars, **stop and ask the user** for a valid slug rather
|
|
242
|
+
than writing an invalid filename. If it is >48 chars but has
|
|
243
|
+
meaningful prefix content, propose truncation at 48 (dropping any
|
|
244
|
+
trailing hyphen) and confirm with the user before proceeding; (c)
|
|
245
|
+
**check for slug collisions BEFORE writing anything**: if
|
|
246
|
+
`data/clients/items/<slug>.json` already exists AND its `name`
|
|
247
|
+
field does not match the display value being reconciled, stop and
|
|
248
|
+
disambiguate — either append a numeric suffix (`acme-corp-2`)
|
|
249
|
+
after confirming the new client is genuinely distinct, or ask the
|
|
250
|
+
user which client the worklog entries belong to. Do **not**
|
|
251
|
+
silently overwrite an existing client. Also stop when two
|
|
252
|
+
different display values in the current inventory slugify to the
|
|
253
|
+
same slug (e.g. `"ACME Corp"` and `"Acme Corp."`); ask the user
|
|
254
|
+
whether they're the same client (merge to one slug) or distinct
|
|
255
|
+
(assign each a suffixed slug); (d) create
|
|
256
|
+
`data/clients/items/<slug>.json` with `id: <slug>`, `name:
|
|
257
|
+
<original display value>`, other fields blank; (e) rewrite the raw
|
|
258
|
+
display value inside every affected `data/worklog/items/*.json`
|
|
259
|
+
to the new slug in the `clientId` field (preserve every other
|
|
260
|
+
field untouched).
|
|
261
|
+
3. **Report** what changed: how many client records were created and how
|
|
262
|
+
many worklog `clientId` values were rewritten. Do not summarise the
|
|
263
|
+
raw records — point at `/collections/clients` and `/collections/worklog`.
|
|
264
|
+
|
|
265
|
+
If the worklog directory did not exist yet (fresh setup, no pre-existing
|
|
266
|
+
data), this step is a no-op and you can proceed straight to "Done".
|
|
267
|
+
|
|
268
|
+
**Don't skip this step because the LLM output says "existing records will
|
|
269
|
+
render as-is."** That claim only holds when both invariants (slug format
|
|
270
|
+
+ matching client file) are already true; the reconcile confirms them.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
209
274
|
## Done
|
|
210
275
|
|
|
211
276
|
Tell the user the two collections are ready at `/collections/clients` and
|
|
212
277
|
`/collections/worklog`. The bridge mirrors the files and re-scans, so they appear
|
|
213
|
-
without a restart. If
|
|
214
|
-
|
|
215
|
-
|
|
278
|
+
without a restart. If the reconcile in §3 created client stubs or rewrote
|
|
279
|
+
`clientId` values, mention the counts so the user knows to fill in the
|
|
280
|
+
stubs' contact info. If invoicing is the goal, point them at the next
|
|
281
|
+
step: run *"Set up invoicing for my business"* (the
|
|
282
|
+
`config/helps/billing-invoice.md` recipe, which references these
|
|
283
|
+
`clients` and pulls hours from this `worklog`).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CollectionItem, CollectionSchema, DynamicIconSource, DynamicIconSpec } from './schema';
|
|
2
|
+
/** Reduce `records` to the one record that decides the effective icon, per
|
|
3
|
+
* `source`'s `where` filter + `from` strategy:
|
|
4
|
+
* - pool = `source.where`-filtered records, or every record when unset;
|
|
5
|
+
* - an empty pool resolves to `null` (no source record → fallback);
|
|
6
|
+
* - `from: "first"` / `"when"` → the first pool record (storage order);
|
|
7
|
+
* - `from: "latest"` (default), with `orderBy` given → the pool record
|
|
8
|
+
* whose `String(record[orderBy])` sorts highest;
|
|
9
|
+
* - `from: "latest"`, with no `orderBy` → the last pool record.
|
|
10
|
+
* `recordsById` (the source collection's records keyed by primaryKey)
|
|
11
|
+
* resolves any `valueFrom` reference inside `source.where`; omitted for
|
|
12
|
+
* callers with no cross-record lookups. */
|
|
13
|
+
export declare function selectDynamicRecord(records: CollectionItem[], source: DynamicIconSource, orderBy: string | undefined, recordsById?: Record<string, CollectionItem>): CollectionItem | null;
|
|
14
|
+
/** Map a resolved source record to the effective icon: `spec.fallback`
|
|
15
|
+
* (or the collection's own static `icon`) when there's no record or no
|
|
16
|
+
* rule matches; otherwise the `icon` of the first rule whose `where`
|
|
17
|
+
* matches the record. `recordsById` resolves any `valueFrom` reference
|
|
18
|
+
* inside a rule's `where`, same as `selectDynamicRecord`. */
|
|
19
|
+
export declare function resolveIcon(record: CollectionItem | null, spec: DynamicIconSpec, staticIcon: string, recordsById?: Record<string, CollectionItem>): string;
|
|
20
|
+
/** The first field key (declaration order) whose type is `date` or
|
|
21
|
+
* `datetime` — the default `orderBy` for `from: "latest"` when a
|
|
22
|
+
* `DynamicIconSource` doesn't name one. `undefined` when the schema has
|
|
23
|
+
* no date-like field. */
|
|
24
|
+
export declare function firstDateField(schema: CollectionSchema): string | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Where } from './where';
|
|
1
2
|
/** Minimal "this collection is a feed" descriptor carried on the schema.
|
|
2
3
|
* Deliberately narrow — the canonical collection contract stays
|
|
3
4
|
* independent of the host's feeds subsystem. The host's richer retrieval
|
|
@@ -226,6 +227,48 @@ export interface CollectionAction {
|
|
|
226
227
|
* shown. */
|
|
227
228
|
when?: CollectionWhen;
|
|
228
229
|
}
|
|
230
|
+
/** One rule in a `dynamicIcon.rules` list: when the resolved source
|
|
231
|
+
* record matches `where` (an AND of typed conditions, see {@link Where}),
|
|
232
|
+
* the collection's effective launcher icon becomes `icon`. Evaluated top
|
|
233
|
+
* to bottom — the first match wins. */
|
|
234
|
+
export interface DynamicIconRule {
|
|
235
|
+
where: Where;
|
|
236
|
+
icon: string;
|
|
237
|
+
}
|
|
238
|
+
/** Where a {@link DynamicIconSpec}'s source record comes from: a (possibly
|
|
239
|
+
* cross-collection) pool of records, optionally narrowed by `where` and
|
|
240
|
+
* reduced to a single record by `from`. */
|
|
241
|
+
export interface DynamicIconSource {
|
|
242
|
+
/** Slug of the collection to read records from — the collection itself
|
|
243
|
+
* (self-reference) or another one (cross-collection). Required. */
|
|
244
|
+
collection: string;
|
|
245
|
+
/** How to reduce the (optionally `where`-filtered) pool to one record.
|
|
246
|
+
* `"latest"` (default): the record with the greatest `orderBy` value.
|
|
247
|
+
* `"first"` / `"when"`: the first record in the pool (storage order). */
|
|
248
|
+
from?: "latest" | "first" | "when";
|
|
249
|
+
/** Field compared for `from: "latest"`. Defaults to the source schema's
|
|
250
|
+
* first `date`/`datetime` field (declaration order); when the source
|
|
251
|
+
* has none, `"latest"` falls back to the last pool record. */
|
|
252
|
+
orderBy?: string;
|
|
253
|
+
/** Optional predicate (AND of typed conditions) narrowing the pool
|
|
254
|
+
* before `from` reduces it to one record (e.g. restrict a shared
|
|
255
|
+
* weather collection to one region). */
|
|
256
|
+
where?: Where;
|
|
257
|
+
}
|
|
258
|
+
/** Declarative "data state → icon" mapping for a collection's launcher
|
|
259
|
+
* shortcut icon (see `CollectionSchema.dynamicIcon`). When absent, the
|
|
260
|
+
* launcher icon is the static `schema.icon`, unchanged from before this
|
|
261
|
+
* field existed. */
|
|
262
|
+
export interface DynamicIconSpec {
|
|
263
|
+
/** The record the `rules` are evaluated against. Required. */
|
|
264
|
+
source: DynamicIconSource;
|
|
265
|
+
/** First-match-wins list of "resolved record matches `where` → `icon`"
|
|
266
|
+
* rules. */
|
|
267
|
+
rules: DynamicIconRule[];
|
|
268
|
+
/** Icon used when no source record resolves or no rule matches.
|
|
269
|
+
* Defaults to the collection's own static `schema.icon`. */
|
|
270
|
+
fallback?: string;
|
|
271
|
+
}
|
|
229
272
|
export interface CollectionFieldSpec {
|
|
230
273
|
type: CollectionFieldType;
|
|
231
274
|
label: string;
|
|
@@ -426,12 +469,24 @@ export interface CollectionSchema {
|
|
|
426
469
|
* `ingest.template`, and the worker edits the records itself. The host's
|
|
427
470
|
* feeds subsystem narrows this to its richer `IngestSpec`. */
|
|
428
471
|
ingest?: CollectionIngest;
|
|
472
|
+
/** Optional data-driven override for the launcher shortcut icon: when
|
|
473
|
+
* set, the host resolves a record from `dynamicIcon.source` and maps
|
|
474
|
+
* it through `dynamicIcon.rules` to compute `CollectionSummary.icon`
|
|
475
|
+
* instead of using this static `icon`. Absent ⇒ unchanged static-icon
|
|
476
|
+
* behaviour. See `computeCollectionIcon` (server) and
|
|
477
|
+
* `selectDynamicRecord`/`resolveIcon` (pure resolver). */
|
|
478
|
+
dynamicIcon?: DynamicIconSpec;
|
|
429
479
|
}
|
|
430
480
|
export interface CollectionSummary {
|
|
431
481
|
slug: string;
|
|
432
482
|
title: string;
|
|
433
483
|
icon: string;
|
|
434
484
|
source: CollectionSource;
|
|
485
|
+
/** Slugs of the source collection(s) a `dynamicIcon` icon was computed
|
|
486
|
+
* from — present only when `schema.dynamicIcon` is set. Lets a client
|
|
487
|
+
* know which collection change-channel(s) to watch for a live icon
|
|
488
|
+
* update (see `useDynamicShortcutIcons`). */
|
|
489
|
+
iconSources?: string[];
|
|
435
490
|
}
|
|
436
491
|
export interface CollectionDetail extends CollectionSummary {
|
|
437
492
|
schema: CollectionSchema;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Comparison operators one `WhereCond` may apply to `record[field]`. */
|
|
2
|
+
export type WhereOp = "eq" | "ne" | "in" | "gt" | "gte" | "lt" | "lte" | "contains";
|
|
3
|
+
/** Reads the comparison value from a field instead of a schema literal:
|
|
4
|
+
* - `record` set → another record: `recordsById[record][field]` (e.g. a
|
|
5
|
+
* `_config` singleton's `defaultCity`, following a per-user setting);
|
|
6
|
+
* - `record` omitted → the SAME record being matched (field-to-field, e.g.
|
|
7
|
+
* `spent > budget`). */
|
|
8
|
+
export interface ValueRef {
|
|
9
|
+
record?: string;
|
|
10
|
+
field: string;
|
|
11
|
+
}
|
|
12
|
+
/** One typed condition: `record[field] <op> value`. The comparison value is
|
|
13
|
+
* either a literal `value` (a plain string for every op except `in`, which
|
|
14
|
+
* takes the allowed set) or a `valueFrom` reference resolved against the
|
|
15
|
+
* `recordsById` map passed to `matchesWhere`. Exactly one of the two is
|
|
16
|
+
* expected — enforced by zod at the schema boundary (`server/discovery.ts`),
|
|
17
|
+
* not here. */
|
|
18
|
+
export interface WhereCond {
|
|
19
|
+
field: string;
|
|
20
|
+
op: WhereOp;
|
|
21
|
+
value?: string | string[];
|
|
22
|
+
valueFrom?: ValueRef;
|
|
23
|
+
}
|
|
24
|
+
/** A `where` clause is the AND of its conditions — every one must match. */
|
|
25
|
+
export type Where = WhereCond[];
|
|
26
|
+
/** True when `record` satisfies every condition in `where` (AND). An empty
|
|
27
|
+
* `where` matches everything. `recordsById` — the source collection's
|
|
28
|
+
* records keyed by primaryKey — resolves any `valueFrom` reference;
|
|
29
|
+
* omitted (default `{}`) for callers with no cross-record lookups, in
|
|
30
|
+
* which case every `valueFrom` condition is UNRESOLVED and so never
|
|
31
|
+
* matches. */
|
|
32
|
+
export declare function matchesWhere(where: Where, record: Record<string, unknown>, recordsById?: Record<string, Record<string, unknown>>): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_deriveAll = require("../deriveAll-
|
|
2
|
+
const require_deriveAll = require("../deriveAll-BQ_p5HSB.cjs");
|
|
3
3
|
//#region src/collection/core/presentCollection.ts
|
|
4
4
|
var TOOL_NAME = "presentCollection";
|
|
5
5
|
var TOOL_DEFINITION = {
|
|
@@ -769,12 +769,14 @@ exports.errorMessage = errorMessage;
|
|
|
769
769
|
exports.evaluateDerived = require_deriveAll.evaluateDerived;
|
|
770
770
|
exports.executePresentCollection = executePresentCollection;
|
|
771
771
|
exports.fieldVisible = fieldVisible;
|
|
772
|
+
exports.firstDateField = require_deriveAll.firstDateField;
|
|
772
773
|
exports.firstMissingRequiredField = firstMissingRequiredField;
|
|
773
774
|
exports.isFieldDrivenEvery = require_deriveAll.isFieldDrivenEvery;
|
|
774
775
|
exports.isSortableField = isSortableField;
|
|
775
776
|
exports.itemIdOf = itemIdOf;
|
|
776
777
|
exports.itemLabelOf = itemLabelOf;
|
|
777
778
|
exports.labelFieldFor = labelFieldFor;
|
|
779
|
+
exports.matchesWhere = require_deriveAll.matchesWhere;
|
|
778
780
|
exports.monthAnchorDate = monthAnchorDate;
|
|
779
781
|
exports.nextSortDirection = nextSortDirection;
|
|
780
782
|
exports.numericSortValue = numericSortValue;
|
|
@@ -783,8 +785,10 @@ exports.parseIsoDateTime = parseIsoDateTime;
|
|
|
783
785
|
exports.parseTimeRange = parseTimeRange;
|
|
784
786
|
exports.recordSpan = recordSpan;
|
|
785
787
|
exports.resolveEnumColor = resolveEnumColor;
|
|
788
|
+
exports.resolveIcon = require_deriveAll.resolveIcon;
|
|
786
789
|
exports.resolveRowRefs = require_deriveAll.resolveRowRefs;
|
|
787
790
|
exports.rowFromItem = rowFromItem;
|
|
791
|
+
exports.selectDynamicRecord = require_deriveAll.selectDynamicRecord;
|
|
788
792
|
exports.shortHexId = shortHexId;
|
|
789
793
|
exports.sortItems = sortItems;
|
|
790
794
|
exports.spanCoversDay = spanCoversDay;
|
|
@@ -4,6 +4,8 @@ export * from './core/presentCollection';
|
|
|
4
4
|
export * from './core/enumColors';
|
|
5
5
|
export * from './core/draft';
|
|
6
6
|
export * from './core/actionVisible';
|
|
7
|
+
export * from './core/where';
|
|
8
|
+
export * from './core/dynamicIcon';
|
|
7
9
|
export * from './core/derivedFormula';
|
|
8
10
|
export * from './core/deriveAll';
|
|
9
11
|
export * from './core/sortItems';
|
package/dist/collection/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as resolveIcon, c as AGENT_INGEST_KIND, d as embedTargetId, f as isFieldDrivenEvery, i as firstDateField, l as FEED_SCHEDULES, n as resolveRowRefs, o as selectDynamicRecord, r as evaluateDerived, s as matchesWhere, t as deriveAll, u as INGEST_KINDS } from "../deriveAll-CMFXDQ_G.js";
|
|
2
2
|
//#region src/collection/core/presentCollection.ts
|
|
3
3
|
var TOOL_NAME = "presentCollection";
|
|
4
4
|
var TOOL_DEFINITION = {
|
|
@@ -735,6 +735,6 @@ function defangForPrompt(value) {
|
|
|
735
735
|
return value.replace(/[<>]/g, "").replace(/`/g, "'").replace(/\$\{/g, "$ {").replace(/\s+/g, " ").slice(0, DEFANG_MAX_LEN);
|
|
736
736
|
}
|
|
737
737
|
//#endregion
|
|
738
|
-
export { AGENT_INGEST_KIND, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, TOOL_DEFINITION, TOOL_NAME, actionVisible, assignLanes, boolSortValue, bucketRecords, buildMonthGrid, buildUpdatedRecord, coerceInlineValue, compareSortValues, compareYmd, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldVisible, firstMissingRequiredField, isFieldDrivenEvery, isSortableField, itemIdOf, itemLabelOf, labelFieldFor, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, recordSpan, resolveEnumColor, resolveRowRefs, rowFromItem, shortHexId, sortItems, spanCoversDay, stringSortValue, whenMatches, ymdKey };
|
|
738
|
+
export { AGENT_INGEST_KIND, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, TOOL_DEFINITION, TOOL_NAME, actionVisible, assignLanes, boolSortValue, bucketRecords, buildMonthGrid, buildUpdatedRecord, coerceInlineValue, compareSortValues, compareYmd, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldVisible, firstDateField, firstMissingRequiredField, isFieldDrivenEvery, isSortableField, itemIdOf, itemLabelOf, labelFieldFor, matchesWhere, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rowFromItem, selectDynamicRecord, shortHexId, sortItems, spanCoversDay, stringSortValue, whenMatches, ymdKey };
|
|
739
739
|
|
|
740
740
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("../../../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
-
const require_server = require("../../../server-
|
|
3
|
+
const require_server = require("../../../server-G6GtOdaW.cjs");
|
|
4
4
|
const require_collection_paths = require("../../paths.cjs");
|
|
5
5
|
const require_types = require("../../../types-CNqkLT4M.cjs");
|
|
6
6
|
const require_skill_bridge_index = require("../../../skill-bridge/index.cjs");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as safeRecordId, R as writeFileAtomic, X as log, _ as CollectionSchemaZ, b as loadCollection, m as errorMessage, p as ONE_SECOND_MS, q as collectionsRegistriesConfigPath, v as acceptParsedSchema } from "../../../server-DKXXFTbw.js";
|
|
2
2
|
import { isSafeActionTemplatePath } from "../../paths.js";
|
|
3
3
|
import { n as parseRegistryIndex, r as isRecord, t as OFFICIAL_REGISTRY_NAME } from "../../../types-BKVZrtyc.js";
|
|
4
4
|
import { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from "../../../skill-bridge/index.js";
|
|
@@ -192,6 +192,57 @@ export declare const CollectionSchemaZ: z.ZodObject<{
|
|
|
192
192
|
role: z.ZodString;
|
|
193
193
|
template: z.ZodString;
|
|
194
194
|
}, z.core.$strip>], "kind">>;
|
|
195
|
+
dynamicIcon: z.ZodOptional<z.ZodObject<{
|
|
196
|
+
source: z.ZodObject<{
|
|
197
|
+
collection: z.ZodString;
|
|
198
|
+
from: z.ZodOptional<z.ZodEnum<{
|
|
199
|
+
latest: "latest";
|
|
200
|
+
first: "first";
|
|
201
|
+
when: "when";
|
|
202
|
+
}>>;
|
|
203
|
+
orderBy: z.ZodOptional<z.ZodString>;
|
|
204
|
+
where: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
205
|
+
field: z.ZodString;
|
|
206
|
+
op: z.ZodEnum<{
|
|
207
|
+
eq: "eq";
|
|
208
|
+
ne: "ne";
|
|
209
|
+
in: "in";
|
|
210
|
+
gt: "gt";
|
|
211
|
+
gte: "gte";
|
|
212
|
+
lt: "lt";
|
|
213
|
+
lte: "lte";
|
|
214
|
+
contains: "contains";
|
|
215
|
+
}>;
|
|
216
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
217
|
+
valueFrom: z.ZodOptional<z.ZodObject<{
|
|
218
|
+
record: z.ZodOptional<z.ZodString>;
|
|
219
|
+
field: z.ZodString;
|
|
220
|
+
}, z.core.$strip>>;
|
|
221
|
+
}, z.core.$strip>>>;
|
|
222
|
+
}, z.core.$strip>;
|
|
223
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
224
|
+
where: z.ZodArray<z.ZodObject<{
|
|
225
|
+
field: z.ZodString;
|
|
226
|
+
op: z.ZodEnum<{
|
|
227
|
+
eq: "eq";
|
|
228
|
+
ne: "ne";
|
|
229
|
+
in: "in";
|
|
230
|
+
gt: "gt";
|
|
231
|
+
gte: "gte";
|
|
232
|
+
lt: "lt";
|
|
233
|
+
lte: "lte";
|
|
234
|
+
contains: "contains";
|
|
235
|
+
}>;
|
|
236
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
237
|
+
valueFrom: z.ZodOptional<z.ZodObject<{
|
|
238
|
+
record: z.ZodOptional<z.ZodString>;
|
|
239
|
+
field: z.ZodString;
|
|
240
|
+
}, z.core.$strip>>;
|
|
241
|
+
}, z.core.$strip>>;
|
|
242
|
+
icon: z.ZodString;
|
|
243
|
+
}, z.core.$strip>>;
|
|
244
|
+
fallback: z.ZodOptional<z.ZodString>;
|
|
245
|
+
}, z.core.$strip>>;
|
|
195
246
|
}, z.core.$strip>;
|
|
196
247
|
/** Result of the post-Zod acceptance gates: the resolved record dir on
|
|
197
248
|
* success, or a one-line reason discovery would skip the schema. */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DiscoveryOptions } from './discovery';
|
|
2
|
+
import { LoadedCollection } from './discoveredCollection';
|
|
3
|
+
/** Compute the effective launcher icon for `collection`: its static
|
|
4
|
+
* `schema.icon` when it declares no `dynamicIcon`, else the icon
|
|
5
|
+
* resolved from `dynamicIcon.source`'s RAW stored records (no
|
|
6
|
+
* derive/enrich — the icon rules match against stored values) via the
|
|
7
|
+
* pure resolver. Fails soft on any read/discovery error (missing source
|
|
8
|
+
* collection, filesystem error): falls back to `dynamicIcon.fallback ??
|
|
9
|
+
* schema.icon` rather than surfacing to the collections list. */
|
|
10
|
+
export declare function computeCollectionIcon(collection: LoadedCollection, opts?: DiscoveryOptions): Promise<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_server = require("../../server-
|
|
2
|
+
const require_server = require("../../server-G6GtOdaW.cjs");
|
|
3
3
|
const require_collection_paths = require("../paths.cjs");
|
|
4
4
|
exports.COMPUTED_TYPES = require_server.COMPUTED_TYPES;
|
|
5
5
|
exports.CollectionSchemaZ = require_server.CollectionSchemaZ;
|
|
@@ -8,6 +8,7 @@ exports.acceptParsedSchema = require_server.acceptParsedSchema;
|
|
|
8
8
|
exports.advanceTriggerDate = require_server.advanceTriggerDate;
|
|
9
9
|
exports.buildActionSeedPrompt = require_server.buildActionSeedPrompt;
|
|
10
10
|
exports.buildCollectionActionSeedPrompt = require_server.buildCollectionActionSeedPrompt;
|
|
11
|
+
exports.computeCollectionIcon = require_server.computeCollectionIcon;
|
|
11
12
|
exports.computeSuccessor = require_server.computeSuccessor;
|
|
12
13
|
exports.configureCollectionHost = require_server.configureCollectionHost;
|
|
13
14
|
exports.daysInMonth = require_server.daysInMonth;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as listItems, B as isContainedInRoot, C as COMPUTED_TYPES, D as buildCollectionActionSeedPrompt, E as buildActionSeedPrompt, F as readSkillTemplate, G as safeRecordId, H as itemFilePath, I as resolveCreateItemId, J as configureCollectionHost, K as safeSlugName, L as writeItem, M as readCustomViewHtml, N as readCustomViewI18n, O as deleteItem, P as readItem, Q as setCollectionChangePublisher, S as toSummary, T as validateRecordObject, U as resolveDataDir, V as isContainedInWorkspace, W as resolveTemplatePath, X as log, Y as getWorkspaceRoot, Z as publishCollectionChange, _ as CollectionSchemaZ, a as computeSuccessor, b as loadCollection, c as isTriggerDue, d as resolveEvery, f as successorId, g as enrichItems, h as computeCollectionIcon, i as advanceTriggerDate, j as promptPathsFor, k as generateItemId, l as maybeSpawnSuccessor, n as deleteCollection, o as daysInMonth, r as deleteCollectionRefusalMessage, s as formatCivil, t as deleteCustomView, u as parseCivil, v as acceptParsedSchema, w as validateCollectionRecords, x as toDetail, y as discoverCollections, z as SCHEMA_FILE } from "../../server-DKXXFTbw.js";
|
|
2
2
|
import { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath } from "../paths.js";
|
|
3
|
-
export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, promptPathsFor, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, safeRecordId, safeSlugName, setCollectionChangePublisher, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
|
|
3
|
+
export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeCollectionIcon, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, promptPathsFor, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, safeRecordId, safeSlugName, setCollectionChangePublisher, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_collection_index = require("../collection/index.cjs");
|
|
3
|
-
const require_server = require("../server-
|
|
3
|
+
const require_server = require("../server-G6GtOdaW.cjs");
|
|
4
4
|
const require_notifier = require("../notifier-bS8IEeLA.cjs");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { whenMatches } from "../collection/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { A as listItems, P as readItem, b as loadCollection, c as isTriggerDue, l as maybeSpawnSuccessor, y as discoverCollections } from "../server-DKXXFTbw.js";
|
|
3
3
|
import { d as publish, m as updateForPlugin, n as clear, s as listAll } from "../notifier-ChpY0XrY.js";
|
|
4
4
|
import { watch } from "node:fs";
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
@@ -38,6 +38,130 @@ function embedTargetId(field, record) {
|
|
|
38
38
|
return "";
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
|
+
//#region src/collection/core/where.ts
|
|
42
|
+
/** True when `record[field]` is absent (`undefined`/`null`) — the only case
|
|
43
|
+
* where `ne` and every other op disagree on the result. */
|
|
44
|
+
function isMissing(raw) {
|
|
45
|
+
return raw === void 0 || raw === null;
|
|
46
|
+
}
|
|
47
|
+
/** The effective comparison value for `cond`: its literal `value`, or — for
|
|
48
|
+
* a `valueFrom` reference — the target field read out of `recordsById`.
|
|
49
|
+
* `undefined` means UNRESOLVED (no such record, or the field on it is
|
|
50
|
+
* missing); the caller must treat that as "never matches", not as a
|
|
51
|
+
* literal `undefined` value to compare against. */
|
|
52
|
+
function resolveValue(cond, record, recordsById) {
|
|
53
|
+
if (!cond.valueFrom) return cond.value;
|
|
54
|
+
const { record: refRecord, field } = cond.valueFrom;
|
|
55
|
+
const raw = (refRecord === void 0 ? record : recordsById[refRecord])?.[field];
|
|
56
|
+
return isMissing(raw) ? void 0 : String(raw);
|
|
57
|
+
}
|
|
58
|
+
function matchesNumericOp(operator, left, right) {
|
|
59
|
+
if (operator === "gt") return left > right;
|
|
60
|
+
if (operator === "gte") return left >= right;
|
|
61
|
+
if (operator === "lt") return left < right;
|
|
62
|
+
return left <= right;
|
|
63
|
+
}
|
|
64
|
+
/** `Number("")` / `Number(" ")` are `0`, not `NaN`, so treat a blank string
|
|
65
|
+
* as non-numeric explicitly — an empty field must fail a numeric compare,
|
|
66
|
+
* not read as zero. */
|
|
67
|
+
function toNumber(raw) {
|
|
68
|
+
return raw.trim() === "" ? NaN : Number(raw);
|
|
69
|
+
}
|
|
70
|
+
function matchesNumeric(operator, raw, value) {
|
|
71
|
+
if (Array.isArray(value)) return false;
|
|
72
|
+
const left = toNumber(raw);
|
|
73
|
+
const right = toNumber(value);
|
|
74
|
+
if (Number.isNaN(left) || Number.isNaN(right)) return false;
|
|
75
|
+
return matchesNumericOp(operator, left, right);
|
|
76
|
+
}
|
|
77
|
+
/** True when the present string `raw` satisfies `operator` against the
|
|
78
|
+
* resolved `value` (field known to exist — MISSING is handled by the
|
|
79
|
+
* caller before this runs, and an UNRESOLVED `valueFrom` never reaches
|
|
80
|
+
* here either). */
|
|
81
|
+
function matchesPresent(operator, raw, value) {
|
|
82
|
+
switch (operator) {
|
|
83
|
+
case "eq": return raw === String(value);
|
|
84
|
+
case "ne": return raw !== String(value);
|
|
85
|
+
case "in": return Array.isArray(value) && value.includes(raw);
|
|
86
|
+
case "contains": return raw.includes(String(value));
|
|
87
|
+
case "gt":
|
|
88
|
+
case "gte":
|
|
89
|
+
case "lt":
|
|
90
|
+
case "lte": return matchesNumeric(operator, raw, value);
|
|
91
|
+
default: return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** True when `record` satisfies one condition, given `recordsById` to
|
|
95
|
+
* resolve a `valueFrom` reference. Two independent MISSING cases, checked
|
|
96
|
+
* in order:
|
|
97
|
+
* - `record[cond.field]` absent (`undefined`/`null`) → matches only `ne`
|
|
98
|
+
* (vacuously true — "not equal to X" holds when there's no value at
|
|
99
|
+
* all); every other op is false. Unchanged from the literal-`value`
|
|
100
|
+
* behaviour, regardless of whether `valueFrom` would also resolve.
|
|
101
|
+
* - the resolved comparison value is UNRESOLVED (a `valueFrom` whose
|
|
102
|
+
* target record/field doesn't exist) → false for EVERY op, including
|
|
103
|
+
* `ne` — a broken reference must never spuriously match. */
|
|
104
|
+
function matchesCond(cond, record, recordsById) {
|
|
105
|
+
const raw = record[cond.field];
|
|
106
|
+
if (isMissing(raw)) return cond.op === "ne";
|
|
107
|
+
const value = resolveValue(cond, record, recordsById);
|
|
108
|
+
if (value === void 0) return false;
|
|
109
|
+
return matchesPresent(cond.op, String(raw), value);
|
|
110
|
+
}
|
|
111
|
+
/** True when `record` satisfies every condition in `where` (AND). An empty
|
|
112
|
+
* `where` matches everything. `recordsById` — the source collection's
|
|
113
|
+
* records keyed by primaryKey — resolves any `valueFrom` reference;
|
|
114
|
+
* omitted (default `{}`) for callers with no cross-record lookups, in
|
|
115
|
+
* which case every `valueFrom` condition is UNRESOLVED and so never
|
|
116
|
+
* matches. */
|
|
117
|
+
function matchesWhere(where, record, recordsById = {}) {
|
|
118
|
+
return where.every((cond) => matchesCond(cond, record, recordsById));
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/collection/core/dynamicIcon.ts
|
|
122
|
+
/** The record with the greatest `String(record[field])` (localeCompare) —
|
|
123
|
+
* ties keep the first-seen record (stable left-to-right `reduce`). */
|
|
124
|
+
function latestByField(pool, field) {
|
|
125
|
+
return pool.reduce((latest, candidate) => String(candidate[field] ?? "").localeCompare(String(latest[field] ?? "")) > 0 ? candidate : latest);
|
|
126
|
+
}
|
|
127
|
+
/** Reduce `records` to the one record that decides the effective icon, per
|
|
128
|
+
* `source`'s `where` filter + `from` strategy:
|
|
129
|
+
* - pool = `source.where`-filtered records, or every record when unset;
|
|
130
|
+
* - an empty pool resolves to `null` (no source record → fallback);
|
|
131
|
+
* - `from: "first"` / `"when"` → the first pool record (storage order);
|
|
132
|
+
* - `from: "latest"` (default), with `orderBy` given → the pool record
|
|
133
|
+
* whose `String(record[orderBy])` sorts highest;
|
|
134
|
+
* - `from: "latest"`, with no `orderBy` → the last pool record.
|
|
135
|
+
* `recordsById` (the source collection's records keyed by primaryKey)
|
|
136
|
+
* resolves any `valueFrom` reference inside `source.where`; omitted for
|
|
137
|
+
* callers with no cross-record lookups. */
|
|
138
|
+
function selectDynamicRecord(records, source, orderBy, recordsById = {}) {
|
|
139
|
+
const { where } = source;
|
|
140
|
+
const pool = where ? records.filter((record) => matchesWhere(where, record, recordsById)) : records;
|
|
141
|
+
if (pool.length === 0) return null;
|
|
142
|
+
if (source.from === "first" || source.from === "when") return pool[0];
|
|
143
|
+
return orderBy ? latestByField(pool, orderBy) : pool[pool.length - 1];
|
|
144
|
+
}
|
|
145
|
+
/** Map a resolved source record to the effective icon: `spec.fallback`
|
|
146
|
+
* (or the collection's own static `icon`) when there's no record or no
|
|
147
|
+
* rule matches; otherwise the `icon` of the first rule whose `where`
|
|
148
|
+
* matches the record. `recordsById` resolves any `valueFrom` reference
|
|
149
|
+
* inside a rule's `where`, same as `selectDynamicRecord`. */
|
|
150
|
+
function resolveIcon(record, spec, staticIcon, recordsById = {}) {
|
|
151
|
+
const fallback = spec.fallback ?? staticIcon;
|
|
152
|
+
if (!record) return fallback;
|
|
153
|
+
const matched = spec.rules.find((rule) => matchesWhere(rule.where, record, recordsById));
|
|
154
|
+
return matched ? matched.icon : fallback;
|
|
155
|
+
}
|
|
156
|
+
var isDateLikeField = (field) => field.type === "date" || field.type === "datetime";
|
|
157
|
+
/** The first field key (declaration order) whose type is `date` or
|
|
158
|
+
* `datetime` — the default `orderBy` for `from: "latest"` when a
|
|
159
|
+
* `DynamicIconSource` doesn't name one. `undefined` when the schema has
|
|
160
|
+
* no date-like field. */
|
|
161
|
+
function firstDateField(schema) {
|
|
162
|
+
return Object.entries(schema.fields).find(([, field]) => isDateLikeField(field))?.[0];
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
41
165
|
//#region src/collection/core/derivedFormula.ts
|
|
42
166
|
function evaluateDerived(formula, ctx) {
|
|
43
167
|
let tokens;
|
|
@@ -413,17 +537,41 @@ Object.defineProperty(exports, "evaluateDerived", {
|
|
|
413
537
|
return evaluateDerived;
|
|
414
538
|
}
|
|
415
539
|
});
|
|
540
|
+
Object.defineProperty(exports, "firstDateField", {
|
|
541
|
+
enumerable: true,
|
|
542
|
+
get: function() {
|
|
543
|
+
return firstDateField;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
416
546
|
Object.defineProperty(exports, "isFieldDrivenEvery", {
|
|
417
547
|
enumerable: true,
|
|
418
548
|
get: function() {
|
|
419
549
|
return isFieldDrivenEvery;
|
|
420
550
|
}
|
|
421
551
|
});
|
|
552
|
+
Object.defineProperty(exports, "matchesWhere", {
|
|
553
|
+
enumerable: true,
|
|
554
|
+
get: function() {
|
|
555
|
+
return matchesWhere;
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
Object.defineProperty(exports, "resolveIcon", {
|
|
559
|
+
enumerable: true,
|
|
560
|
+
get: function() {
|
|
561
|
+
return resolveIcon;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
422
564
|
Object.defineProperty(exports, "resolveRowRefs", {
|
|
423
565
|
enumerable: true,
|
|
424
566
|
get: function() {
|
|
425
567
|
return resolveRowRefs;
|
|
426
568
|
}
|
|
427
569
|
});
|
|
570
|
+
Object.defineProperty(exports, "selectDynamicRecord", {
|
|
571
|
+
enumerable: true,
|
|
572
|
+
get: function() {
|
|
573
|
+
return selectDynamicRecord;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
428
576
|
|
|
429
|
-
//# sourceMappingURL=deriveAll-
|
|
577
|
+
//# sourceMappingURL=deriveAll-BQ_p5HSB.cjs.map
|