things-api 0.13.0 → 0.15.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 +3 -3
- package/dist/cli/commands/area.d.ts +5 -5
- package/dist/cli/commands/area.js +13 -8
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/project.js +2 -2
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.js +94 -16
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/show.js +6 -2
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.js +2 -2
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +136 -70
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/glyphs.d.ts +9 -2
- package/dist/cli/glyphs.js +20 -15
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +4 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/read-driver.d.ts +45 -9
- package/dist/cli/read-driver.js +39 -16
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +57 -27
- package/dist/cli/render.js +117 -65
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +127 -38
- package/dist/client.js +86 -22
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +35 -34
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +212 -148
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/dates.d.ts +26 -0
- package/dist/model/dates.js +46 -4
- package/dist/model/dates.js.map +1 -1
- package/dist/model/entities.d.ts +61 -39
- 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-filter.d.ts +1 -1
- package/dist/read/area-filter.js +5 -4
- package/dist/read/area-filter.js.map +1 -1
- package/dist/read/area-view.d.ts +0 -11
- package/dist/read/area-view.js +7 -11
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +2 -2
- 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.d.ts +34 -19
- package/dist/read/project-view.js +31 -25
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/scope.d.ts +1 -1
- package/dist/read/scope.js +5 -4
- package/dist/read/scope.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 +78 -18
- package/dist/read/shape.js +496 -185
- 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 +31 -13
- package/dist/read/stage.js +28 -11
- package/dist/read/stage.js.map +1 -1
- package/dist/read/truncation.d.ts +75 -2
- package/dist/read/truncation.js +69 -46
- package/dist/read/truncation.js.map +1 -1
- package/dist/read/views.d.ts +81 -18
- package/dist/read/views.js +136 -17
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +6 -0
- package/dist/surface-copy.js +6 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/write/batch.js +0 -1
- 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/commands.d.ts +25 -1
- package/dist/write/commands.js +350 -134
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.js +36 -16
- package/dist/write/guards.js.map +1 -1
- package/dist/write/move.d.ts +26 -5
- package/dist/write/move.js +358 -2
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +50 -19
- package/dist/write/operations.js +3 -2
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.js +28 -7
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +89 -28
- package/dist/write/pre-state.js +197 -19
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reorder.js +52 -5
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +34 -0
- package/dist/write/resolution-timestamps.js +286 -0
- package/dist/write/resolution-timestamps.js.map +1 -0
- package/dist/write/reversibility.js +10 -5
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.js +2 -5
- package/dist/write/scope-guard.js.map +1 -1
- package/dist/write/undo.d.ts +1 -7
- package/dist/write/undo.js +63 -16
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +23 -4
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +210 -9
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +0 -8
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +71 -12
- package/dist/write/verify/delta.js +94 -12
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +19 -70
- package/skills/things-cli/SKILL.md +13 -9
- package/skills/things-cli/references/contracts.md +3 -4
- package/skills/things-cli/references/{model.md → data-model.md} +10 -8
- package/skills/things-cli/references/ordering.md +18 -7
package/dist/client.d.ts
CHANGED
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
import type { AuditWriter } from "./audit/log.ts";
|
|
6
6
|
import { type ThingsApiConfig } from "./config.ts";
|
|
7
7
|
import { type ClockMeta } from "./model/clock.ts";
|
|
8
|
-
import { type Truncation } from "./contracts.ts";
|
|
8
|
+
import { type GroupBlock, type Truncation } from "./contracts.ts";
|
|
9
9
|
import { type FingerprintStatus, type SchemaStatus } from "./db/fingerprint.ts";
|
|
10
10
|
import type { AnyTask, Area, Project, Tag } from "./model/entities.ts";
|
|
11
11
|
import type { RefPromoter } from "./read/shape.ts";
|
|
12
12
|
import { type AreaView } from "./read/area-view.ts";
|
|
13
|
+
import { type LogState } from "./read/log-boundary.ts";
|
|
13
14
|
import { type ProjectView } from "./read/project-view.ts";
|
|
14
15
|
import { type Snapshot } from "./read/snapshot.ts";
|
|
15
16
|
import { type ShowTarget } from "./read/show-target.ts";
|
|
16
|
-
import { type ChangedItem, type InboxFilter, type LiteSearchResult, type ListItem, type LogbookFilter, type SearchOptions, type SearchResultItem, type SidebarSection, type SomedayFilter, type TodayFilter, type TodayView, type UpcomingFilter, type ViewFilter } from "./read/views.ts";
|
|
17
|
+
import { type ChangedItem, type DeadlinesFilter, type InboxFilter, type LiteSearchResult, type ListItem, type LogbookFilter, type SearchOptions, type SearchResultItem, type SidebarSection, type SomedayFilter, type TodayFilter, type TodayView, type UpcomingFilter, type ViewFilter } from "./read/views.ts";
|
|
18
|
+
import { type TodayBucketTotals, type AreaBucketTotals, type SectionTotals, type UpcomingBlockTotals } from "./read/truncation.ts";
|
|
17
19
|
import { type AreaScopedRead, type ViewFilterMeta } from "./read/area-filter.ts";
|
|
18
20
|
import { type ScopeMeta } from "./read/scope.ts";
|
|
19
|
-
import type { AreaAddParams, AreaUpdateParams, ContainerRef, ProjectCancelParams, OperationKind, OperationParamsMap, ProjectAddParams, ProjectCompleteParams, ProjectAddRepeatingParams, ProjectUpdateParams, RepeatRuleParams, ReorderParams, TagAddParams, TagUpdateParams, HeadingArchiveParams, HeadingUnarchiveParams, HeadingPlacement,
|
|
21
|
+
import type { AreaAddParams, AreaUpdateParams, ContainerRef, ProjectCancelParams, OperationKind, OperationParamsMap, ProjectAddParams, ProjectCompleteParams, ProjectAddRepeatingParams, ProjectUpdateParams, RepeatRuleParams, ReorderParams, TagAddParams, TagUpdateParams, HeadingArchiveParams, HeadingUnarchiveParams, HeadingPlacement, TodoAddParams, TodoMoveParams, TodoUpdateParams } from "./write/operations.ts";
|
|
20
22
|
import { type MutationResult, type WriteOptions } from "./write/pipeline.ts";
|
|
21
23
|
import { type BatchItemResult, type BatchOp, type BatchOptions, type BatchResult } from "./write/batch.ts";
|
|
22
24
|
import { type EnvironmentTracker } from "./write/environment.ts";
|
|
@@ -99,33 +101,74 @@ export interface BoundedList<T> {
|
|
|
99
101
|
filter?: ViewFilterMeta;
|
|
100
102
|
}
|
|
101
103
|
/**
|
|
102
|
-
* A bounded
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
104
|
+
* A bounded Logbook list: the shown logged rows plus the view-level log-move
|
|
105
|
+
* cadence fact ({@link LogState}) — surfaced as `meta.logging`, rendered as the
|
|
106
|
+
* card header on a TTY. A sibling of `filter`, never part of the JSON `data`.
|
|
107
|
+
*/
|
|
108
|
+
export interface BoundedLogbook extends BoundedList<ListItem> {
|
|
109
|
+
logging: LogState;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A bounded global `upcoming` list: the flat {@link truncateList} slice (rendered
|
|
113
|
+
* as-is, grouped by day in the renderer) plus `upcomingTotals` — the PRE-cap
|
|
114
|
+
* per-day-block sizes keyed by `when`, from which each `data.sections` day block's
|
|
115
|
+
* inline `total` derives (present iff the flat row cap straddled that day, R1).
|
|
116
|
+
*/
|
|
117
|
+
export interface BoundedUpcomingList extends BoundedList<ListItem> {
|
|
118
|
+
/** Pre-cap day-block sizes for the wire's inline `total` (see {@link UpcomingBlockTotals}). */
|
|
119
|
+
upcomingTotals: UpcomingBlockTotals;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A bounded Today view: `view` is the shown split (capped in render order —
|
|
123
|
+
* Today, then This Evening), `truncation` the exact global counts, and `totals`
|
|
124
|
+
* the PRE-cap Today/This-Evening bucket sizes — the renderer keeps This Evening
|
|
125
|
+
* honest under the single global cap, and each `children` bucket's inline `total`
|
|
126
|
+
* (present iff capped, read-shape v2 R1) derives from them.
|
|
106
127
|
*/
|
|
107
128
|
export interface BoundedTodayView {
|
|
108
129
|
view: TodayView;
|
|
109
130
|
truncation: Truncation;
|
|
131
|
+
/** Pre-cap Today / This-Evening bucket sizes (see {@link TodayBucketTotals}). */
|
|
132
|
+
totals: TodayBucketTotals;
|
|
110
133
|
/** The active `area` scope, when one was applied (surfaced as `meta.filter`). */
|
|
111
134
|
filter?: ViewFilterMeta;
|
|
112
135
|
}
|
|
113
136
|
/**
|
|
114
137
|
* A bounded sidebar catalogue (anytime/someday): `view` is the
|
|
115
|
-
* per-block-capped sections and `truncation` the
|
|
116
|
-
*
|
|
117
|
-
*
|
|
138
|
+
* per-block-capped sections and `truncation` the WHOLE-VIEW completeness rollup
|
|
139
|
+
* (`{shown,total,limit,truncated}`). Per-section completeness rides its inline
|
|
140
|
+
* `total` on the wire (stamped from `sectionTotals`, R1); `blocks` carries the
|
|
141
|
+
* identity-bearing per-block counts (project blocks nested under their area/loose
|
|
142
|
+
* block) as INTERNAL render plumbing only — the grouped renderers' "… N more"
|
|
143
|
+
* drill-downs — never on the wire (doctrine v2 PR 5: the sidecar retired).
|
|
118
144
|
*/
|
|
119
145
|
export interface BoundedSectionsView {
|
|
120
146
|
view: SidebarSection[];
|
|
121
147
|
truncation: Truncation;
|
|
148
|
+
/** Per-block render detail (TTY drill-downs); never serialized. */
|
|
149
|
+
blocks: GroupBlock[];
|
|
150
|
+
/**
|
|
151
|
+
* Pre-cap per-section sizes keyed by area uuid (`null` = loose) — the inline
|
|
152
|
+
* `total` a consumer stamps on each capped section ({@link withSectionTotals},
|
|
153
|
+
* R1), so completeness is answerable locally with no `blocks[]` sidecar.
|
|
154
|
+
*/
|
|
155
|
+
sectionTotals: SectionTotals;
|
|
122
156
|
/** The active `area` scope, when one was applied (surfaced as `meta.filter`). */
|
|
123
157
|
filter?: ViewFilterMeta;
|
|
124
158
|
}
|
|
125
|
-
/** A bounded composite area card: the per-section-capped view and the
|
|
159
|
+
/** A bounded composite area card: the per-section-capped view and the whole-view rollup. */
|
|
126
160
|
export interface BoundedAreaView {
|
|
127
161
|
view: AreaView;
|
|
128
162
|
truncation: Truncation;
|
|
163
|
+
/** Per-block render detail (the `area show` "… N more" footers); never serialized. */
|
|
164
|
+
blocks: GroupBlock[];
|
|
165
|
+
/**
|
|
166
|
+
* Pre-cap scope sizes (read-shape v2 R1, PR 3) — the direct-to-dos and
|
|
167
|
+
* project-rows counts a consumer stamps as each capped scope's inline `total`
|
|
168
|
+
* ({@link withAreaBucketTotals}), so completeness is answerable locally with no
|
|
169
|
+
* `meta.truncation.blocks[]` sidecar.
|
|
170
|
+
*/
|
|
171
|
+
totals: AreaBucketTotals;
|
|
129
172
|
/**
|
|
130
173
|
* A resolution disclosure — present ONLY on the `loose` pseudo-area read when
|
|
131
174
|
* a real area shadows the reserved word (names it, by uuid, for targeting).
|
|
@@ -141,6 +184,16 @@ export interface BoundedAreaView {
|
|
|
141
184
|
*/
|
|
142
185
|
loggedCount?: number;
|
|
143
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolution-timestamp flags (plan §2): an ISO date (`2025-01-15`) or datetime
|
|
189
|
+
* (`2025-01-15T09:30`). `completedAt` sets the completion timestamp (also the
|
|
190
|
+
* "Completed on" stamp for canceled items); `createdAt` backdates creation. A
|
|
191
|
+
* date-only value normalizes to noon in the effective zone (§5).
|
|
192
|
+
*/
|
|
193
|
+
export interface ResolutionDates {
|
|
194
|
+
createdAt?: string;
|
|
195
|
+
completedAt?: string;
|
|
196
|
+
}
|
|
144
197
|
export interface ThingsClient {
|
|
145
198
|
dbPath: string;
|
|
146
199
|
config: ThingsApiConfig;
|
|
@@ -192,11 +245,11 @@ export interface ThingsClient {
|
|
|
192
245
|
refPromoter(): RefPromoter;
|
|
193
246
|
read: {
|
|
194
247
|
/**
|
|
195
|
-
* The Today list
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
248
|
+
* The Today list (Today + This Evening split) plus the whole-view `counts`
|
|
249
|
+
* aggregate (the app's sidebar count), bounded to `limit` rows (default 50)
|
|
250
|
+
* counted in render order — Today first, then This Evening. `all`/`limit:
|
|
251
|
+
* null` returns every row; `totals` carries the pre-cap Today/This-Evening
|
|
252
|
+
* bucket sizes (the two `children` bucket records project from the split).
|
|
200
253
|
*/
|
|
201
254
|
today(options?: TodayFilter & ListBound & ClockScopedRead & AreaScopedRead): BoundedTodayView;
|
|
202
255
|
/** Inbox captures, bounded (default 50). */
|
|
@@ -208,8 +261,8 @@ export interface ThingsClient {
|
|
|
208
261
|
* catalogue to one area (its rows survive; the rest drop).
|
|
209
262
|
*/
|
|
210
263
|
anytime(options?: ViewFilter & GroupedBound & ClockScopedRead & AreaScopedRead): BoundedSectionsView;
|
|
211
|
-
/** Future-scheduled items in date order, bounded (default 50). */
|
|
212
|
-
upcoming(options?: UpcomingFilter & ListBound & ClockScopedRead & AreaScopedRead):
|
|
264
|
+
/** Future-scheduled items in date order, bounded (default 50); day-block totals for R1. */
|
|
265
|
+
upcoming(options?: UpcomingFilter & ListBound & ClockScopedRead & AreaScopedRead): BoundedUpcomingList;
|
|
213
266
|
/**
|
|
214
267
|
* Someday catalogue: `areaLimit` (default 30) caps each group; with
|
|
215
268
|
* `activeProjectItems`, `projectLimit` (default: every item) caps each
|
|
@@ -217,8 +270,13 @@ export interface ThingsClient {
|
|
|
217
270
|
* the catalogue to one area.
|
|
218
271
|
*/
|
|
219
272
|
someday(options?: SomedayFilter & GroupedBound & ClockScopedRead & AreaScopedRead): BoundedSectionsView;
|
|
220
|
-
/**
|
|
221
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Logbook entries (most recent first), bounded (default 50), plus the
|
|
275
|
+
* view-level log-move cadence fact ({@link LogState}) surfaced as
|
|
276
|
+
* `meta.logging` — the "Move completed items to Logbook" setting in CC's own
|
|
277
|
+
* words, and the last-logged instant under Manually.
|
|
278
|
+
*/
|
|
279
|
+
logbook(options?: Omit<LogbookFilter, "limit"> & ListBound & ClockScopedRead): BoundedLogbook;
|
|
222
280
|
/** Trashed items (most recently modified first), bounded (default 50). */
|
|
223
281
|
trash(options?: ListBound & ClockScopedRead): BoundedList<ListItem>;
|
|
224
282
|
/**
|
|
@@ -268,6 +326,16 @@ export interface ThingsClient {
|
|
|
268
326
|
changes(options: {
|
|
269
327
|
since: Date;
|
|
270
328
|
} & ListBound & ClockScopedRead): BoundedList<ChangedItem>;
|
|
329
|
+
/**
|
|
330
|
+
* The deadline-horizon view: every live to-do AND project carrying a
|
|
331
|
+
* deadline, ordered deadline ASC (most-overdue first), then todayIndex, then
|
|
332
|
+
* uuid; deadline-bearing repeating templates project at their next
|
|
333
|
+
* occurrence's deadline. Bounded (default 50). `todayOnly` restricts to
|
|
334
|
+
* current Today members (evening-inclusive); `overdue` keeps only open,
|
|
335
|
+
* past-deadline rows (and excludes projections); `project`/`area` scope to a
|
|
336
|
+
* container; the tag filters compose.
|
|
337
|
+
*/
|
|
338
|
+
deadlines(options?: DeadlinesFilter & ListBound & ClockScopedRead): BoundedList<ListItem>;
|
|
271
339
|
byUuid(uuid: string): AnyTask | null;
|
|
272
340
|
/**
|
|
273
341
|
* Classify a loose reference (uuid, >=6-char prefix, share link, or
|
|
@@ -281,9 +349,20 @@ export interface ThingsClient {
|
|
|
281
349
|
/** Generic entry: run any cataloged operation. */
|
|
282
350
|
run<K extends OperationKind>(op: K, params: OperationParamsMap[K], options?: WriteOptions): Promise<MutationResult>;
|
|
283
351
|
addTodo(params: TodoAddParams, options?: WriteOptions): Promise<MutationResult>;
|
|
284
|
-
updateTodo(uuid: string, patch: Omit<TodoUpdateParams, "uuid"
|
|
285
|
-
|
|
286
|
-
|
|
352
|
+
updateTodo(uuid: string, patch: Omit<TodoUpdateParams, "uuid"> & ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
353
|
+
/**
|
|
354
|
+
* Complete a to-do. With `resolution.completedAt` (ISO date or datetime) the
|
|
355
|
+
* to-do lands completed and BACKDATED — resolving it first if needed, then
|
|
356
|
+
* an AppleScript completion-date write; a date-only value normalizes to noon
|
|
357
|
+
* in the effective zone (§5). Multi-leg, disclosed in the result / dry-run.
|
|
358
|
+
*/
|
|
359
|
+
completeTodo(uuid: string, resolution?: ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
360
|
+
/**
|
|
361
|
+
* Cancel a to-do. With `resolution.completedAt` it lands canceled and
|
|
362
|
+
* BACKDATED via the certified flip-dance (→completed · AS backdate · →canceled)
|
|
363
|
+
* — the only headless path that keeps a canceled item canceled.
|
|
364
|
+
*/
|
|
365
|
+
cancelTodo(uuid: string, resolution?: ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
287
366
|
reopenTodo(uuid: string, options?: WriteOptions): Promise<MutationResult>;
|
|
288
367
|
moveTodo(uuid: string, dest: Omit<TodoMoveParams, "uuid">, options?: WriteOptions): Promise<MutationResult>;
|
|
289
368
|
/**
|
|
@@ -303,6 +382,18 @@ export interface ThingsClient {
|
|
|
303
382
|
* fail closed.
|
|
304
383
|
*/
|
|
305
384
|
reorderTodos(request: ReorderRequest, options?: WriteOptions): Promise<MoveResult>;
|
|
385
|
+
/**
|
|
386
|
+
* The ONE reorder verb (`things reorder`). Rearranges a single-KIND set IN
|
|
387
|
+
* PLACE — to-dos, projects, headings, OR sidebar areas — dispatching the
|
|
388
|
+
* protocol by kind: the index/day/view engine for to-dos and projects (a
|
|
389
|
+
* to-do+project set intermixes only on the shared Today/Evening/day axes), the
|
|
390
|
+
* certified heading-block wire for a project's headings (archived headings
|
|
391
|
+
* reorderable, reopens disclosed — #V11), and the sidebar-drag driver for
|
|
392
|
+
* areas. A mixed-kind set, a cross-container set, and a non-member anchor each
|
|
393
|
+
* get one precise refusal. Bare (no position) assembles the block at the
|
|
394
|
+
* earliest movee's slot; --start/--end/--before/--after position it.
|
|
395
|
+
*/
|
|
396
|
+
reorderAny(request: ReorderRequest, options?: WriteOptions): Promise<MoveResult>;
|
|
306
397
|
/** Replace the full tag set (an empty list clears all tags). */
|
|
307
398
|
setTags(uuid: string, tags: string[], options?: WriteOptions): Promise<MutationResult>;
|
|
308
399
|
/** Merge: current direct tags + new ones, then replace. */
|
|
@@ -321,17 +412,6 @@ export interface ThingsClient {
|
|
|
321
412
|
* Reversible with `undo`. Force a path with `vector: "shortcuts" | "url-scheme"`.
|
|
322
413
|
*/
|
|
323
414
|
clearReminder(uuid: string, options?: WriteOptions): Promise<MutationResult>;
|
|
324
|
-
/**
|
|
325
|
-
* Rewrite a to-do's completion and/or creation timestamp to noon (local)
|
|
326
|
-
* on the given date. Completion requires the to-do to be completed or
|
|
327
|
-
* canceled already.
|
|
328
|
-
*/
|
|
329
|
-
backdateTodo(uuid: string, dates: Omit<TodoBackdateParams, "uuid">, options?: WriteOptions): Promise<MutationResult>;
|
|
330
|
-
/**
|
|
331
|
-
* Create a to-do directly in the Logbook, completed, with backdated
|
|
332
|
-
* completion (and optionally creation) timestamps.
|
|
333
|
-
*/
|
|
334
|
-
addLoggedTodo(params: TodoAddLoggedParams, options?: WriteOptions): Promise<MutationResult>;
|
|
335
415
|
/**
|
|
336
416
|
* Create a heading inside an EXISTING project; the new heading's uuid is
|
|
337
417
|
* on the result. Delivered through the Things proxy shortcuts (run
|
|
@@ -391,8 +471,9 @@ export interface ThingsClient {
|
|
|
391
471
|
*/
|
|
392
472
|
editChecklist(uuid: string, edit: ChecklistEdit, options?: WriteOptions): Promise<MutationResult>;
|
|
393
473
|
addProject(params: ProjectAddParams, options?: WriteOptions): Promise<MutationResult>;
|
|
394
|
-
updateProject(uuid: string, patch: Omit<ProjectUpdateParams, "uuid"
|
|
395
|
-
|
|
474
|
+
updateProject(uuid: string, patch: Omit<ProjectUpdateParams, "uuid"> & ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
475
|
+
/** Complete a project; `resolution.completedAt` backdates it (see completeTodo). */
|
|
476
|
+
completeProject(uuid: string, policy: Pick<ProjectCompleteParams, "children"> & ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
396
477
|
/** Move a project to another area. */
|
|
397
478
|
moveProject(uuid: string, area: ContainerRef, options?: WriteOptions): Promise<MutationResult>;
|
|
398
479
|
/** Detach a project from its current area. */
|
|
@@ -405,8 +486,8 @@ export interface ThingsClient {
|
|
|
405
486
|
moveProjects(request: ProjectMoveRequest, options?: WriteOptions): Promise<MoveResult>;
|
|
406
487
|
/** Reorder projects IN PLACE among their siblings (spec §4). */
|
|
407
488
|
reorderProjects(request: ReorderRequest, options?: WriteOptions): Promise<MoveResult>;
|
|
408
|
-
/** Cancel a project — open children are canceled with it, so the children policy is mandatory. */
|
|
409
|
-
cancelProject(uuid: string, policy: Pick<ProjectCancelParams, "children"
|
|
489
|
+
/** Cancel a project — open children are canceled with it, so the children policy is mandatory. `resolution.completedAt` backdates it (flip-dance). */
|
|
490
|
+
cancelProject(uuid: string, policy: Pick<ProjectCancelParams, "children"> & ResolutionDates, options?: WriteOptions): Promise<MutationResult>;
|
|
410
491
|
/**
|
|
411
492
|
* Reopen a completed/canceled project. Children stay resolved unless
|
|
412
493
|
* restoreChildren reopens the ones resolved together with the project.
|
|
@@ -446,6 +527,14 @@ export interface ThingsClient {
|
|
|
446
527
|
updateTag(target: string, patch: Omit<TagUpdateParams, "target">, options?: WriteOptions): Promise<MutationResult>;
|
|
447
528
|
deleteTag(target: string, options?: WriteOptions): Promise<MutationResult>;
|
|
448
529
|
emptyTrash(options?: WriteOptions): Promise<MutationResult>;
|
|
530
|
+
/**
|
|
531
|
+
* Move completed items into the Logbook now (`log completed now`). The result
|
|
532
|
+
* discloses how many items were logged (`observed.logged`); when nothing is
|
|
533
|
+
* pending it is a clean no-op (`logged: 0`, not an error). IRREVERSIBLE — the
|
|
534
|
+
* log-move boundary cannot be rewound by any official surface, so no undo token
|
|
535
|
+
* is emitted.
|
|
536
|
+
*/
|
|
537
|
+
logNow(options?: WriteOptions): Promise<MutationResult>;
|
|
449
538
|
/**
|
|
450
539
|
* Reorder within Today / This Evening / a project / an area. Partial
|
|
451
540
|
* uuid lists are placed on top; the rest keep their current order.
|
package/dist/client.js
CHANGED
|
@@ -11,12 +11,13 @@ import { byUuid } from "./read/detail.js";
|
|
|
11
11
|
import { makeRefPromoter, resolveAreaUuid, resolveHeadingUuid, resolveProjectUuid, resolveTaskUuidPrefix, } from "./read/queries.js";
|
|
12
12
|
import { areaView } from "./read/area-view.js";
|
|
13
13
|
import { isLooseRef, looseShadowNotice, shadowingLooseArea } from "./read/pseudo-area.js";
|
|
14
|
+
import { logState } from "./read/log-boundary.js";
|
|
14
15
|
import { projectView } from "./read/project-view.js";
|
|
15
16
|
import { snapshotView } from "./read/snapshot.js";
|
|
16
17
|
import { classifyShowTarget } from "./read/show-target.js";
|
|
17
18
|
import { areasView, tagsView } from "./read/tags.js";
|
|
18
|
-
import { anytimeView, areaLoggedCount, changesView, inboxView, liteTitleSearch, logbookView, projectsView, searchView, somedayView, todayView, trashView, upcomingView, } from "./read/views.js";
|
|
19
|
-
import { capAreaSections, previewSections, previewSomedaySections, truncateList, truncateToday, } from "./read/truncation.js";
|
|
19
|
+
import { anytimeView, areaLoggedCount, changesView, deadlinesView, inboxView, liteTitleSearch, logbookView, projectsView, searchView, somedayView, todayView, trashView, upcomingView, } from "./read/views.js";
|
|
20
|
+
import { capAreaSections, previewSections, previewSomedaySections, truncateList, truncateToday, upcomingBlockTotals, } from "./read/truncation.js";
|
|
20
21
|
import { filterListByArea, filterSectionsByArea, filterTodayByArea, resolveAreaFilter, } from "./read/area-filter.js";
|
|
21
22
|
import { filterListByScope, filterSectionsByScope, filterTodayByScope, inScopeItem, namedAreaClause, namedProjectClause, resolveScope, scopeMeta, taskMembershipClause, } from "./read/scope.js";
|
|
22
23
|
import { localToday } from "./model/dates.js";
|
|
@@ -31,8 +32,9 @@ import { runAddHeading, runHeadingArchive, runHeadingUnarchive, } from "./write/
|
|
|
31
32
|
import { runClearReminder } from "./write/clear-reminder.js";
|
|
32
33
|
import { runEditChecklist } from "./write/edit-checklist.js";
|
|
33
34
|
import { runAddRepeatingProject, runMakeRepeatingProject } from "./write/make-repeating-project.js";
|
|
35
|
+
import { runCancelWithDate, runCompleteWithDate, runUpdateDates, } from "./write/resolution-timestamps.js";
|
|
34
36
|
import { runReorder } from "./write/reorder.js";
|
|
35
|
-
import { runInPlaceReorder, runProjectMove, runTodoMove, } from "./write/move.js";
|
|
37
|
+
import { runInPlaceReorder, runProjectMove, runTodoMove, runUniversalReorder, } from "./write/move.js";
|
|
36
38
|
import { readAuditRecords, runUndo } from "./write/undo.js";
|
|
37
39
|
import { runProjectReopen, } from "./write/reopen.js";
|
|
38
40
|
import { defaultVectors } from "./write/vectors/registry.js";
|
|
@@ -182,6 +184,26 @@ export function openThings(options = {}) {
|
|
|
182
184
|
// to the effective zone before dispatch (a no-op without a zone).
|
|
183
185
|
return runMutation(writeDeps, op, params, { ...writeOptions, normalizeWhen: true });
|
|
184
186
|
};
|
|
187
|
+
// update dispatcher: the resolution-timestamp flags route through the
|
|
188
|
+
// multi-leg orchestrator; plain attribute edits stay on `todo/project.update`.
|
|
189
|
+
// When BOTH are present the attribute edit lands first (its own undo), then
|
|
190
|
+
// the timestamp legs — two independently-undoable changes.
|
|
191
|
+
const runUpdate = async (kind, uuid, patch, o) => {
|
|
192
|
+
const { createdAt, completedAt, ...attrs } = patch;
|
|
193
|
+
const op = kind === "project" ? "project.update" : "todo.update";
|
|
194
|
+
if (createdAt === undefined && completedAt === undefined) {
|
|
195
|
+
return run(op, { uuid, ...attrs }, o);
|
|
196
|
+
}
|
|
197
|
+
if (Object.keys(attrs).length > 0) {
|
|
198
|
+
const attrResult = await run(op, { uuid, ...attrs }, o);
|
|
199
|
+
if (attrResult.kind !== "ok" && attrResult.kind !== "dry-run")
|
|
200
|
+
return attrResult;
|
|
201
|
+
}
|
|
202
|
+
return runUpdateDates(writeDeps, kind, uuid, {
|
|
203
|
+
...(createdAt !== undefined && { createdAt }),
|
|
204
|
+
...(completedAt !== undefined && { completedAt }),
|
|
205
|
+
}, o ?? {});
|
|
206
|
+
};
|
|
185
207
|
return {
|
|
186
208
|
dbPath: located.path,
|
|
187
209
|
config,
|
|
@@ -227,8 +249,8 @@ export function openThings(options = {}) {
|
|
|
227
249
|
view = filterTodayByArea(view, target.uuid, localToday(now(), zoneOf(o)));
|
|
228
250
|
filter = { area: target };
|
|
229
251
|
}
|
|
230
|
-
const { data, truncation } = truncateToday(view, listCap(o));
|
|
231
|
-
return { view: data, truncation, ...(filter !== undefined && { filter }) };
|
|
252
|
+
const { data, truncation, totals } = truncateToday(view, listCap(o));
|
|
253
|
+
return { view: data, truncation, totals, ...(filter !== undefined && { filter }) };
|
|
232
254
|
},
|
|
233
255
|
inbox: (o) => {
|
|
234
256
|
let items = inboxView(conn.db, now(), o, zoneOf(o));
|
|
@@ -249,8 +271,14 @@ export function openThings(options = {}) {
|
|
|
249
271
|
sections = filterSectionsByArea(sections, target.uuid);
|
|
250
272
|
filter = { area: target };
|
|
251
273
|
}
|
|
252
|
-
const { data, truncation } = previewSections(sections, groupedCaps(o, AREA_PREVIEW_LIMIT, PROJECT_PREVIEW_LIMIT));
|
|
253
|
-
return {
|
|
274
|
+
const { data, truncation, blocks, totals } = previewSections(sections, groupedCaps(o, AREA_PREVIEW_LIMIT, PROJECT_PREVIEW_LIMIT));
|
|
275
|
+
return {
|
|
276
|
+
view: data,
|
|
277
|
+
truncation,
|
|
278
|
+
blocks,
|
|
279
|
+
sectionTotals: totals,
|
|
280
|
+
...(filter !== undefined && { filter }),
|
|
281
|
+
};
|
|
254
282
|
},
|
|
255
283
|
upcoming: (o) => {
|
|
256
284
|
let items = upcomingView(conn.db, now(), o, zoneOf(o));
|
|
@@ -262,8 +290,11 @@ export function openThings(options = {}) {
|
|
|
262
290
|
items = filterListByArea(items, target.uuid);
|
|
263
291
|
filter = { area: target };
|
|
264
292
|
}
|
|
293
|
+
// Pre-cap per-day-block sizes for the wire's inline `total` (R1): counted
|
|
294
|
+
// over the in-scope, area-filtered stream BEFORE the flat row cap slices it.
|
|
295
|
+
const upcomingTotals = upcomingBlockTotals(items);
|
|
265
296
|
const { data, truncation } = truncateList(items, listCap(o));
|
|
266
|
-
return { items: data, truncation, ...(filter !== undefined && { filter }) };
|
|
297
|
+
return { items: data, truncation, upcomingTotals, ...(filter !== undefined && { filter }) };
|
|
267
298
|
},
|
|
268
299
|
someday: (o) => {
|
|
269
300
|
let sections = somedayView(conn.db, now(), o, zoneOf(o));
|
|
@@ -275,8 +306,14 @@ export function openThings(options = {}) {
|
|
|
275
306
|
sections = filterSectionsByArea(sections, target.uuid);
|
|
276
307
|
filter = { area: target };
|
|
277
308
|
}
|
|
278
|
-
const { data, truncation } = previewSomedaySections(sections, groupedCaps(o, AREA_PREVIEW_LIMIT, null));
|
|
279
|
-
return {
|
|
309
|
+
const { data, truncation, blocks, totals } = previewSomedaySections(sections, groupedCaps(o, AREA_PREVIEW_LIMIT, null));
|
|
310
|
+
return {
|
|
311
|
+
view: data,
|
|
312
|
+
truncation,
|
|
313
|
+
blocks,
|
|
314
|
+
sectionTotals: totals,
|
|
315
|
+
...(filter !== undefined && { filter }),
|
|
316
|
+
};
|
|
280
317
|
},
|
|
281
318
|
logbook: (o) => {
|
|
282
319
|
// The bound is the truncation cap; the underlying query stays unbounded
|
|
@@ -293,7 +330,12 @@ export function openThings(options = {}) {
|
|
|
293
330
|
// only resolves the target for the additive `filter` annotation so the
|
|
294
331
|
// meta shape matches the post-filtered views.
|
|
295
332
|
const areaFilter = o?.area !== undefined ? { area: areaFilterTarget(o.area) } : undefined;
|
|
296
|
-
return {
|
|
333
|
+
return {
|
|
334
|
+
items: data,
|
|
335
|
+
truncation,
|
|
336
|
+
logging: logState(conn.db),
|
|
337
|
+
...(areaFilter !== undefined && { filter: areaFilter }),
|
|
338
|
+
};
|
|
297
339
|
},
|
|
298
340
|
trash: (o) => {
|
|
299
341
|
let items = trashView(conn.db, now(), { limit: null }, zoneOf(o));
|
|
@@ -340,7 +382,7 @@ export function openThings(options = {}) {
|
|
|
340
382
|
: { where: "0", binds: [] };
|
|
341
383
|
resolveAreaUuid(conn.db, ref, { scopeWhere: clause.where, scopeBinds: clause.binds });
|
|
342
384
|
}
|
|
343
|
-
const { data, truncation } = capAreaSections(areaView(conn.db, ref, now(), o ?? {}, zoneOf(o)), groupedCaps(o, AREA_PREVIEW_LIMIT, AREA_PREVIEW_LIMIT), now(), zoneOf(o));
|
|
385
|
+
const { data, truncation, blocks, totals } = capAreaSections(areaView(conn.db, ref, now(), o ?? {}, zoneOf(o)), groupedCaps(o, AREA_PREVIEW_LIMIT, AREA_PREVIEW_LIMIT), now(), zoneOf(o));
|
|
344
386
|
// Reserved-word disclosure: `loose` ALWAYS wins over a real area named
|
|
345
387
|
// "Loose"; when one shadows, name it (by uuid) so it stays targetable.
|
|
346
388
|
const shadow = isLooseRef(ref) ? shadowingLooseArea(conn.db) : undefined;
|
|
@@ -353,6 +395,8 @@ export function openThings(options = {}) {
|
|
|
353
395
|
return {
|
|
354
396
|
view: data,
|
|
355
397
|
truncation,
|
|
398
|
+
blocks,
|
|
399
|
+
totals,
|
|
356
400
|
...(shadow !== undefined && { notice: looseShadowNotice(shadow) }),
|
|
357
401
|
...(loggedCount !== undefined && { loggedCount }),
|
|
358
402
|
};
|
|
@@ -396,11 +440,25 @@ export function openThings(options = {}) {
|
|
|
396
440
|
const { data, truncation } = truncateList(items, listCap(o));
|
|
397
441
|
return { items: data, truncation };
|
|
398
442
|
},
|
|
443
|
+
deadlines: (o) => {
|
|
444
|
+
const { limit: _limit, ...filter } = o ?? {};
|
|
445
|
+
// The view is computed UNBOUNDED (project/area/tag/overdue/today scoping
|
|
446
|
+
// happens inside it); the whole-view truncation is applied here so the
|
|
447
|
+
// total behind the cut is honest.
|
|
448
|
+
let items = deadlinesView(conn.db, now(), filter, zoneOf(o));
|
|
449
|
+
// A container jail is an additive post-filter (parity with search): keep
|
|
450
|
+
// only in-scope rows so an out-of-scope deadline never leaks.
|
|
451
|
+
if (scope !== undefined)
|
|
452
|
+
items = items.filter((i) => inScopeItem(i, scope));
|
|
453
|
+
const { data, truncation } = truncateList(items, listCap(o));
|
|
454
|
+
return { items: data, truncation };
|
|
455
|
+
},
|
|
399
456
|
showTarget: (ref) => classifyShowTarget(conn.db, ref, scope),
|
|
400
457
|
byUuid: (uuid) => {
|
|
401
458
|
// Prefix-friendly: unknown refs keep the null contract; ambiguity throws.
|
|
402
|
-
// The injected clock gates
|
|
403
|
-
// consumer's own today (a pinned-clock/lab run reads
|
|
459
|
+
// The injected clock gates the today/evening markers (and reminder
|
|
460
|
+
// liveness) under the consumer's own today (a pinned-clock/lab run reads
|
|
461
|
+
// honestly). Under a
|
|
404
462
|
// scope, an out-of-scope uuid resolves to not-found → null (parity).
|
|
405
463
|
try {
|
|
406
464
|
return byUuid(conn.db, resolveTaskUuidPrefix(conn.db, uuid, "to-do", scopeClauses?.task), now(), defaultZone);
|
|
@@ -423,13 +481,14 @@ export function openThings(options = {}) {
|
|
|
423
481
|
write: {
|
|
424
482
|
run,
|
|
425
483
|
addTodo: (params, o) => run("todo.add", params, o),
|
|
426
|
-
updateTodo: (uuid, patch, o) =>
|
|
427
|
-
completeTodo: (uuid, o) =>
|
|
428
|
-
cancelTodo: (uuid, o) =>
|
|
484
|
+
updateTodo: (uuid, patch, o) => runUpdate("todo", uuid, patch, o),
|
|
485
|
+
completeTodo: (uuid, resolution, o) => runCompleteWithDate(writeDeps, "todo", uuid, resolution ?? {}, o ?? {}),
|
|
486
|
+
cancelTodo: (uuid, resolution, o) => runCancelWithDate(writeDeps, "todo", uuid, resolution ?? {}, o ?? {}),
|
|
429
487
|
reopenTodo: (uuid, o) => run("todo.reopen", { uuid }, o),
|
|
430
488
|
moveTodo: (uuid, dest, o) => run("todo.move", { uuid, ...dest }, o),
|
|
431
489
|
moveTodos: (request, o) => runTodoMove(writeDeps, request, o ?? {}),
|
|
432
490
|
reorderTodos: (request, o) => runInPlaceReorder(writeDeps, "todo.move", request, o ?? {}),
|
|
491
|
+
reorderAny: (request, o) => runUniversalReorder(writeDeps, request, o ?? {}),
|
|
433
492
|
setTags: (uuid, tags, o) => run("todo.set-tags", { uuid, tags }, o),
|
|
434
493
|
addTags(uuid, tags, o) {
|
|
435
494
|
const current = byUuid(conn.db, uuid);
|
|
@@ -441,8 +500,6 @@ export function openThings(options = {}) {
|
|
|
441
500
|
deleteTodo: (uuid, o) => run("todo.delete", { uuid }, o),
|
|
442
501
|
duplicateTodo: (uuid, o) => run("todo.duplicate", { uuid }, o),
|
|
443
502
|
restoreTodo: (uuid, o) => run("todo.restore", { uuid }, o),
|
|
444
|
-
backdateTodo: (uuid, dates, o) => run("todo.backdate", { uuid, ...dates }, o),
|
|
445
|
-
addLoggedTodo: (params, o) => run("todo.add-logged", params, o),
|
|
446
503
|
addHeading: (project, title, placement, o) => runAddHeading(writeDeps, project, title, placement, o ?? {}),
|
|
447
504
|
renameHeading: (uuid, title, o) => run("project.rename-heading", { uuid, title }, o),
|
|
448
505
|
moveHeading: (project, headings, placement, o) => run("project.move-heading", { project, headings, placement }, o),
|
|
@@ -454,13 +511,19 @@ export function openThings(options = {}) {
|
|
|
454
511
|
detachTodo: (uuid, o) => run("todo.move", { uuid, loose: true }, o),
|
|
455
512
|
editChecklist: (uuid, edit, o) => runEditChecklist(writeDeps, uuid, edit, o ?? {}),
|
|
456
513
|
addProject: (params, o) => run("project.add", params, o),
|
|
457
|
-
updateProject: (uuid, patch, o) =>
|
|
458
|
-
completeProject: (uuid, policy, o) =>
|
|
514
|
+
updateProject: (uuid, patch, o) => runUpdate("project", uuid, patch, o),
|
|
515
|
+
completeProject: (uuid, policy, o) => runCompleteWithDate(writeDeps, "project", uuid, {
|
|
516
|
+
children: policy.children,
|
|
517
|
+
...(policy.completedAt !== undefined && { completedAt: policy.completedAt }),
|
|
518
|
+
}, o ?? {}),
|
|
459
519
|
moveProject: (uuid, area, o) => run("project.move", { uuid, area }, o),
|
|
460
520
|
detachProject: (uuid, o) => run("project.move", { uuid, noArea: true }, o),
|
|
461
521
|
moveProjects: (request, o) => runProjectMove(writeDeps, request, o ?? {}),
|
|
462
522
|
reorderProjects: (request, o) => runInPlaceReorder(writeDeps, "project.move", request, o ?? {}),
|
|
463
|
-
cancelProject: (uuid, policy, o) =>
|
|
523
|
+
cancelProject: (uuid, policy, o) => runCancelWithDate(writeDeps, "project", uuid, {
|
|
524
|
+
children: policy.children,
|
|
525
|
+
...(policy.completedAt !== undefined && { completedAt: policy.completedAt }),
|
|
526
|
+
}, o ?? {}),
|
|
464
527
|
reopenProject: (uuid, o) => runProjectReopen(writeDeps, uuid, o ?? {}),
|
|
465
528
|
restoreProject: (uuid, o) => run("project.restore", { uuid }, o),
|
|
466
529
|
duplicateProject: (uuid, o) => run("project.duplicate", { uuid }, o),
|
|
@@ -481,6 +544,7 @@ export function openThings(options = {}) {
|
|
|
481
544
|
updateTag: (target, patch, o) => run("tag.update", { target, ...patch }, o),
|
|
482
545
|
deleteTag: (target, o) => run("tag.delete", { target }, o),
|
|
483
546
|
emptyTrash: (o) => run("trash.empty", {}, o),
|
|
547
|
+
logNow: (o) => run("log-now", {}, o),
|
|
484
548
|
reorder: (params, o) => runReorder(writeDeps, params, o ?? {}),
|
|
485
549
|
batch: (ops, o, onResult) => runBatch(writeDeps, ops, o ?? {}, onResult),
|
|
486
550
|
undo: (o, onItem) => runUndo(writeDeps, auditDir(env), o ?? {}, onItem),
|