things-api 0.14.0 → 0.16.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 +4 -4
- package/dist/audit/schema.d.ts +8 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/commands/area.js +1 -1
- package/dist/cli/commands/install-skill.js +9 -2
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/project.js +1 -1
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.js +73 -4
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.d.ts +11 -1
- package/dist/cli/commands/repeat-flags.js +23 -0
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/todo.js +50 -3
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +202 -61
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/glyphs.js +9 -10
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +18 -13
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/read-driver.d.ts +9 -3
- package/dist/cli/read-driver.js +3 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +33 -2
- package/dist/cli/render.js +60 -9
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/skill.d.ts +24 -0
- package/dist/cli/skill.js +40 -0
- package/dist/cli/skill.js.map +1 -1
- package/dist/client.d.ts +88 -15
- package/dist/client.js +31 -5
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +13 -2
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +187 -36
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +97 -40
- package/dist/model/entities.js +0 -4
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.js +24 -43
- package/dist/model/mappers.js.map +1 -1
- package/dist/read/area-view.js +6 -6
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +51 -3
- package/dist/read/detail.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +3 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/log-boundary.d.ts +46 -2
- package/dist/read/log-boundary.js +43 -2
- package/dist/read/log-boundary.js.map +1 -1
- package/dist/read/predicates.d.ts +13 -0
- package/dist/read/predicates.js +13 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.js +5 -5
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/queries.d.ts +12 -0
- package/dist/read/queries.js +14 -0
- package/dist/read/queries.js.map +1 -1
- package/dist/read/search-rank.js +2 -2
- package/dist/read/search-rank.js.map +1 -1
- package/dist/read/shape.d.ts +30 -26
- package/dist/read/shape.js +130 -101
- package/dist/read/shape.js.map +1 -1
- package/dist/read/snapshot.js +2 -2
- package/dist/read/snapshot.js.map +1 -1
- package/dist/read/stage.d.ts +36 -18
- package/dist/read/stage.js +30 -13
- package/dist/read/stage.js.map +1 -1
- package/dist/read/views.d.ts +62 -0
- package/dist/read/views.js +134 -14
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +2 -2
- package/dist/surface-copy.js +2 -2
- package/dist/write/batch.d.ts +28 -3
- package/dist/write/batch.js +162 -46
- package/dist/write/batch.js.map +1 -1
- package/dist/write/clear-reminder.js +3 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/clone.d.ts +4 -0
- package/dist/write/clone.js +531 -0
- package/dist/write/clone.js.map +1 -0
- package/dist/write/commands.js +196 -29
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +44 -12
- package/dist/write/guards.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +2 -3
- package/dist/write/make-repeating-project.js +2 -73
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +53 -1
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +134 -11
- package/dist/write/operations.js +5 -1
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +38 -0
- package/dist/write/pipeline.js +71 -6
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +41 -0
- package/dist/write/pre-state.js +71 -8
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/preserve-modified.d.ts +55 -0
- package/dist/write/preserve-modified.js +106 -0
- package/dist/write/preserve-modified.js.map +1 -0
- package/dist/write/promote-clone.d.ts +32 -0
- package/dist/write/promote-clone.js +620 -0
- package/dist/write/promote-clone.js.map +1 -0
- package/dist/write/reorder.js +49 -2
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/resolution-timestamps.js +29 -7
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +32 -14
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.js +2 -0
- package/dist/write/scope-guard.js.map +1 -1
- package/dist/write/undo.d.ts +12 -2
- package/dist/write/undo.js +145 -22
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +9 -0
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +242 -10
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/ui-certification.d.ts +1 -1
- package/dist/write/vectors/ui-certification.js +10 -3
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/verify/delta.d.ts +55 -10
- package/dist/write/verify/delta.js +73 -10
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +25 -0
- package/skills/things-cli/SKILL.md +10 -7
- package/skills/things-cli/references/banner.md +1 -1
- package/skills/things-cli/references/contracts.md +17 -9
- package/skills/things-cli/references/{model.md → data-model.md} +5 -2
- package/skills/things-cli/references/ordering.md +2 -0
package/dist/read/shape.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The read-payload SHAPING transform: the token-economy rules
|
|
3
|
-
* universal item-DTO reshapes (
|
|
2
|
+
* The read-payload SHAPING transform: the token-economy rules RS1 and RS2, the
|
|
3
|
+
* universal item-DTO reshapes (RS4), and the RS5 lifecycle taxonomy — applied at
|
|
4
4
|
* the JSON emit boundary of the read surfaces (the CLI `--json` read envelope,
|
|
5
5
|
* src/cli/read-driver.ts, and the MCP read tool results, src/mcp/server.ts), the
|
|
6
6
|
* same boundaries omit-empty runs at. Shaping runs BEFORE omit-empty. The
|
|
7
7
|
* human-render path keeps the full, unshaped entities, so this is JSON-only.
|
|
8
8
|
*
|
|
9
|
-
* Both
|
|
9
|
+
* Both RS1/RS2 are deterministic BY VIEW KIND / SECTION — the emitter knows whether
|
|
10
10
|
* it is inside a single-container view, a lifecycle bucket, or a mixed list —
|
|
11
11
|
* never a per-item heuristic.
|
|
12
12
|
*
|
|
13
|
-
* ##
|
|
13
|
+
* ## RS5 — the `stage` lifecycle taxonomy (every tier, every kind incl. detail)
|
|
14
14
|
* The three former wire fields `start` / `logged` / `trashed` are DELETED from
|
|
15
15
|
* every item and replaced by ONE derived `stage` ∈ `inbox | upcoming | anytime |
|
|
16
16
|
* someday | logbook | trash` (src/read/stage.ts `deriveStage`, the single pure
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
* bucket a view puts an item in). Today/evening membership is a SEPARATE
|
|
19
19
|
* presence-keyed axis — `today: true` / `evening: true` (evening implies today) —
|
|
20
20
|
* derived in the mapper with the Today view's own two-arm predicate.
|
|
21
|
-
* - `stage` is DROPPED only where the enclosing node PROVABLY states it — the
|
|
21
|
+
* - `stage` is DROPPED only where the enclosing node PROVABLY states it — the RS1
|
|
22
22
|
* rule (drop only what the node provably says). That is the stage-PURE flat
|
|
23
23
|
* views (inbox, `anytime`, someday, logbook, trash) and the stage-named card
|
|
24
24
|
* sub-buckets (anytime/upcoming/someday/logbook/trash, which the bucketer splits
|
|
25
|
-
* BY stage). The `anytime` catalogue is stage-PURE (
|
|
25
|
+
* BY stage). The `anytime` catalogue is stage-PURE (RS5.2): every member is an
|
|
26
26
|
* Anytime-view row (ANYTIME_SELF) — undated-active, arrived-active, or arrived
|
|
27
27
|
* someday-scheduled — and an ARRIVED dated row derives `anytime` (Upcoming is
|
|
28
28
|
* STRICTLY FUTURE, UPC1), so the field is redundant there.
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
* non-redundant information — plus `today` (mixes upcoming + anytime-deadline
|
|
33
33
|
* rows), search, changes, the projects/areas listings, the card NODE, and
|
|
34
34
|
* detail.
|
|
35
|
-
* - the former `todaySection` field is
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* - the former `todaySection` field is DELETED from the entity model entirely:
|
|
36
|
+
* it merely duplicated the presence-keyed `today`/`evening` markers
|
|
37
|
+
* (`todaySection: "evening"` ⇔ `evening: true`; `"today"` ⇔ `today && !evening`),
|
|
38
|
+
* which every consumer now reads directly (the human render, the write-verify
|
|
39
|
+
* delta, the today-view evening split).
|
|
39
40
|
*
|
|
40
|
-
* ##
|
|
41
|
+
* ## RS7 — `when`, the derived TIME-AXIS position (replaces startDate + markers)
|
|
41
42
|
* Today/evening membership and the scheduled/projected date collapse onto ONE
|
|
42
43
|
* derived, presence-keyed field `when` (src/read/stage.ts `deriveWhen`): `"today"`
|
|
43
44
|
* / `"evening"` (Today-view membership, from the SAME `today`/`evening` markers the
|
|
@@ -62,11 +63,11 @@
|
|
|
62
63
|
* it). KEPT everywhere
|
|
63
64
|
* else it is present — including the flat `upcoming`/`anytime`/`inbox`/`someday`
|
|
64
65
|
* catalogues, search, changes (a deadline-pulled row reads `when: "today"` in the
|
|
65
|
-
* mixed search/changes surfaces, informatively; note
|
|
66
|
+
* mixed search/changes surfaces, informatively; note RS8 re-files it to stage
|
|
66
67
|
* `anytime` and the flat inbox/someday views now EXCLUDE it — it appears in the
|
|
67
68
|
* `anytime` catalogue instead, `when: "today"` kept, stage dropped as pure).
|
|
68
69
|
*
|
|
69
|
-
* ##
|
|
70
|
+
* ## RS8 — provisional Today members + GUI-faithful pulled-row membership
|
|
70
71
|
* BANNER1 / BANNER1b (docs/lab/banner1-research.md). Two coupled facts:
|
|
71
72
|
* - **`provisional: true`** — a presence-keyed marker on every Today member the GUI
|
|
72
73
|
* pips / counts in the "You have N new to-dos" banner: a Today member NOT yet
|
|
@@ -83,35 +84,38 @@
|
|
|
83
84
|
* key-implied `when`. The flat someday/inbox views EXCLUDE pulled rows and the anytime view
|
|
84
85
|
* INCLUDES them (src/read/views.ts + predicates.ts DEADLINE_PULLED) — GUI fidelity.
|
|
85
86
|
*
|
|
86
|
-
* ## Universal item-DTO reshapes (
|
|
87
|
+
* ## Universal item-DTO reshapes (RS4 — EVERY tier, EVERY read kind incl. detail)
|
|
87
88
|
* - **checklist nesting** — flat counts → presence-keyed `checklist: {open,total}`.
|
|
88
89
|
* - **todos counts** — a project's flat leaf-action counts → presence-keyed
|
|
89
90
|
* `todos: {open, total}` (omit when total 0).
|
|
90
|
-
* - **repeating template/instance split (
|
|
91
|
+
* - **repeating template/instance split (RS6)** — the wire drops the
|
|
91
92
|
* `isTemplate`/`isInstance` discriminators; key presence carries the fact. A
|
|
92
93
|
* TEMPLATE keeps a nested `repeating: {paused?, deadlined?, rule?,
|
|
93
94
|
* latestInstance?}` — the series object (rule config + backward pointer +
|
|
94
95
|
* state flags); presence MEANS template. The forward pointer `nextOccurrence`
|
|
95
|
-
* moved to the top-level `when` (
|
|
96
|
+
* moved to the top-level `when` (RS7 — a template's projected date IS its time
|
|
96
97
|
* position); `latestInstance` is detail-only (SL1). An INSTANCE keeps a flat
|
|
97
|
-
* `instanceOf: <templateUuid>` and no `repeating
|
|
98
|
-
*
|
|
98
|
+
* `instanceOf: <templateUuid>` and no `repeating`; on a DETAIL read it also
|
|
99
|
+
* gains a sibling `repeats: {rule?, next?, paused?}` — its template's repeat
|
|
100
|
+
* context (the GUI's lower-corner caption), `rule` byte-consistent with the
|
|
101
|
+
* template card's `repeating.rule` and `next` the fixed-mode next occurrence. A
|
|
102
|
+
* plain row keeps neither. See {@link reshapeRepeatingWire}.
|
|
99
103
|
* - **string tags** — `tags`/`inheritedTags` become plain arrays of names.
|
|
100
104
|
* - **one project key** — a headed item's owning project (formerly
|
|
101
105
|
* `headingProject`) is merged into `project`; `headingProject` never appears.
|
|
102
106
|
*
|
|
103
|
-
* ##
|
|
107
|
+
* ## RS1 — no-redundant-ancestry (both tiers)
|
|
104
108
|
* project-view children drop `project`+`area` (heading-group members also drop
|
|
105
109
|
* `heading`); area-view children/project-cards drop `area`; anytime/someday
|
|
106
110
|
* section items drop `area`. Mixed lists keep every ref. (In the COMPACT tier the
|
|
107
|
-
* `heading` ref is additionally dropped everywhere —
|
|
111
|
+
* `heading` ref is additionally dropped everywhere — RS2.)
|
|
108
112
|
*
|
|
109
|
-
* ##
|
|
113
|
+
* ## RS2 — named detail tiers (compact | full)
|
|
110
114
|
* List contexts default to COMPACT; `detail`/`show` and `--full` / `full:true`
|
|
111
115
|
* use FULL. Compact drops `created`/`modified`, the full `notes` string (a
|
|
112
116
|
* presence-keyed `hasNotes: true` marks a row with notes), and the `heading` ref;
|
|
113
|
-
* `status` is omitted when `open`. FULL keeps them but still applies
|
|
114
|
-
* universal reshapes, and
|
|
117
|
+
* `status` is omitted when `open`. FULL keeps them but still applies RS1, the
|
|
118
|
+
* universal reshapes, and RS5.
|
|
115
119
|
*/
|
|
116
120
|
import { deriveStage, deriveWhen, whenIsProvisional } from "./stage.js";
|
|
117
121
|
/** The DB-less default: assume every title round-trips (bare title, no uuid sibling). */
|
|
@@ -212,7 +216,7 @@ function reshapeTodos(o) {
|
|
|
212
216
|
o["todos"] = { open, total };
|
|
213
217
|
}
|
|
214
218
|
/**
|
|
215
|
-
*
|
|
219
|
+
* RS6 — rewrite the internal `repeating` block into the wire's template/instance
|
|
216
220
|
* split, mutating `o` in place. The internal entity carries the full
|
|
217
221
|
* RepeatingInfo (`isTemplate`/`isInstance`/`templateUuid`/…); the wire loses the
|
|
218
222
|
* `isTemplate`/`isInstance` discriminators entirely and instead lets KEY
|
|
@@ -223,13 +227,21 @@ function reshapeTodos(o) {
|
|
|
223
227
|
* template (an unadorned template emits `repeating: {}` — a bare `{}` is NOT
|
|
224
228
|
* pruned by omit-empty, so the presence signal survives). The inner false
|
|
225
229
|
* booleans are default-pruned (presence-keyed). The forward pointer
|
|
226
|
-
* `nextOccurrence` moved OUT to the top-level `when` (
|
|
230
|
+
* `nextOccurrence` moved OUT to the top-level `when` (RS7 — a template's
|
|
227
231
|
* projected date IS its time position); `rule` and `latestInstance` stay
|
|
228
232
|
* detail-only (populated by src/read/detail.ts on `entity.repeating`);
|
|
229
233
|
* `latestInstance` is the backward pointer symmetric to `when`.
|
|
230
234
|
* - **Instance** (`isInstance`) → a flat presence-keyed `instanceOf:
|
|
231
|
-
* <templateUuid>`
|
|
232
|
-
*
|
|
235
|
+
* <templateUuid>` (the instance marker + the write handle) and, on a DETAIL
|
|
236
|
+
* read, a sibling `repeats` object — the template's repeat CONTEXT joined onto
|
|
237
|
+
* the instance (`{rule?, next?, paused?}`), the GUI's lower-corner "Repeats on
|
|
238
|
+
* Aug 19" / "Repeats 1 day after completion" caption. `rule` is the SAME decoded
|
|
239
|
+
* shape a template card emits under `repeating.rule` (one recurrence vocabulary
|
|
240
|
+
* on the wire); `next` is the template's projected next occurrence, FIXED mode
|
|
241
|
+
* ONLY (absent for after-completion — no successor date exists yet); `paused`
|
|
242
|
+
* surfaces the template's paused flag. Populated by src/read/detail.ts's mirror
|
|
243
|
+
* join; absent when the template is unresolvable (dangling FK) or carries no
|
|
244
|
+
* caption. NO `repeating` object on an instance.
|
|
233
245
|
* - **Plain** (neither) → neither key.
|
|
234
246
|
*/
|
|
235
247
|
function reshapeRepeatingWire(o) {
|
|
@@ -247,7 +259,7 @@ function reshapeRepeatingWire(o) {
|
|
|
247
259
|
if (r["rule"] != null)
|
|
248
260
|
out["rule"] = r["rule"]; // detail read only
|
|
249
261
|
// The SL1 "Show Latest" pick — detail-only; the backward pointer symmetric to
|
|
250
|
-
// the top-level `when` (the forward pointer,
|
|
262
|
+
// the top-level `when` (the forward pointer, RS7).
|
|
251
263
|
if (typeof r["latestInstance"] === "string")
|
|
252
264
|
out["latestInstance"] = r["latestInstance"];
|
|
253
265
|
o["repeating"] = out;
|
|
@@ -255,27 +267,43 @@ function reshapeRepeatingWire(o) {
|
|
|
255
267
|
else if (r["isInstance"] === true) {
|
|
256
268
|
if (typeof r["templateUuid"] === "string")
|
|
257
269
|
o["instanceOf"] = r["templateUuid"];
|
|
270
|
+
// The instance's TEMPLATE context (detail-only mirror join). Presence-keyed:
|
|
271
|
+
// the whole `repeats` object is omitted unless it carries at least one fact.
|
|
272
|
+
const ctx = r["repeats"];
|
|
273
|
+
if (ctx !== null && typeof ctx === "object") {
|
|
274
|
+
const c = ctx;
|
|
275
|
+
const out = {};
|
|
276
|
+
if (c["rule"] != null)
|
|
277
|
+
out["rule"] = c["rule"]; // same shape as a template card's `repeating.rule`
|
|
278
|
+
if (typeof c["next"] === "string")
|
|
279
|
+
out["next"] = c["next"]; // FIXED mode only
|
|
280
|
+
if (c["paused"] === true)
|
|
281
|
+
out["paused"] = true;
|
|
282
|
+
if (Object.keys(out).length > 0)
|
|
283
|
+
o["repeats"] = out;
|
|
284
|
+
}
|
|
258
285
|
}
|
|
259
286
|
}
|
|
260
|
-
/** The
|
|
287
|
+
/** The RS5 stage input read straight off a materialized task entity's `derived` bag. */
|
|
261
288
|
function stageOf(s) {
|
|
262
289
|
const repeating = s["repeating"];
|
|
263
290
|
const isTemplate = repeating !== null &&
|
|
264
291
|
typeof repeating === "object" &&
|
|
265
292
|
repeating["isTemplate"] === true;
|
|
293
|
+
const d = (s["derived"] ?? {});
|
|
266
294
|
return deriveStage({
|
|
267
|
-
trashed:
|
|
268
|
-
logged:
|
|
269
|
-
start:
|
|
295
|
+
trashed: d["trashed"] === true,
|
|
296
|
+
logged: d["logged"] === true,
|
|
297
|
+
start: d["start"],
|
|
270
298
|
startDate: s["startDate"] ?? null,
|
|
271
299
|
repeating: { isTemplate },
|
|
272
300
|
// The presence-keyed Today marker (stamped at materialize with the response
|
|
273
301
|
// clock) discriminates an ARRIVED dated row (→ anytime) from a strictly-
|
|
274
|
-
// future one (→ upcoming). Read
|
|
275
|
-
today:
|
|
302
|
+
// future one (→ upcoming). Read from the `derived` substrate bag.
|
|
303
|
+
today: d["today"] === true,
|
|
276
304
|
});
|
|
277
305
|
}
|
|
278
|
-
/** The
|
|
306
|
+
/** The RS7 `when` input read straight off a materialized task entity (given its stage). */
|
|
279
307
|
function whenOf(s, stage) {
|
|
280
308
|
const repeating = s["repeating"];
|
|
281
309
|
const isTemplate = repeating !== null &&
|
|
@@ -284,20 +312,21 @@ function whenOf(s, stage) {
|
|
|
284
312
|
const nextOccurrence = isTemplate && typeof repeating["nextOccurrence"] === "string"
|
|
285
313
|
? repeating["nextOccurrence"]
|
|
286
314
|
: null;
|
|
315
|
+
const d = (s["derived"] ?? {});
|
|
287
316
|
return deriveWhen({
|
|
288
317
|
stage,
|
|
289
318
|
// The SAME presence-keyed markers stageOf reads — never re-derived, so a
|
|
290
319
|
// `when` of today/evening can never disagree with Today-view membership.
|
|
291
|
-
today:
|
|
292
|
-
evening:
|
|
320
|
+
today: d["today"] === true,
|
|
321
|
+
evening: d["evening"] === true,
|
|
293
322
|
startDate: s["startDate"] ?? null,
|
|
294
323
|
repeating: { isTemplate, nextOccurrence },
|
|
295
324
|
});
|
|
296
325
|
}
|
|
297
326
|
/**
|
|
298
|
-
* Shape ONE task entity (to-do or project): the universal reshapes, the
|
|
299
|
-
* stage/`when` rewrite, then the
|
|
300
|
-
*
|
|
327
|
+
* Shape ONE task entity (to-do or project): the universal reshapes, the RS5/RS7
|
|
328
|
+
* stage/`when` rewrite, then the RS1 ancestry drops, then — when `compact` — the
|
|
329
|
+
* RS2 default-pruning. A shallow copy is taken so unknown sibling keys
|
|
301
330
|
* (`changeKind` on a changes row, `match` on a search hit) pass through
|
|
302
331
|
* untouched. Non-task values (areas, tags, refs, headings) are returned as-is.
|
|
303
332
|
*/
|
|
@@ -309,8 +338,8 @@ function shapeItem(src, drop, compact, promoter) {
|
|
|
309
338
|
if (type !== "to-do" && type !== "project")
|
|
310
339
|
return src; // not a shaped entity
|
|
311
340
|
const stage = stageOf(s); // from the ORIGINAL fields, before any reshape
|
|
312
|
-
const when = whenOf(s, stage); //
|
|
313
|
-
//
|
|
341
|
+
const when = whenOf(s, stage); // RS7 — derived from the same fields + markers
|
|
342
|
+
// RS8 (BANNER1 law L-B): a Today member is PROVISIONAL — the GUI pips it and
|
|
314
343
|
// counts it in the "You have N new to-dos" banner — until the app MATERIALIZES
|
|
315
344
|
// it (start:=1, startDate:=today). Presence-keyed marker, derived from the SAME
|
|
316
345
|
// inputs as the stage/`when` axes (never an independent re-derivation): the row
|
|
@@ -321,9 +350,9 @@ function shapeItem(src, drop, compact, promoter) {
|
|
|
321
350
|
// section, so no enclosing node implies it. The app rewrites start/startDate
|
|
322
351
|
// when the user acknowledges the banner; that is a GUI-only side effect our
|
|
323
352
|
// read cannot clear (watchers beware — see contract.md `provisional`).
|
|
324
|
-
const provisional = whenIsProvisional(when, s["start"], s["startDate"] ?? null);
|
|
353
|
+
const provisional = whenIsProvisional(when, (s["derived"] ?? {})["start"], s["startDate"] ?? null);
|
|
325
354
|
const o = { ...s };
|
|
326
|
-
//
|
|
355
|
+
// RS4 universal reshapes (every tier, every kind incl. detail).
|
|
327
356
|
reshapeChecklist(o);
|
|
328
357
|
reshapeTodos(o);
|
|
329
358
|
flattenTags(o);
|
|
@@ -331,41 +360,32 @@ function shapeItem(src, drop, compact, promoter) {
|
|
|
331
360
|
if (o["project"] == null && o["headingProject"] != null)
|
|
332
361
|
o["project"] = o["headingProject"];
|
|
333
362
|
delete o["headingProject"];
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
|
|
363
|
+
// The ENTIRE internal derivation substrate leaves the wire in ONE structural
|
|
364
|
+
// drop (one-vocabulary Batch 2, Option B): `start`/`logged`/`trashed` (RS5 —
|
|
365
|
+
// replaced by `stage`), `today`/`evening` (RS7 — replaced by `when`), and the
|
|
366
|
+
// raw `reminder` byte all live in the nested `o.derived` bag. §9n: the
|
|
367
|
+
// top-level consumer `reminder` is ALREADY the live-gated value (null once the
|
|
368
|
+
// byte is presentation-dead — its `startDate` gone strictly past), gated at the
|
|
369
|
+
// mapper; a null key is pruned by omit-empty, so a stale reminder never reaches
|
|
370
|
+
// the wire without any drop here. `stage`/`when`/`provisional` are then stamped
|
|
371
|
+
// from the derivations above.
|
|
372
|
+
delete o["derived"];
|
|
341
373
|
if (drop.stage !== true)
|
|
342
374
|
o["stage"] = stage;
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
// enclosing context provably states the position (the today view's sections;
|
|
346
|
-
// a card date-group — handled in rebucketChildren).
|
|
347
|
-
delete o["today"];
|
|
348
|
-
delete o["evening"];
|
|
349
|
-
// §9n — a reminder byte is presentation-dead once its `startDate` goes strictly
|
|
350
|
-
// past: the GUI hides the bell but never clears the byte. The materialize-time
|
|
351
|
-
// `reminderLive` marker (mappers, via `reminderIsLive` under the response clock)
|
|
352
|
-
// says whether the stored reminder still renders; drop the `reminder` key when
|
|
353
|
-
// it does not, mirroring the GUI. The marker itself never rides the wire.
|
|
354
|
-
if (o["reminderLive"] !== true)
|
|
355
|
-
delete o["reminder"];
|
|
356
|
-
delete o["reminderLive"];
|
|
375
|
+
// RS7 — `when` is emitted unless the enclosing context provably states the
|
|
376
|
+
// position (the today view's sections; a card date-group in rebucketChildren).
|
|
357
377
|
if (drop.when !== true && when !== undefined)
|
|
358
378
|
o["when"] = when;
|
|
359
|
-
//
|
|
379
|
+
// RS8 — the provisional banner marker (never dropped; presence-keyed).
|
|
360
380
|
if (provisional)
|
|
361
381
|
o["provisional"] = true;
|
|
362
382
|
// The owning project's uuid scopes the heading round-trip (headings resolve
|
|
363
|
-
// within their project). Captured BEFORE the
|
|
383
|
+
// within their project). Captured BEFORE the RS1 project-drop so a project-view
|
|
364
384
|
// LOGBOOK row — whose `project` is dropped as redundant, yet which KEEPS its
|
|
365
385
|
// heading ref (drop.keepHeading) — can still promote its `headingUuid` in the
|
|
366
386
|
// project's scope.
|
|
367
387
|
const projectUuid = refUuid(o["project"]);
|
|
368
|
-
//
|
|
388
|
+
// RS1 — drop redundant ancestry (both tiers).
|
|
369
389
|
if (drop.project === true)
|
|
370
390
|
delete o["project"];
|
|
371
391
|
if (drop.area === true)
|
|
@@ -386,7 +406,7 @@ function shapeItem(src, drop, compact, promoter) {
|
|
|
386
406
|
// `isRepeatingTemplate` marker, so re-emit it here as a flat presence-keyed
|
|
387
407
|
// sibling of the `project` ref (never `false`), riding wherever `project`
|
|
388
408
|
// rides. A heading-nested row already merged its owning project into `project`
|
|
389
|
-
// above, so direct AND headed template children mark; the
|
|
409
|
+
// above, so direct AND headed template children mark; the RS1 project-drop
|
|
390
410
|
// above already removed `project` where the view implies it, so a project-view
|
|
391
411
|
// child carries no orphaned marker. Both tiers — it is a correctness signal,
|
|
392
412
|
// not detail. Only project refs ever carry the flag (area/heading never do).
|
|
@@ -396,13 +416,13 @@ function shapeItem(src, drop, compact, promoter) {
|
|
|
396
416
|
flattenRef(o, "area", "areaUuid", "area", forceUuid, promoter);
|
|
397
417
|
if (!compact || drop.keepHeading === true)
|
|
398
418
|
flattenRef(o, "heading", "headingUuid", "heading", forceUuid, promoter, projectUuid);
|
|
399
|
-
//
|
|
419
|
+
// RS7 — FULL/DETAIL keep the raw `startDate` beside `when` as the SUBSTRATE
|
|
400
420
|
// (`startDate` = what is stored, `when` = where it sits). COMPACT drops it below
|
|
401
421
|
// (the position `when` carries is what a list needs).
|
|
402
422
|
if (!compact)
|
|
403
423
|
return o;
|
|
404
|
-
//
|
|
405
|
-
delete o["startDate"]; //
|
|
424
|
+
// RS2 compact — default-pruning (absence = the default).
|
|
425
|
+
delete o["startDate"]; // RS7 — position lives in `when`; substrate is full-tier only
|
|
406
426
|
if (o["status"] === "open")
|
|
407
427
|
delete o["status"];
|
|
408
428
|
delete o["created"];
|
|
@@ -432,12 +452,12 @@ function withShapedItems(base, drop, compact, promoter) {
|
|
|
432
452
|
}
|
|
433
453
|
/**
|
|
434
454
|
* Re-bucket a project's / area's / heading's live (non-logbook/trash) children
|
|
435
|
-
* into the
|
|
455
|
+
* into the RS5 card shape by their derived {@link deriveStage} — so the bucket an
|
|
436
456
|
* item lands in ALWAYS equals its `stage`:
|
|
437
457
|
* - `anytime` — stage anytime, in encounter order;
|
|
438
|
-
* - `upcoming` — stage upcoming,
|
|
458
|
+
* - `upcoming` — stage upcoming, day-grouped `[{when, items}]` (a dated row under
|
|
439
459
|
* its `startDate`, a template under its `nextOccurrence`), date ASC; date-LESS
|
|
440
|
-
* templates (after-completion / paused) form a trailing `{
|
|
460
|
+
* templates (after-completion / paused) form a trailing `{when: null, items}`
|
|
441
461
|
* group (explicit null per the `area: null` section precedent);
|
|
442
462
|
* - `someday` — stage someday.
|
|
443
463
|
* Items are already in view order (index / date+todayIndex) from the read layer,
|
|
@@ -477,7 +497,7 @@ function rebucketChildren(children, drop, compact, promoter) {
|
|
|
477
497
|
datedByKey.set(date, []);
|
|
478
498
|
datedOrder.push(date);
|
|
479
499
|
}
|
|
480
|
-
//
|
|
500
|
+
// RS7 — inside a date-group the group states the date, so a member whose
|
|
481
501
|
// `when` equals it drops it (every scheduled row and every projected
|
|
482
502
|
// template does — that IS the group key).
|
|
483
503
|
const shaped = shape(c);
|
|
@@ -495,12 +515,12 @@ function rebucketChildren(children, drop, compact, promoter) {
|
|
|
495
515
|
}
|
|
496
516
|
const upcoming = datedOrder
|
|
497
517
|
.toSorted((a, b) => a.localeCompare(b))
|
|
498
|
-
.map((date) => ({ date, items: datedByKey.get(date) }));
|
|
518
|
+
.map((date) => ({ when: date, items: datedByKey.get(date) }));
|
|
499
519
|
if (restingTemplates.length > 0)
|
|
500
|
-
upcoming.push({
|
|
520
|
+
upcoming.push({ when: null, items: restingTemplates });
|
|
501
521
|
return { anytime, upcoming, someday };
|
|
502
522
|
}
|
|
503
|
-
/** Flatten an internal IsoDateGroup[] (`[{
|
|
523
|
+
/** Flatten an internal IsoDateGroup[] (`[{when, items}]`) to its items, in order. */
|
|
504
524
|
function flattenGroups(groups) {
|
|
505
525
|
if (!Array.isArray(groups))
|
|
506
526
|
return [];
|
|
@@ -515,7 +535,7 @@ function flattenGroups(groups) {
|
|
|
515
535
|
/** Coerce an unknown value to an array (empty when absent). */
|
|
516
536
|
const asArray = (v) => (Array.isArray(v) ? v : []);
|
|
517
537
|
/**
|
|
518
|
-
* The
|
|
538
|
+
* The RS1 ref drop for the un-headed BODY's four `children` bucket records (v2):
|
|
519
539
|
* every body child drops project/area (the card states them), the bucket-implied
|
|
520
540
|
* stage (each of `anytime`/`upcoming`/`someday`/`logbook` is stage-pure), and the
|
|
521
541
|
* heading ref — a body child is by construction un-headed (its `heading` is null),
|
|
@@ -524,7 +544,7 @@ const asArray = (v) => (Array.isArray(v) ? v : []);
|
|
|
524
544
|
*/
|
|
525
545
|
const PROJECT_CHILD_DROP = { project: true, area: true, heading: true, stage: true };
|
|
526
546
|
/**
|
|
527
|
-
* The
|
|
547
|
+
* The RS1 ref drop for a HEADING's four `children` bucket records (v2): a headed
|
|
528
548
|
* child drops project/area (the card states them), the heading ref (its position
|
|
529
549
|
* UNDER `headings[].children` states membership — structural, #362 / task item 6),
|
|
530
550
|
* and the bucket-implied stage. Applied uniformly to the heading's live buckets
|
|
@@ -537,10 +557,10 @@ const AREA_CHILD_DROP = { area: true, stage: true };
|
|
|
537
557
|
const AREA_PROJECTS_DROP = { area: true };
|
|
538
558
|
/**
|
|
539
559
|
* Anytime sidebar-section items: stage-PURE → drop area + the section-implied
|
|
540
|
-
* stage (
|
|
560
|
+
* stage (RS5.2). Every Anytime-view member derives `anytime`: ANYTIME_SELF
|
|
541
561
|
* (undated-active, arrived-active, arrived someday-scheduled) — an ARRIVED dated
|
|
542
562
|
* row is Anytime, not Upcoming (Upcoming is STRICTLY FUTURE, UPC1) — AND, since
|
|
543
|
-
*
|
|
563
|
+
* RS8, the DEADLINE-PULLED undated Inbox/Someday rows the view now includes
|
|
544
564
|
* (BANNER1b), which derive `anytime` too (deriveStage step 2½: a Today-marked
|
|
545
565
|
* undated row is a pull → anytime). Repeating templates are excluded from the
|
|
546
566
|
* view (NOT_TEMPLATE), so no stage-`upcoming` row can appear here — still pure.
|
|
@@ -552,8 +572,8 @@ const SOMEDAY_SECTION_DROP = { area: true, stage: true };
|
|
|
552
572
|
const NO_DROP = {};
|
|
553
573
|
/**
|
|
554
574
|
* The today view's two `children` bucket records: drop the key-implied `when`
|
|
555
|
-
* (
|
|
556
|
-
* `stage` (
|
|
575
|
+
* (RS7 — the `today`/`evening` bucket key states it) AND the bucket-implied
|
|
576
|
+
* `stage` (RS8). Every Today member now derives stage `anytime` by construction —
|
|
557
577
|
* an ARRIVED `startDate` (step 5) or a DEADLINE PULL (step 2½) both derive
|
|
558
578
|
* `anytime`, and there are no future-dated or undated-someday Today members — so
|
|
559
579
|
* both buckets are provably stage-PURE `anytime` and the field is redundant there
|
|
@@ -613,7 +633,7 @@ function stoppedMs(o) {
|
|
|
613
633
|
* heading (HEADARC2-C anomaly) is NOT logged, so it rides `anytime` here — its
|
|
614
634
|
* presence in a live bucket under an `archived` heading node is self-evident.
|
|
615
635
|
* `drop` carries the container's ancestry drops (body vs heading); the day-block
|
|
616
|
-
* `when`
|
|
636
|
+
* key is `when` end-to-end (`rebucketChildren` builds it — no rename) — `null` for
|
|
617
637
|
* the resting block. No bucket is capped in the project view today, so every
|
|
618
638
|
* `total` is absent (R1: an untruncated bucket never restates its length); the
|
|
619
639
|
* `total?` argument keeps the record + day-block shape ready for PR 5's sweep and
|
|
@@ -623,7 +643,11 @@ function shapeContainerChildren(children, drop, compact, promoter) {
|
|
|
623
643
|
const live = [];
|
|
624
644
|
const logged = [];
|
|
625
645
|
for (const c of asArray(children)) {
|
|
626
|
-
|
|
646
|
+
// The logbook-boundary flag lives on the internal `derived` substrate bag.
|
|
647
|
+
const isLogged = c !== null &&
|
|
648
|
+
typeof c === "object" &&
|
|
649
|
+
c["derived"]?.["logged"] === true;
|
|
650
|
+
if (isLogged)
|
|
627
651
|
logged.push(c);
|
|
628
652
|
else
|
|
629
653
|
live.push(c);
|
|
@@ -633,8 +657,9 @@ function shapeContainerChildren(children, drop, compact, promoter) {
|
|
|
633
657
|
const logbook = shapeList(loggedSorted, drop, compact, promoter);
|
|
634
658
|
return {
|
|
635
659
|
anytime: bucketRecord(anytime),
|
|
636
|
-
// The day-block ARRAY
|
|
637
|
-
|
|
660
|
+
// The day-block ARRAY (`{when, items}`, `when: null` the resting block #V8);
|
|
661
|
+
// `rebucketChildren` already keys each block by `when`.
|
|
662
|
+
upcoming,
|
|
638
663
|
someday: bucketRecord(someday),
|
|
639
664
|
logbook: bucketRecord(logbook),
|
|
640
665
|
};
|
|
@@ -664,7 +689,7 @@ function shapeProjectView(view, compact, promoter) {
|
|
|
664
689
|
});
|
|
665
690
|
});
|
|
666
691
|
return {
|
|
667
|
-
// The card NODE keeps everything but is still an item DTO (universal +
|
|
692
|
+
// The card NODE keeps everything but is still an item DTO (universal + RS5 reshapes).
|
|
668
693
|
project: shapeItem(view["project"], NO_DROP, false, promoter),
|
|
669
694
|
children: shapeContainerChildren(view["bodyChildren"], PROJECT_CHILD_DROP, compact, promoter),
|
|
670
695
|
headings: headingContainers,
|
|
@@ -676,7 +701,7 @@ function shapeProjectView(view, compact, promoter) {
|
|
|
676
701
|
* key; the area logbook is the bounded query `things logbook --area <ref>`, #346).
|
|
677
702
|
* The same stage-derived bucketing as {@link shapeContainerChildren} minus the
|
|
678
703
|
* logbook split: `anytime`/`someday` are `{items, total?}` records, `upcoming` is
|
|
679
|
-
* the day-block ARRAY (R3,
|
|
704
|
+
* the day-block ARRAY (R3, keyed by `when`) with the trailing `{when: null, items}`
|
|
680
705
|
* resting block for date-less recurring templates (#V8). Inline `total` is stamped
|
|
681
706
|
* downstream by {@link withAreaBucketTotals} (only `anytime` can be capped — the
|
|
682
707
|
* `--area-limit` scope; the scheduled/someday direct to-dos always survive).
|
|
@@ -685,8 +710,9 @@ function shapeAreaChildren(members, drop, compact, promoter) {
|
|
|
685
710
|
const { anytime, upcoming, someday } = rebucketChildren(members, drop, compact, promoter);
|
|
686
711
|
return {
|
|
687
712
|
anytime: bucketRecord(anytime),
|
|
688
|
-
// The day-block ARRAY
|
|
689
|
-
|
|
713
|
+
// The day-block ARRAY (`{when, items}`, `when: null` the resting block #V8);
|
|
714
|
+
// `rebucketChildren` already keys each block by `when`.
|
|
715
|
+
upcoming,
|
|
690
716
|
someday: bucketRecord(someday),
|
|
691
717
|
};
|
|
692
718
|
}
|
|
@@ -803,7 +829,7 @@ function shapeUpcomingView(items, compact, promoter) {
|
|
|
803
829
|
datedByKey.set(key, []);
|
|
804
830
|
datedOrder.push(key);
|
|
805
831
|
}
|
|
806
|
-
//
|
|
832
|
+
// RS7 — inside a dated block the block states the date, so a member whose
|
|
807
833
|
// `when` equals it drops it (a scheduled row's when IS the key). A forecast
|
|
808
834
|
// row has no `when`; a horizon-projected row whose `when` diverges keeps it.
|
|
809
835
|
if (shaped !== null && typeof shaped === "object" && shaped["when"] === key) {
|
|
@@ -938,9 +964,9 @@ function shapeSections(sections, drop, compact, promoter) {
|
|
|
938
964
|
* The flat, mixed-provenance list kinds mapped to their drop spec. Only the
|
|
939
965
|
* stage-PURE catalogues (inbox/someday/logbook/trash; the section-based `anytime`
|
|
940
966
|
* is pure too, handled via shapeSections below) drop the bucket-implied `stage`.
|
|
941
|
-
* The mixed/derived surfaces (search/changes/projects) keep it. The global
|
|
967
|
+
* The mixed/derived surfaces (search/changes/deadlines/projects) keep it. The global
|
|
942
968
|
* `upcoming` view is NOT here — it reshapes into `data.sections` day blocks
|
|
943
|
-
* ({@link shapeUpcomingView}), keeping `stage` (
|
|
969
|
+
* ({@link shapeUpcomingView}), keeping `stage` (RS5.2: stage-mixed — future-dated
|
|
944
970
|
* `upcoming` rows beside deadline-forecast `anytime`/`someday` ones).
|
|
945
971
|
*/
|
|
946
972
|
const FLAT_LIST_DROP = new Map([
|
|
@@ -949,11 +975,13 @@ const FLAT_LIST_DROP = new Map([
|
|
|
949
975
|
["trash", { stage: true }],
|
|
950
976
|
["changes", NO_DROP],
|
|
951
977
|
["search", NO_DROP],
|
|
978
|
+
// deadlines is stage-MIXED (to-dos + projects, deadline-ordered) — keep `stage`.
|
|
979
|
+
["deadlines", NO_DROP],
|
|
952
980
|
["projects", NO_DROP],
|
|
953
981
|
]);
|
|
954
982
|
/**
|
|
955
|
-
* Apply the universal reshapes +
|
|
956
|
-
* `kind`. `full` forces the FULL tier (
|
|
983
|
+
* Apply the universal reshapes + RS1 + RS2 + RS5 to a read payload for one view
|
|
984
|
+
* `kind`. `full` forces the FULL tier (RS2 default-pruning off, everything else
|
|
957
985
|
* applied); an unrecognized kind passes through unchanged. The input is never
|
|
958
986
|
* mutated (shallow copies throughout), so the human-render path keeps the full
|
|
959
987
|
* entities.
|
|
@@ -1031,9 +1059,10 @@ export function candidateRef(type, src) {
|
|
|
1031
1059
|
if (project !== null)
|
|
1032
1060
|
out.project = project;
|
|
1033
1061
|
// stage/when only for the task kinds, and only when the source carries the
|
|
1034
|
-
// materialized lifecycle substrate (`start`) — a thin uuid+title
|
|
1035
|
-
// does not, so the keys stay absent (presence-keyed, like the wire).
|
|
1036
|
-
if ((type === "to-do" || type === "project") &&
|
|
1062
|
+
// materialized lifecycle substrate (`derived.start`) — a thin uuid+title
|
|
1063
|
+
// resolver row does not, so the keys stay absent (presence-keyed, like the wire).
|
|
1064
|
+
if ((type === "to-do" || type === "project") &&
|
|
1065
|
+
typeof (s["derived"] ?? {})["start"] === "string") {
|
|
1037
1066
|
const stage = stageOf(s);
|
|
1038
1067
|
out.stage = stage;
|
|
1039
1068
|
const when = whenOf(s, stage);
|