things-api 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/dist/audit/log.js +56 -2
- package/dist/audit/log.js.map +1 -1
- package/dist/audit/schema.d.ts +26 -8
- package/dist/audit/schema.js +42 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/commands/area.d.ts +34 -2
- package/dist/cli/commands/area.js +140 -28
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -1
- package/dist/cli/commands/doctor.js +44 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/mcp.js +23 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/project.d.ts +23 -1
- package/dist/cli/commands/project.js +82 -15
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.d.ts +6 -125
- package/dist/cli/commands/reads.js +649 -657
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.d.ts +20 -0
- package/dist/cli/commands/repeat-flags.js +76 -0
- package/dist/cli/commands/repeat-flags.js.map +1 -0
- package/dist/cli/commands/setup.js +2 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/show.d.ts +1 -1
- package/dist/cli/commands/show.js +169 -18
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/snapshot.js +1 -1
- package/dist/cli/commands/snapshot.js.map +1 -1
- package/dist/cli/commands/todo.d.ts +1 -1
- package/dist/cli/commands/todo.js +21 -9
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +378 -148
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/did-you-mean.d.ts +29 -0
- package/dist/cli/did-you-mean.js +53 -0
- package/dist/cli/did-you-mean.js.map +1 -0
- package/dist/cli/excess-args.d.ts +15 -0
- package/dist/cli/excess-args.js +51 -0
- package/dist/cli/excess-args.js.map +1 -0
- package/dist/cli/glyphs.d.ts +67 -7
- package/dist/cli/glyphs.js +202 -19
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +54 -0
- package/dist/cli/help.js +355 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/main.js +36 -21
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/period.d.ts +39 -0
- package/dist/cli/period.js +141 -0
- package/dist/cli/period.js.map +1 -0
- package/dist/cli/read-driver.d.ts +83 -0
- package/dist/cli/read-driver.js +215 -0
- package/dist/cli/read-driver.js.map +1 -0
- package/dist/cli/render.d.ts +246 -0
- package/dist/cli/render.js +806 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/resolve-invocation.d.ts +116 -0
- package/dist/cli/resolve-invocation.js +261 -0
- package/dist/cli/resolve-invocation.js.map +1 -0
- package/dist/cli/shell-quote.d.ts +9 -0
- package/dist/cli/shell-quote.js +12 -0
- package/dist/cli/shell-quote.js.map +1 -0
- package/dist/cli/tag-filters.d.ts +59 -0
- package/dist/cli/tag-filters.js +57 -0
- package/dist/cli/tag-filters.js.map +1 -0
- package/dist/cli/verb-hint.d.ts +25 -0
- package/dist/cli/verb-hint.js +138 -0
- package/dist/cli/verb-hint.js.map +1 -0
- package/dist/cli/width.d.ts +135 -0
- package/dist/cli/width.js +313 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/client.d.ts +146 -24
- package/dist/client.js +106 -16
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +136 -1
- package/dist/contracts.js +44 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/fingerprint.d.ts +12 -0
- package/dist/db/fingerprint.js +15 -1
- package/dist/db/fingerprint.js.map +1 -1
- package/dist/db/locate.js +1 -1
- package/dist/db/locate.js.map +1 -1
- package/dist/diagnose.d.ts +63 -0
- package/dist/diagnose.js +38 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -1
- package/dist/mcp/server.js +850 -128
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +40 -7
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.d.ts +7 -0
- package/dist/model/mappers.js +20 -3
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/occurrences.d.ts +6 -1
- package/dist/model/occurrences.js +9 -5
- package/dist/model/occurrences.js.map +1 -1
- package/dist/model/recurrence.d.ts +17 -8
- package/dist/model/recurrence.js.map +1 -1
- package/dist/model/serialize.d.ts +28 -0
- package/dist/model/serialize.js +80 -0
- package/dist/model/serialize.js.map +1 -0
- package/dist/model/when-sugar.d.ts +47 -0
- package/dist/model/when-sugar.js +45 -0
- package/dist/model/when-sugar.js.map +1 -0
- package/dist/read/area-view.d.ts +8 -1
- package/dist/read/area-view.js +49 -13
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/caps.d.ts +14 -0
- package/dist/read/caps.js +21 -0
- package/dist/read/caps.js.map +1 -0
- package/dist/read/filter-contract.d.ts +117 -0
- package/dist/read/filter-contract.js +78 -0
- package/dist/read/filter-contract.js.map +1 -0
- package/dist/read/log-boundary.d.ts +9 -4
- package/dist/read/log-boundary.js.map +1 -1
- package/dist/read/predicates.d.ts +61 -0
- package/dist/read/predicates.js +78 -0
- package/dist/read/predicates.js.map +1 -0
- package/dist/read/project-view.d.ts +12 -1
- package/dist/read/project-view.js +39 -8
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/queries.d.ts +156 -8
- package/dist/read/queries.js +261 -38
- package/dist/read/queries.js.map +1 -1
- package/dist/read/search-rank.d.ts +36 -0
- package/dist/read/search-rank.js +31 -0
- package/dist/read/search-rank.js.map +1 -0
- package/dist/read/sections.d.ts +51 -0
- package/dist/read/sections.js +37 -0
- package/dist/read/sections.js.map +1 -0
- package/dist/read/show-target.d.ts +13 -4
- package/dist/read/show-target.js +11 -2
- package/dist/read/show-target.js.map +1 -1
- package/dist/read/sidebar-order.d.ts +19 -0
- package/dist/read/sidebar-order.js +93 -0
- package/dist/read/sidebar-order.js.map +1 -0
- package/dist/read/tags.d.ts +27 -3
- package/dist/read/tags.js +83 -12
- package/dist/read/tags.js.map +1 -1
- package/dist/read/truncation.d.ts +71 -0
- package/dist/read/truncation.js +217 -0
- package/dist/read/truncation.js.map +1 -0
- package/dist/read/views.d.ts +177 -19
- package/dist/read/views.js +390 -213
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +42 -0
- package/dist/surface-copy.js +55 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/sync-health.d.ts +78 -0
- package/dist/sync-health.js +312 -0
- package/dist/sync-health.js.map +1 -0
- package/dist/write/accessibility-probe.d.ts +12 -0
- package/dist/write/accessibility-probe.js +63 -0
- package/dist/write/accessibility-probe.js.map +1 -0
- package/dist/write/automation-probe.d.ts +7 -0
- package/dist/write/automation-probe.js +8 -1
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/batch.js +4 -2
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +8 -0
- package/dist/write/capabilities.js +13 -5
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/commands.d.ts +2 -0
- package/dist/write/commands.js +562 -48
- package/dist/write/commands.js.map +1 -1
- package/dist/write/edit-checklist.js +3 -2
- package/dist/write/edit-checklist.js.map +1 -1
- package/dist/write/guards.d.ts +2 -7
- package/dist/write/guards.js +64 -6
- package/dist/write/guards.js.map +1 -1
- package/dist/write/heading.js +2 -0
- package/dist/write/heading.js.map +1 -1
- package/dist/write/lock.d.ts +40 -2
- package/dist/write/lock.js +91 -14
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +4 -0
- package/dist/write/make-repeating-project.js +253 -0
- package/dist/write/make-repeating-project.js.map +1 -0
- package/dist/write/operations.d.ts +145 -1
- package/dist/write/operations.js +48 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +19 -0
- package/dist/write/pipeline.js +109 -26
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +68 -3
- package/dist/write/pre-state.js +102 -3
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reopen.js +3 -2
- package/dist/write/reopen.js.map +1 -1
- package/dist/write/reorder.js +27 -17
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-rule.d.ts +22 -0
- package/dist/write/repeat-rule.js +258 -0
- package/dist/write/repeat-rule.js.map +1 -0
- package/dist/write/reversibility.js +49 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/tag-refs.d.ts +47 -0
- package/dist/write/tag-refs.js +126 -0
- package/dist/write/tag-refs.js.map +1 -0
- package/dist/write/undo.d.ts +74 -5
- package/dist/write/undo.js +494 -80
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +17 -1
- package/dist/write/vectors/registry.js +18 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/types.d.ts +119 -3
- package/dist/write/vectors/ui-certification.d.ts +48 -0
- package/dist/write/vectors/ui-certification.js +46 -0
- package/dist/write/vectors/ui-certification.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +188 -0
- package/dist/write/vectors/ui-drag.js +1095 -0
- package/dist/write/vectors/ui-drag.js.map +1 -0
- package/dist/write/vectors/ui-recipes.d.ts +70 -0
- package/dist/write/vectors/ui-recipes.js +558 -0
- package/dist/write/vectors/ui-recipes.js.map +1 -0
- package/dist/write/vectors/ui.d.ts +137 -0
- package/dist/write/vectors/ui.js +656 -0
- package/dist/write/vectors/ui.js.map +1 -0
- package/dist/write/verify/delta.d.ts +31 -3
- package/dist/write/verify/delta.js +22 -6
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.js +1 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/exit-codes.d.ts +0 -26
- package/dist/cli/exit-codes.js +0 -26
- package/dist/cli/exit-codes.js.map +0 -1
- package/dist/cli/output.d.ts +0 -42
- package/dist/cli/output.js +0 -16
- package/dist/cli/output.js.map +0 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emit-time omit-empty transform for entity/data payloads (the "Omit-empty"
|
|
3
|
+
* contract in docs/design/contracts.md). Applied at the JSON emit boundary of
|
|
4
|
+
* the read surfaces ONLY — the CLI `--json` read envelope (src/cli/read-driver.ts)
|
|
5
|
+
* and the MCP read tool results (src/mcp/server.ts) — never to the in-memory
|
|
6
|
+
* entities the library returns, and never to the error envelope.
|
|
7
|
+
*
|
|
8
|
+
* The rule: an ENTITY object omits any optional field whose value is empty —
|
|
9
|
+
* null, undefined, "" (empty string), or [] (empty array). A consumer MUST
|
|
10
|
+
* read an absent key as unset/empty/default and MUST NOT distinguish absent
|
|
11
|
+
* from empty. Values that are semantically meaningful when "empty" are kept:
|
|
12
|
+
* booleans (a real `false`) and numbers (a `0` count) are never omitted, and
|
|
13
|
+
* the identity keys ({@link IDENTITY_KEYS}) are always present.
|
|
14
|
+
*
|
|
15
|
+
* Why entity-scoped, not a blanket deep prune: the same key means opposite
|
|
16
|
+
* things on different shapes. A to-do's `area: null` is "no area" (omit), but a
|
|
17
|
+
* sidebar section's `area: null` is the load-bearing "top-level/loose block"
|
|
18
|
+
* discriminant (keep). So pruning is gated to the recognized entity shapes;
|
|
19
|
+
* the view scaffolding that CARRIES entities (today/evening split, project/area
|
|
20
|
+
* card sections, grouped blocks) is structural and passes through untouched —
|
|
21
|
+
* only its nested entities are pruned.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Keys never omitted from an entity even when empty: the identity a consumer
|
|
25
|
+
* always keys on. `uuid`/`type` are never empty in practice; `title`/`name`
|
|
26
|
+
* can be "" (an untitled to-do) and MUST still be present.
|
|
27
|
+
*/
|
|
28
|
+
const IDENTITY_KEYS = new Set(["uuid", "type", "title", "name"]);
|
|
29
|
+
/** A to-do/project/heading row: uuid + a task-type discriminant. */
|
|
30
|
+
function isTaskEntity(o) {
|
|
31
|
+
return (typeof o["uuid"] === "string" &&
|
|
32
|
+
(o["type"] === "to-do" || o["type"] === "project" || o["type"] === "heading"));
|
|
33
|
+
}
|
|
34
|
+
/** An area: uuid + the `visible` flag (distinguishes it from a bare Ref). */
|
|
35
|
+
function isAreaEntity(o) {
|
|
36
|
+
return typeof o["uuid"] === "string" && typeof o["visible"] === "boolean";
|
|
37
|
+
}
|
|
38
|
+
/** A taxonomy tag row (uuid-free): a title plus the parent/shortcut columns. */
|
|
39
|
+
function isTagEntity(o) {
|
|
40
|
+
return (o["uuid"] === undefined && typeof o["title"] === "string" && "parent" in o && "shortcut" in o);
|
|
41
|
+
}
|
|
42
|
+
/** Whether this object's empty optional fields should be omitted. */
|
|
43
|
+
function prunesEmpties(o) {
|
|
44
|
+
return isTaskEntity(o) || isAreaEntity(o) || isTagEntity(o);
|
|
45
|
+
}
|
|
46
|
+
/** null | undefined | "" | [] — the values an entity omits the key for. */
|
|
47
|
+
function isEmpty(v) {
|
|
48
|
+
return v === null || v === undefined || v === "" || (Array.isArray(v) && v.length === 0);
|
|
49
|
+
}
|
|
50
|
+
function prune(value) {
|
|
51
|
+
if (value === null || value === undefined)
|
|
52
|
+
return value;
|
|
53
|
+
// Dates serialize to ISO strings via JSON.stringify — never recurse into them.
|
|
54
|
+
if (value instanceof Date)
|
|
55
|
+
return value;
|
|
56
|
+
if (Array.isArray(value))
|
|
57
|
+
return value.map(prune);
|
|
58
|
+
if (typeof value === "object") {
|
|
59
|
+
const o = value;
|
|
60
|
+
const omitEmpties = prunesEmpties(o);
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const key of Object.keys(o)) {
|
|
63
|
+
const v = o[key];
|
|
64
|
+
if (omitEmpties && !IDENTITY_KEYS.has(key) && isEmpty(v))
|
|
65
|
+
continue;
|
|
66
|
+
out[key] = prune(v);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Return a deep copy of `payload` with every entity's empty optional fields
|
|
74
|
+
* omitted (the "Omit-empty" contract). Pure — the input is never mutated, so
|
|
75
|
+
* the human-render path keeps the full entity beside the JSON emit.
|
|
76
|
+
*/
|
|
77
|
+
export function omitEmpty(payload) {
|
|
78
|
+
return prune(payload);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/model/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;GAIG;AACH,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAItF,oEAAoE;AACpE,SAAS,YAAY,CAAC,CAAM;IAC1B,OAAO,CACL,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ;QAC7B,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAC,CAAM;IAC1B,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,SAAS,CAAC;AAC5E,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CAAC,CAAM;IACzB,OAAO,CACL,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,CAC9F,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,SAAS,aAAa,CAAC,CAAM;IAC3B,OAAO,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,SAAS,OAAO,CAAC,CAAU;IACzB,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,KAAY,CAAC;QACvB,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,GAAG,GAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,WAAW,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;gBAAE,SAAS;YACnE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAI,OAAU;IACrC,OAAO,KAAK,CAAC,OAAO,CAAM,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<when>@<time>` scheduling sugar, shared by every consumer surface. A
|
|
3
|
+
* single string like `2026-07-05@09:00` (or `today@evening`-style keyword +
|
|
4
|
+
* time) carries BOTH the schedule and its reminder; this splits it into the
|
|
5
|
+
* separate `when` + `reminder` the write pipeline expects, or reports the one
|
|
6
|
+
* usage error. Extracted from the CLI so the CLI and MCP validate the suffix
|
|
7
|
+
* identically instead of each re-deriving it — see docs/design/architecture.md
|
|
8
|
+
* (Consumer boundary).
|
|
9
|
+
*
|
|
10
|
+
* Pure: it neither mutates its input nor validates the DATE/TIME values (the
|
|
11
|
+
* pipeline does that downstream) — it only owns the `@` split and the two
|
|
12
|
+
* usage messages. The flag/parameter LABELS are injected so each surface keeps
|
|
13
|
+
* its own vocabulary (`--when`/`--reminder` vs `when`/`reminder`).
|
|
14
|
+
*/
|
|
15
|
+
/** The surface's spelling of the two parameters, interpolated into usage copy. */
|
|
16
|
+
export interface WhenSugarLabels {
|
|
17
|
+
when: string;
|
|
18
|
+
reminder: string;
|
|
19
|
+
}
|
|
20
|
+
/** CLI flag spellings — the default, since the sugar originated there. */
|
|
21
|
+
export declare const CLI_WHEN_LABELS: WhenSugarLabels;
|
|
22
|
+
/** MCP tool-parameter spellings. */
|
|
23
|
+
export declare const MCP_WHEN_LABELS: WhenSugarLabels;
|
|
24
|
+
/**
|
|
25
|
+
* The result of inspecting a `when` value for the `@time` suffix:
|
|
26
|
+
* - `unchanged` — no suffix; pass `when` through as-is.
|
|
27
|
+
* - `split` — a valid suffix; use the split `when` + `reminder`.
|
|
28
|
+
* - `error` — a malformed suffix, or a suffix given ALONGSIDE a separate
|
|
29
|
+
* reminder; emit `message` as a usage error.
|
|
30
|
+
*/
|
|
31
|
+
export type WhenSugar = {
|
|
32
|
+
kind: "unchanged";
|
|
33
|
+
} | {
|
|
34
|
+
kind: "split";
|
|
35
|
+
when: string;
|
|
36
|
+
reminder: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "error";
|
|
39
|
+
message: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Split a `when` value on its `@time` suffix. `reminderProvided` reflects
|
|
43
|
+
* whether the caller ALSO passed a separate reminder — a suffix plus an
|
|
44
|
+
* explicit reminder is ambiguous and rejected. A non-string or suffix-free
|
|
45
|
+
* `when` is `unchanged`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function splitWhenSugar(when: unknown, reminderProvided: boolean, labels?: WhenSugarLabels): WhenSugar;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<when>@<time>` scheduling sugar, shared by every consumer surface. A
|
|
3
|
+
* single string like `2026-07-05@09:00` (or `today@evening`-style keyword +
|
|
4
|
+
* time) carries BOTH the schedule and its reminder; this splits it into the
|
|
5
|
+
* separate `when` + `reminder` the write pipeline expects, or reports the one
|
|
6
|
+
* usage error. Extracted from the CLI so the CLI and MCP validate the suffix
|
|
7
|
+
* identically instead of each re-deriving it — see docs/design/architecture.md
|
|
8
|
+
* (Consumer boundary).
|
|
9
|
+
*
|
|
10
|
+
* Pure: it neither mutates its input nor validates the DATE/TIME values (the
|
|
11
|
+
* pipeline does that downstream) — it only owns the `@` split and the two
|
|
12
|
+
* usage messages. The flag/parameter LABELS are injected so each surface keeps
|
|
13
|
+
* its own vocabulary (`--when`/`--reminder` vs `when`/`reminder`).
|
|
14
|
+
*/
|
|
15
|
+
/** CLI flag spellings — the default, since the sugar originated there. */
|
|
16
|
+
export const CLI_WHEN_LABELS = { when: "--when", reminder: "--reminder" };
|
|
17
|
+
/** MCP tool-parameter spellings. */
|
|
18
|
+
export const MCP_WHEN_LABELS = { when: "when", reminder: "reminder" };
|
|
19
|
+
/**
|
|
20
|
+
* Split a `when` value on its `@time` suffix. `reminderProvided` reflects
|
|
21
|
+
* whether the caller ALSO passed a separate reminder — a suffix plus an
|
|
22
|
+
* explicit reminder is ambiguous and rejected. A non-string or suffix-free
|
|
23
|
+
* `when` is `unchanged`.
|
|
24
|
+
*/
|
|
25
|
+
export function splitWhenSugar(when, reminderProvided, labels = CLI_WHEN_LABELS) {
|
|
26
|
+
if (typeof when !== "string" || !when.includes("@"))
|
|
27
|
+
return { kind: "unchanged" };
|
|
28
|
+
const at = when.indexOf("@");
|
|
29
|
+
const date = when.slice(0, at);
|
|
30
|
+
const time = when.slice(at + 1);
|
|
31
|
+
if (date === "" || time === "" || time.includes("@")) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "error",
|
|
34
|
+
message: `invalid ${labels.when} "${when}" — expected today | evening | anytime | someday | YYYY-MM-DD (set a reminder with ${labels.reminder} HH:mm)`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (reminderProvided) {
|
|
38
|
+
return {
|
|
39
|
+
kind: "error",
|
|
40
|
+
message: `${labels.when} "${when}" carries an @time suffix and ${labels.reminder} was also given — use one`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { kind: "split", when: date, reminder: time };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=when-sugar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when-sugar.js","sourceRoot":"","sources":["../../src/model/when-sugar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAoB,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;AAE3F,oCAAoC;AACpC,MAAM,CAAC,MAAM,eAAe,GAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAcvF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAa,EACb,gBAAyB,EACzB,SAA0B,eAAe;IAEzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAClF,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAChC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,WAAW,MAAM,CAAC,IAAI,KAAK,IAAI,sFAAsF,MAAM,CAAC,QAAQ,SAAS;SACvJ,CAAC;IACJ,CAAC;IACD,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,KAAK,IAAI,iCAAiC,MAAM,CAAC,QAAQ,2BAA2B;SAC5G,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC"}
|
package/dist/read/area-view.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { DatabaseSync } from "node:sqlite";
|
|
8
8
|
import type { Area, IsoDateGroup, Project, Todo } from "../model/entities.ts";
|
|
9
|
+
import { type ViewFilter } from "./views.ts";
|
|
9
10
|
export interface AreaView {
|
|
10
11
|
area: Area;
|
|
11
12
|
/** Open, unscheduled/current direct to-dos, by index. */
|
|
@@ -22,5 +23,11 @@ export interface AreaView {
|
|
|
22
23
|
logged: Todo[];
|
|
23
24
|
trashed: Todo[];
|
|
24
25
|
}
|
|
26
|
+
/** A someday project row: open, incubated, no start date. */
|
|
27
|
+
export declare const isSomedayProjectRow: (p: Project) => boolean;
|
|
28
|
+
/** A future-scheduled project row (start date strictly after `todayIso`). */
|
|
29
|
+
export declare const isScheduledProjectRow: (p: Project, todayIso: string) => boolean;
|
|
30
|
+
/** An active project row — everything that is neither someday nor future-scheduled. */
|
|
31
|
+
export declare const isActiveProjectRow: (p: Project, todayIso: string) => boolean;
|
|
25
32
|
/** Resolves by uuid or unique (case-insensitive) title; throws like the ref resolvers. */
|
|
26
|
-
export declare function areaView(db: DatabaseSync, ref: string, now?: Date): AreaView;
|
|
33
|
+
export declare function areaView(db: DatabaseSync, ref: string, now?: Date, filter?: ViewFilter): AreaView;
|
package/dist/read/area-view.js
CHANGED
|
@@ -2,9 +2,25 @@ import { encodePackedDate, localToday, decodePackedDate } from "../model/dates.j
|
|
|
2
2
|
import { mapProject, mapTodo } from "../model/mappers.js";
|
|
3
3
|
import { fetchTagsForTasks, fetchTaskRows, makeRefResolver, resolveAreaUuid } from "./queries.js";
|
|
4
4
|
import { logBoundary, markLogged } from "./log-boundary.js";
|
|
5
|
+
import { OVERDUE } from "./predicates.js";
|
|
5
6
|
import { areaTags } from "./tags.js";
|
|
7
|
+
import { tagFilter } from "./views.js";
|
|
8
|
+
// The area card splits its open projects into three display buckets by their
|
|
9
|
+
// own schedule (the card mirrors the GUI: active projects as rows, scheduled
|
|
10
|
+
// ones under Upcoming, someday ones under Someday). Closed-but-unswept projects
|
|
11
|
+
// stay "active" (checked in place) — start/startDate only classify OPEN rows.
|
|
12
|
+
// Shared by the bounding layer (capAreaSections keeps scheduled/someday whole
|
|
13
|
+
// while capping the active rows) and the human renderer, so the split is
|
|
14
|
+
// defined ONCE and the two never drift.
|
|
15
|
+
/** A someday project row: open, incubated, no start date. */
|
|
16
|
+
export const isSomedayProjectRow = (p) => p.status === "open" && p.start === "someday" && p.startDate === null;
|
|
17
|
+
/** A future-scheduled project row (start date strictly after `todayIso`). */
|
|
18
|
+
export const isScheduledProjectRow = (p, todayIso) => p.status === "open" && p.startDate !== null && p.startDate > todayIso;
|
|
19
|
+
/** An active project row — everything that is neither someday nor future-scheduled. */
|
|
20
|
+
export const isActiveProjectRow = (p, todayIso) => !isSomedayProjectRow(p) && !isScheduledProjectRow(p, todayIso);
|
|
6
21
|
/** Resolves by uuid or unique (case-insensitive) title; throws like the ref resolvers. */
|
|
7
|
-
export function areaView(db, ref, now) {
|
|
22
|
+
export function areaView(db, ref, now, filter = {}) {
|
|
23
|
+
const overdue = filter.overdue === true;
|
|
8
24
|
const uuid = resolveAreaUuid(db, ref);
|
|
9
25
|
const row = db
|
|
10
26
|
.prepare(`SELECT uuid, title, visible, "index" FROM TMArea WHERE uuid = ?`)
|
|
@@ -15,18 +31,38 @@ export function areaView(db, ref, now) {
|
|
|
15
31
|
uuid: row.uuid,
|
|
16
32
|
title: row.title ?? "",
|
|
17
33
|
visible: row.visible !== 0,
|
|
18
|
-
tags
|
|
34
|
+
// Area tags by NAME only (tag uuids are internal).
|
|
35
|
+
tags: areaTags(db, uuid).map((t) => ({ title: t.title })),
|
|
19
36
|
};
|
|
20
37
|
const refs = makeRefResolver(db);
|
|
21
38
|
const tagsOf = (rows) => fetchTagsForTasks(db, rows.map((r) => r.uuid));
|
|
22
39
|
const boundary = logBoundary(db, now);
|
|
40
|
+
// OWN-DEADLINE UNIFORM: `--overdue` filters BOTH displayed row kinds by each
|
|
41
|
+
// row's OWN deadline (open, strictly before today) via the shared OVERDUE
|
|
42
|
+
// predicate — the area's child PROJECTS by the project's own deadline AND the
|
|
43
|
+
// loose to-dos by theirs. There is NO descent into project contents (an
|
|
44
|
+
// overdue to-do inside a non-overdue project never surfaces here — that is
|
|
45
|
+
// `project show --overdue`). Sections with no surviving rows collapse.
|
|
46
|
+
const overdueSql = overdue ? ` AND ${OVERDUE}` : "";
|
|
47
|
+
const overdueBinds = overdue ? [encodePackedDate(localToday(now))] : [];
|
|
48
|
+
// Tag scope (§9a): BOTH displayed row kinds — the child PROJECTS and the
|
|
49
|
+
// loose to-dos — are filtered by a tag carried DIRECTLY on the row (the
|
|
50
|
+
// container semantics). The area's own tags are inherited by every row it
|
|
51
|
+
// holds, so an inheritance-inclusive `--tag` would be vacuous; suppressing the
|
|
52
|
+
// container hop makes `--tag` mean "rows with this tag on themselves" (still
|
|
53
|
+
// descendant-expanded) and `--untagged` "rows with no direct tag". Computed
|
|
54
|
+
// once, spliced into both queries; NO descent into project contents (a child
|
|
55
|
+
// of a matching project is never inspected here). The tag binds trail each
|
|
56
|
+
// query's own binds.
|
|
57
|
+
const tf = tagFilter(db, filter, { container: true });
|
|
23
58
|
// Open projects PLUS closed ones the log-move sweep has not passed — the
|
|
24
|
-
// GUI keeps those checked in place (completion ≠ logged).
|
|
59
|
+
// GUI keeps those checked in place (completion ≠ logged). Under --overdue the
|
|
60
|
+
// OVERDUE predicate's own `status = 0` narrows this to open overdue projects.
|
|
25
61
|
const projectRows = fetchTaskRows(db, `t.type = 1 AND t.area = ? AND t.trashed = 0
|
|
26
|
-
AND (t.status = 0 OR t.stopDate > ?) ORDER BY t."index" ASC`, [uuid, boundary.getTime() / 1000]);
|
|
62
|
+
AND (t.status = 0 OR t.stopDate > ?)${overdueSql}${tf.sql} ORDER BY t."index" ASC`, [uuid, boundary.getTime() / 1000, ...overdueBinds, ...tf.binds]);
|
|
27
63
|
const projectTags = tagsOf(projectRows);
|
|
28
64
|
const projects = markLogged(projectRows.map((r) => mapProject(r, refs, projectTags.get(r.uuid) ?? [])), boundary);
|
|
29
|
-
const todoRows = fetchTaskRows(db, `t.type = 0 AND t.area =
|
|
65
|
+
const todoRows = fetchTaskRows(db, `t.type = 0 AND t.area = ?${overdueSql}${tf.sql} ORDER BY t."index" ASC`, [uuid, ...overdueBinds, ...tf.binds]);
|
|
30
66
|
const todoTags = tagsOf(todoRows);
|
|
31
67
|
const todos = todoRows.map((r) => ({
|
|
32
68
|
row: r,
|
|
@@ -40,8 +76,8 @@ export function areaView(db, ref, now) {
|
|
|
40
76
|
const someday = [];
|
|
41
77
|
const logged = [];
|
|
42
78
|
const trashed = [];
|
|
43
|
-
for (const { row, todo } of todos) {
|
|
44
|
-
if (
|
|
79
|
+
for (const { row: todoRow, todo } of todos) {
|
|
80
|
+
if (todoRow.trashed === 1) {
|
|
45
81
|
trashed.push(todo);
|
|
46
82
|
continue;
|
|
47
83
|
}
|
|
@@ -49,7 +85,7 @@ export function areaView(db, ref, now) {
|
|
|
49
85
|
repeating.push(todo);
|
|
50
86
|
continue;
|
|
51
87
|
}
|
|
52
|
-
if (
|
|
88
|
+
if (todoRow.status !== 0) {
|
|
53
89
|
// Completion ≠ logged: closed-but-unswept items stay checked in the
|
|
54
90
|
// active block, like the GUI.
|
|
55
91
|
if (todo.logged)
|
|
@@ -58,14 +94,14 @@ export function areaView(db, ref, now) {
|
|
|
58
94
|
active.push(todo);
|
|
59
95
|
continue;
|
|
60
96
|
}
|
|
61
|
-
if (
|
|
97
|
+
if (todoRow.start === 2 && todoRow.startDate === null) {
|
|
62
98
|
someday.push(todo);
|
|
63
99
|
continue;
|
|
64
100
|
}
|
|
65
|
-
if (
|
|
101
|
+
if (todoRow.startDate !== null && todoRow.startDate > packedToday) {
|
|
66
102
|
scheduledRows.push({
|
|
67
|
-
date: decodePackedDate(
|
|
68
|
-
ti:
|
|
103
|
+
date: decodePackedDate(todoRow.startDate) ?? "",
|
|
104
|
+
ti: todoRow.todayIndex ?? 0,
|
|
69
105
|
todo,
|
|
70
106
|
});
|
|
71
107
|
continue;
|
|
@@ -75,7 +111,7 @@ export function areaView(db, ref, now) {
|
|
|
75
111
|
logged.sort((a, b) => (b.stopped?.getTime() ?? 0) - (a.stopped?.getTime() ?? 0));
|
|
76
112
|
const scheduled = [];
|
|
77
113
|
// Within a day the UI sorts by todayIndex ASC (Upcoming drag order).
|
|
78
|
-
for (const { date, todo } of scheduledRows.
|
|
114
|
+
for (const { date, todo } of scheduledRows.toSorted((a, b) => a.date.localeCompare(b.date) || a.ti - b.ti)) {
|
|
79
115
|
const last = scheduled[scheduled.length - 1];
|
|
80
116
|
if (last && last.date === date)
|
|
81
117
|
last.items.push(todo);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"area-view.js","sourceRoot":"","sources":["../../src/read/area-view.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAgB,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"area-view.js","sourceRoot":"","sources":["../../src/read/area-view.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one shared decision behind every surface's `--limit`/`--all` (CLI) and
|
|
3
|
+
* `limit`/`all` (MCP) cap resolution: given an explicit value, the `all`
|
|
4
|
+
* flag, and a default, decide the effective row cap. Each surface keeps its
|
|
5
|
+
* own input parsing and error emission (the CLI validates the string and
|
|
6
|
+
* writes a usage error; MCP validates via zod and returns a tool error) —
|
|
7
|
+
* only this pure conflict/default logic is shared.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one cap against `all` into a row cap: `null` = every row,
|
|
11
|
+
* `"conflict"` when an explicit value combines with `all: true`, otherwise
|
|
12
|
+
* the explicit value or the default.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveCap(value: number | undefined, all: boolean | undefined, defaultLimit: number): number | null | "conflict";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one shared decision behind every surface's `--limit`/`--all` (CLI) and
|
|
3
|
+
* `limit`/`all` (MCP) cap resolution: given an explicit value, the `all`
|
|
4
|
+
* flag, and a default, decide the effective row cap. Each surface keeps its
|
|
5
|
+
* own input parsing and error emission (the CLI validates the string and
|
|
6
|
+
* writes a usage error; MCP validates via zod and returns a tool error) —
|
|
7
|
+
* only this pure conflict/default logic is shared.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one cap against `all` into a row cap: `null` = every row,
|
|
11
|
+
* `"conflict"` when an explicit value combines with `all: true`, otherwise
|
|
12
|
+
* the explicit value or the default.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveCap(value, all, defaultLimit) {
|
|
15
|
+
if (all === true && value !== undefined)
|
|
16
|
+
return "conflict";
|
|
17
|
+
if (all === true)
|
|
18
|
+
return null;
|
|
19
|
+
return value ?? defaultLimit;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=caps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caps.js","sourceRoot":"","sources":["../../src/read/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,KAAyB,EACzB,GAAwB,EACxB,YAAoB;IAEpB,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC3D,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,KAAK,IAAI,YAAY,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-view filter contract — the ONE declarative statement of which read
|
|
3
|
+
* filters each view honors, plus the shared predicates that enforce them. Both
|
|
4
|
+
* consumer surfaces (CLI commands, MCP tools) derive their per-view guards from
|
|
5
|
+
* this table instead of hand-coding view lists, so a filter's applicability is
|
|
6
|
+
* stated once and can never drift between surfaces.
|
|
7
|
+
*
|
|
8
|
+
* The table says, per view: whether the `overdue` content scope applies; what
|
|
9
|
+
* tag semantics the view carries (inheritance-inclusive `inherited`, container-
|
|
10
|
+
* direct `direct`, or `rejected` where the view has no per-row tags to filter);
|
|
11
|
+
* the bound model the view truncates under (`flat` row cap, `grouped` per-block
|
|
12
|
+
* caps, or `none`); and whether the view offers the status-widening flags
|
|
13
|
+
* (search's logged/trashed/all) that `overdue` refuses to combine with.
|
|
14
|
+
*
|
|
15
|
+
* The exact usage-error COPY stays surface-owned (consumer voice differs per
|
|
16
|
+
* surface — CLI `--tag`, MCP `tag`; docs/design/surface-copy.md): a surface
|
|
17
|
+
* hands {@link validateViewArgs} its own phrasing via {@link FilterVocab}, and
|
|
18
|
+
* this module owns only the DECISION of which message applies.
|
|
19
|
+
*/
|
|
20
|
+
import type { ViewFilter } from "./views.ts";
|
|
21
|
+
/** Every read view whose filter applicability the contract governs. */
|
|
22
|
+
export type ViewName = "today" | "inbox" | "anytime" | "someday" | "upcoming" | "logbook" | "trash" | "search" | "changes" | "projects" | "areas" | "project-show" | "area-show";
|
|
23
|
+
/**
|
|
24
|
+
* How a view's tag filter behaves:
|
|
25
|
+
* - `inherited` — FLAT views: a `--tag` matches direct OR container-inherited
|
|
26
|
+
* membership (plus hierarchy descendants).
|
|
27
|
+
* - `direct` — CONTAINER views (`project show`, `area show`): a `--tag` matches
|
|
28
|
+
* a tag carried DIRECTLY on the row (the container's own inherited tags are
|
|
29
|
+
* suppressed, since every child inherits them).
|
|
30
|
+
* - `rejected` — the view has no per-row tag list to filter (an area list row,
|
|
31
|
+
* a tag list row, the changes feed); tag flags do not apply.
|
|
32
|
+
*/
|
|
33
|
+
export type TagSemantics = "inherited" | "direct" | "rejected";
|
|
34
|
+
/** The truncation model a view bounds under. */
|
|
35
|
+
export type BoundModel = "flat" | "grouped" | "none";
|
|
36
|
+
/** One row of {@link FILTER_CONTRACT}: a single view's filter applicability. */
|
|
37
|
+
export interface ViewFilterSpec {
|
|
38
|
+
/** Whether the `overdue` content scope (open, past-deadline) applies. */
|
|
39
|
+
readonly overdue: boolean;
|
|
40
|
+
/** The view's tag-filter semantics. */
|
|
41
|
+
readonly tag: TagSemantics;
|
|
42
|
+
/** The bound model the view truncates under. */
|
|
43
|
+
readonly bound: BoundModel;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the view offers the status-widening flags (logged/trashed/all).
|
|
46
|
+
* Only `search` does; `overdue` refuses to combine with them there.
|
|
47
|
+
*/
|
|
48
|
+
readonly statusWidening: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The single source of truth for read-filter applicability, one row per view.
|
|
52
|
+
* Consumed by both surfaces' per-view guards (CLI command actions, MCP tool
|
|
53
|
+
* handlers) so no surface hand-maintains its own view list.
|
|
54
|
+
*/
|
|
55
|
+
export declare const FILTER_CONTRACT: Record<ViewName, ViewFilterSpec>;
|
|
56
|
+
/**
|
|
57
|
+
* The three universal tag-filter inputs in their canonical (CLI-flag) spelling.
|
|
58
|
+
* The CLI's parsed options match this verbatim; the MCP surface maps its
|
|
59
|
+
* snake_case inputs (`exact_tag`) onto it before calling the shared predicates.
|
|
60
|
+
*/
|
|
61
|
+
export interface TagPresence {
|
|
62
|
+
tag?: readonly string[] | undefined;
|
|
63
|
+
exactTag?: boolean | undefined;
|
|
64
|
+
untagged?: boolean | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
|
|
67
|
+
export declare function hasTagPresence(a: TagPresence): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* True when the tag flags are mutually incoherent: the `untagged` negation
|
|
70
|
+
* inverts a tag presence, so it does not combine with a tag-presence flag. A
|
|
71
|
+
* pure predicate — each surface emits its own usage error in its own voice.
|
|
72
|
+
*/
|
|
73
|
+
export declare function tagFlagConflict(a: TagPresence): boolean;
|
|
74
|
+
/** Build the {@link ViewFilter} tag fields from the parsed flags (empty keys omitted). */
|
|
75
|
+
export declare function tagFilterFields(a: TagPresence): ViewFilter;
|
|
76
|
+
/** The parsed read-filter inputs {@link validateViewArgs} inspects. */
|
|
77
|
+
export interface FilterArgs extends TagPresence {
|
|
78
|
+
overdue?: boolean | undefined;
|
|
79
|
+
/** search only: the status-widening flags overdue refuses to combine with. */
|
|
80
|
+
logged?: boolean | undefined;
|
|
81
|
+
trashed?: boolean | undefined;
|
|
82
|
+
all?: boolean | undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The surface's exact usage-error copy for each conflict {@link validateViewArgs}
|
|
86
|
+
* can detect. Built per call by the surface (it knows the view/kind and its own
|
|
87
|
+
* flag spellings), so the SHARED module decides WHICH message applies while the
|
|
88
|
+
* message text stays consumer-voiced per surface.
|
|
89
|
+
*/
|
|
90
|
+
export interface FilterVocab {
|
|
91
|
+
/** `untagged` combined with a tag-presence flag. */
|
|
92
|
+
untaggedConflict: string;
|
|
93
|
+
/** `overdue` passed to a view that does not honor it. */
|
|
94
|
+
overdueRejected: string;
|
|
95
|
+
/** `overdue` combined with a status-widening flag (search). */
|
|
96
|
+
overdueStatusWiden: string;
|
|
97
|
+
}
|
|
98
|
+
/** {@link validateViewArgs} outcome: the built filter, or the usage message to emit. */
|
|
99
|
+
export type ViewValidation = {
|
|
100
|
+
ok: true;
|
|
101
|
+
filter: ViewFilter;
|
|
102
|
+
} | {
|
|
103
|
+
ok: false;
|
|
104
|
+
message: string;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Validate a view's filter arguments against {@link FILTER_CONTRACT} and, when
|
|
108
|
+
* coherent, build the {@link ViewFilter} tag+overdue portion. The checks (and
|
|
109
|
+
* their precedence) live here so both surfaces stay in lockstep; the surface
|
|
110
|
+
* supplies its own exact copy via {@link FilterVocab}.
|
|
111
|
+
*
|
|
112
|
+
* Does NOT enforce tag-REJECTION (a view refusing tag flags outright): that
|
|
113
|
+
* lives at the two call sites that currently reject — the areas/projects/tags
|
|
114
|
+
* collection listers — so this stays a pure no-op for the flat/container views
|
|
115
|
+
* that merely ignore tags on a sub-path (e.g. `trash`).
|
|
116
|
+
*/
|
|
117
|
+
export declare function validateViewArgs(view: ViewName, args: FilterArgs, vocab: FilterVocab): ViewValidation;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for read-filter applicability, one row per view.
|
|
3
|
+
* Consumed by both surfaces' per-view guards (CLI command actions, MCP tool
|
|
4
|
+
* handlers) so no surface hand-maintains its own view list.
|
|
5
|
+
*/
|
|
6
|
+
export const FILTER_CONTRACT = {
|
|
7
|
+
// Current-work flat views: overdue applies, tags are inheritance-inclusive.
|
|
8
|
+
today: { overdue: true, tag: "inherited", bound: "flat", statusWidening: false },
|
|
9
|
+
inbox: { overdue: true, tag: "inherited", bound: "flat", statusWidening: false },
|
|
10
|
+
// Grouped catalogues: overdue applies, per-block caps.
|
|
11
|
+
anytime: { overdue: true, tag: "inherited", bound: "grouped", statusWidening: false },
|
|
12
|
+
someday: { overdue: true, tag: "inherited", bound: "grouped", statusWidening: false },
|
|
13
|
+
// Forward-looking / closed-item flat views: overdue deliberately does NOT apply.
|
|
14
|
+
upcoming: { overdue: false, tag: "inherited", bound: "flat", statusWidening: false },
|
|
15
|
+
logbook: { overdue: false, tag: "inherited", bound: "flat", statusWidening: false },
|
|
16
|
+
trash: { overdue: false, tag: "rejected", bound: "flat", statusWidening: false },
|
|
17
|
+
// Search: overdue applies but refuses the status-widening flags.
|
|
18
|
+
search: { overdue: true, tag: "inherited", bound: "flat", statusWidening: true },
|
|
19
|
+
// Sync feed: neither overdue nor tags apply.
|
|
20
|
+
changes: { overdue: false, tag: "rejected", bound: "flat", statusWidening: false },
|
|
21
|
+
// Projects LIST is a FLAT view — each project row filtered by its own
|
|
22
|
+
// (inheritance-inclusive) tags; overdue keeps past-deadline projects.
|
|
23
|
+
projects: { overdue: true, tag: "inherited", bound: "none", statusWidening: false },
|
|
24
|
+
// Areas LIST rows carry no deadline and no per-row tag filter.
|
|
25
|
+
areas: { overdue: false, tag: "rejected", bound: "none", statusWidening: false },
|
|
26
|
+
// Single-container views: overdue and DIRECT-tag filtering apply to children.
|
|
27
|
+
"project-show": { overdue: true, tag: "direct", bound: "grouped", statusWidening: false },
|
|
28
|
+
"area-show": { overdue: true, tag: "direct", bound: "grouped", statusWidening: false },
|
|
29
|
+
};
|
|
30
|
+
/** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
|
|
31
|
+
export function hasTagPresence(a) {
|
|
32
|
+
return (a.tag?.length ?? 0) > 0 || a.exactTag === true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* True when the tag flags are mutually incoherent: the `untagged` negation
|
|
36
|
+
* inverts a tag presence, so it does not combine with a tag-presence flag. A
|
|
37
|
+
* pure predicate — each surface emits its own usage error in its own voice.
|
|
38
|
+
*/
|
|
39
|
+
export function tagFlagConflict(a) {
|
|
40
|
+
return a.untagged === true && hasTagPresence(a);
|
|
41
|
+
}
|
|
42
|
+
/** Build the {@link ViewFilter} tag fields from the parsed flags (empty keys omitted). */
|
|
43
|
+
export function tagFilterFields(a) {
|
|
44
|
+
return {
|
|
45
|
+
...(a.tag !== undefined && a.tag.length > 0 && { tags: [...a.tag] }),
|
|
46
|
+
...(a.exactTag === true && { exactTag: true }),
|
|
47
|
+
...(a.untagged === true && { untagged: true }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate a view's filter arguments against {@link FILTER_CONTRACT} and, when
|
|
52
|
+
* coherent, build the {@link ViewFilter} tag+overdue portion. The checks (and
|
|
53
|
+
* their precedence) live here so both surfaces stay in lockstep; the surface
|
|
54
|
+
* supplies its own exact copy via {@link FilterVocab}.
|
|
55
|
+
*
|
|
56
|
+
* Does NOT enforce tag-REJECTION (a view refusing tag flags outright): that
|
|
57
|
+
* lives at the two call sites that currently reject — the areas/projects/tags
|
|
58
|
+
* collection listers — so this stays a pure no-op for the flat/container views
|
|
59
|
+
* that merely ignore tags on a sub-path (e.g. `trash`).
|
|
60
|
+
*/
|
|
61
|
+
export function validateViewArgs(view, args, vocab) {
|
|
62
|
+
const spec = FILTER_CONTRACT[view];
|
|
63
|
+
if (tagFlagConflict(args))
|
|
64
|
+
return { ok: false, message: vocab.untaggedConflict };
|
|
65
|
+
if (args.overdue === true && !spec.overdue) {
|
|
66
|
+
return { ok: false, message: vocab.overdueRejected };
|
|
67
|
+
}
|
|
68
|
+
if (args.overdue === true &&
|
|
69
|
+
spec.statusWidening &&
|
|
70
|
+
(args.logged === true || args.trashed === true || args.all === true)) {
|
|
71
|
+
return { ok: false, message: vocab.overdueStatusWiden };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
filter: { ...tagFilterFields(args), ...(args.overdue === true && { overdue: true }) },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=filter-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-contract.js","sourceRoot":"","sources":["../../src/read/filter-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* GUI Logbook share every TMTask column shape with logged history; only
|
|
9
9
|
* the stopDate-vs-boundary relation differs).
|
|
10
10
|
*
|
|
11
|
-
* logInterval semantics
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* logInterval semantics (GUI enum VERIFIED live in a clone, 2026-07-12,
|
|
12
|
+
* s-campaign-results.md round 3): 0 = immediately, 1 = daily, 4 = manual
|
|
13
|
+
* ("When I choose"). Things 3.22.11's "Move completed items to Logbook"
|
|
14
|
+
* dropdown offers ONLY these three — there is NO weekly or monthly option,
|
|
15
|
+
* so the `case 2`/`case 3` branches below are UNREACHABLE with the current
|
|
16
|
+
* app (kept as defensive analogues in case a future build adds them; the
|
|
17
|
+
* real manual value 4 falls to `default`, which is correct). manualLogDate
|
|
18
|
+
* is the user's last explicit "log now" (an AppleScript `log completed now`
|
|
19
|
+
* advances it to the current time — VERIFIED); it can only move the boundary
|
|
15
20
|
* FORWARD past the interval's own edge.
|
|
16
21
|
*/
|
|
17
22
|
import type { DatabaseSync } from "node:sqlite";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-boundary.js","sourceRoot":"","sources":["../../src/read/log-boundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"log-boundary.js","sourceRoot":"","sources":["../../src/read/log-boundary.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,WAAW,CAAC,EAAgB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,EAElE,CAAC;IACd,MAAM,MAAM,GAAG,GAAG,EAAE,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,IAAI,IAAU,CAAC;IACf,QAAQ,GAAG,EAAE,WAAW,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC;YACJ,IAAI,GAAG,GAAG,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,IAAI,GAAG,UAAU,CAAC;YAClB,MAAM;QACR,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACpC,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD;YACE,IAAI,GAAG,MAAM,IAAI,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CACxB,KAAU,EACV,QAAc;IAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|