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
package/dist/read/show-target.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveAreaUuid, resolveTaskUuidPrefix, stripThingsUri } from "./queries.js";
|
|
1
|
+
import { resolveAreaUuid, resolveProjectUuid, resolveTaskUuidPrefix, stripThingsUri, } from "./queries.js";
|
|
2
2
|
export function classifyShowTarget(db, ref) {
|
|
3
3
|
const stripped = stripThingsUri(ref);
|
|
4
4
|
try {
|
|
@@ -23,9 +23,18 @@ export function classifyShowTarget(db, ref) {
|
|
|
23
23
|
throw err;
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
return { kind: "area", uuid: resolveAreaUuid(db, stripped) };
|
|
26
|
+
return { kind: "area", uuid: resolveAreaUuid(db, stripped, { prefixTier: false }) };
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
29
|
+
// fall through to project-name resolution
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return { kind: "project", uuid: resolveProjectUuid(db, stripped, { prefixTier: false }) };
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
// An ambiguous project name lists its candidates — surface that verbatim.
|
|
36
|
+
if (err instanceof RangeError && err.message.includes("ambiguous"))
|
|
37
|
+
throw err;
|
|
29
38
|
throw new RangeError(`no to-do, project, or area matches "${ref}" (tags and checklist items have no show view)`);
|
|
30
39
|
}
|
|
31
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"show-target.js","sourceRoot":"","sources":["../../src/read/show-target.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"show-target.js","sourceRoot":"","sources":["../../src/read/show-target.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,MAAM,UAAU,kBAAkB,CAAC,EAAgB,EAAE,GAAW;IAC9D,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,IAAI,CAGjF,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACnB,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;gBACtB,MAAM,IAAI,UAAU,CAAC,WAAW,IAAI,oCAAoC,CAAC,CAAC;YAC5E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAClE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,oEAAoE;QACpE,sEAAsE;QACtE,oCAAoC;QACpC,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9E,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC;YAAE,MAAM,GAAG,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0EAA0E;QAC1E,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9E,MAAM,IAAI,UAAU,CAClB,uCAAuC,GAAG,gDAAgD,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar-order arrangement for the grouped views (anytime/someday). Takes an
|
|
3
|
+
* already-materialized flat list and reproduces the UI's sidebar layout:
|
|
4
|
+
* the area-less block first, then each area by its sidebar index; inside a
|
|
5
|
+
* block, direct to-dos first, then each project followed by its members.
|
|
6
|
+
*
|
|
7
|
+
* Type-only imports from views.ts (ListItem, SidebarSection) are erased at
|
|
8
|
+
* runtime, so the sole runtime edge is views.ts → sidebar-order.ts.
|
|
9
|
+
*/
|
|
10
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
11
|
+
import type { ListItem, SidebarSection } from "./views.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Arranges view members into the UI's flat sidebar-mirroring order: the
|
|
14
|
+
* area-less block first (direct to-dos, then each top-level project followed
|
|
15
|
+
* by its members), then each area by its sidebar index (direct to-dos, then
|
|
16
|
+
* its projects). Project and area order here mirrors the sidebar exactly —
|
|
17
|
+
* both read the same "index" columns.
|
|
18
|
+
*/
|
|
19
|
+
export declare function groupBySidebar(db: DatabaseSync, items: ListItem[]): SidebarSection[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** `?, ?, …` SQL placeholder list of length n. */
|
|
2
|
+
const inList = (n) => Array.from({ length: n }, () => "?").join(", ");
|
|
3
|
+
/**
|
|
4
|
+
* Arranges view members into the UI's flat sidebar-mirroring order: the
|
|
5
|
+
* area-less block first (direct to-dos, then each top-level project followed
|
|
6
|
+
* by its members), then each area by its sidebar index (direct to-dos, then
|
|
7
|
+
* its projects). Project and area order here mirrors the sidebar exactly —
|
|
8
|
+
* both read the same "index" columns.
|
|
9
|
+
*/
|
|
10
|
+
export function groupBySidebar(db, items) {
|
|
11
|
+
if (items.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
// Headed children: resolve heading -> project.
|
|
14
|
+
const headingUuids = [
|
|
15
|
+
...new Set(items.flatMap((i) => (i.type === "to-do" && i.heading !== null ? [i.heading.uuid] : []))),
|
|
16
|
+
];
|
|
17
|
+
const headingProject = new Map();
|
|
18
|
+
if (headingUuids.length > 0) {
|
|
19
|
+
for (const row of db
|
|
20
|
+
.prepare(`SELECT uuid, project FROM TMTask WHERE uuid IN (${inList(headingUuids.length)})`)
|
|
21
|
+
.all(...headingUuids)) {
|
|
22
|
+
headingProject.set(row.uuid, row.project);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const effProject = (i) => i.type !== "to-do"
|
|
26
|
+
? null
|
|
27
|
+
: (i.project?.uuid ??
|
|
28
|
+
(i.heading !== null ? (headingProject.get(i.heading.uuid) ?? null) : null));
|
|
29
|
+
// Sidebar rank + title for areas; index + area for every referenced project
|
|
30
|
+
// (a tag-filtered list can contain a child whose project row didn't match).
|
|
31
|
+
const areaRows = db
|
|
32
|
+
.prepare(`SELECT uuid, title, "index" FROM TMArea ORDER BY "index" ASC, uuid ASC`)
|
|
33
|
+
.all();
|
|
34
|
+
const areaRank = new Map(areaRows.map((a, rank) => [a.uuid, rank]));
|
|
35
|
+
const areaTitle = new Map(areaRows.map((a) => [a.uuid, a.title ?? ""]));
|
|
36
|
+
const projectUuids = [
|
|
37
|
+
...new Set([
|
|
38
|
+
...items.flatMap((i) => (i.type === "project" ? [i.uuid] : [])),
|
|
39
|
+
...items.flatMap((i) => {
|
|
40
|
+
const p = effProject(i);
|
|
41
|
+
return p === null ? [] : [p];
|
|
42
|
+
}),
|
|
43
|
+
]),
|
|
44
|
+
];
|
|
45
|
+
const projectMeta = new Map();
|
|
46
|
+
if (projectUuids.length > 0) {
|
|
47
|
+
for (const row of db
|
|
48
|
+
.prepare(`SELECT uuid, "index", area FROM TMTask WHERE uuid IN (${inList(projectUuids.length)})`)
|
|
49
|
+
.all(...projectUuids)) {
|
|
50
|
+
projectMeta.set(row.uuid, { index: row.index ?? 0, area: row.area });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// items arrive in SQL "index" order, so array position IS the per-container
|
|
54
|
+
// rank (the internal index is no longer exposed on the entity).
|
|
55
|
+
const sortKey = (i, pos) => {
|
|
56
|
+
const project = i.type === "project" ? i.uuid : effProject(i);
|
|
57
|
+
const meta = project === null ? undefined : projectMeta.get(project);
|
|
58
|
+
const area = i.area?.uuid ?? meta?.area ?? null;
|
|
59
|
+
return {
|
|
60
|
+
areaRank: area === null ? -1 : (areaRank.get(area) ?? areaRows.length),
|
|
61
|
+
area: area ?? "",
|
|
62
|
+
inProject: project === null ? 0 : 1,
|
|
63
|
+
projectIndex: meta?.index ?? 0,
|
|
64
|
+
project: project ?? "",
|
|
65
|
+
headerFirst: i.type === "project" ? 0 : 1,
|
|
66
|
+
pos,
|
|
67
|
+
uuid: i.uuid,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const keyed = items.map((item, pos) => ({ item, k: sortKey(item, pos) }));
|
|
71
|
+
keyed.sort((a, b) => a.k.areaRank - b.k.areaRank ||
|
|
72
|
+
a.k.area.localeCompare(b.k.area) ||
|
|
73
|
+
a.k.inProject - b.k.inProject ||
|
|
74
|
+
a.k.projectIndex - b.k.projectIndex ||
|
|
75
|
+
a.k.project.localeCompare(b.k.project) ||
|
|
76
|
+
a.k.headerFirst - b.k.headerFirst ||
|
|
77
|
+
a.k.pos - b.k.pos ||
|
|
78
|
+
a.k.uuid.localeCompare(b.k.uuid));
|
|
79
|
+
const sections = [];
|
|
80
|
+
for (const { item, k } of keyed) {
|
|
81
|
+
const areaUuid = k.area === "" ? null : k.area;
|
|
82
|
+
const last = sections.at(-1);
|
|
83
|
+
if (last === undefined || (last.area?.uuid ?? null) !== areaUuid) {
|
|
84
|
+
sections.push({
|
|
85
|
+
area: areaUuid === null ? null : { uuid: areaUuid, title: areaTitle.get(areaUuid) ?? "" },
|
|
86
|
+
items: [],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
sections.at(-1)?.items.push(item);
|
|
90
|
+
}
|
|
91
|
+
return sections;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=sidebar-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar-order.js","sourceRoot":"","sources":["../../src/read/sidebar-order.ts"],"names":[],"mappings":"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"}
|
package/dist/read/tags.d.ts
CHANGED
|
@@ -8,10 +8,34 @@
|
|
|
8
8
|
* direct tags.
|
|
9
9
|
*/
|
|
10
10
|
import type { DatabaseSync } from "node:sqlite";
|
|
11
|
-
import type { Area, Ref, Tag } from "../model/entities.ts";
|
|
11
|
+
import type { Area, Ref, Tag, TagRef } from "../model/entities.ts";
|
|
12
12
|
import type { TaskRow } from "../model/mappers.ts";
|
|
13
|
+
/**
|
|
14
|
+
* The `things tags` listing: the tag TREE, depth-first, so a child always
|
|
15
|
+
* FOLLOWS its parent. Siblings (and roots) order by canonical `TMTag."index"`
|
|
16
|
+
* (the app's Tags-window rank), UUID tiebreak (TAGORD1 oracle — the Tags-window
|
|
17
|
+
* order for index-tied tags is uuid-ascending, not alphabetical; see
|
|
18
|
+
* fetchTagsForTasks).
|
|
19
|
+
*
|
|
20
|
+
* DFS is unambiguous here and matches the Tags window: this is a listing of the
|
|
21
|
+
* hierarchy itself, where the parent→child relationship is the structure. That
|
|
22
|
+
* is DISTINCT from a flat multi-tag pill row (fetchTagsForTasks / areaTags),
|
|
23
|
+
* where the ratified order is flat ascending `index` and the nested-tag
|
|
24
|
+
* interleave is an open question — see fetchTagsForTasks for that caveat.
|
|
25
|
+
*/
|
|
13
26
|
export declare function tagsView(db: DatabaseSync): Tag[];
|
|
14
27
|
export declare function areasView(db: DatabaseSync): Area[];
|
|
15
28
|
export declare function areaTags(db: DatabaseSync, areaUuid: string): Ref[];
|
|
16
|
-
/**
|
|
17
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Tags inherited from ancestors (the heading's project, the project, the area),
|
|
31
|
+
* surfaced by NAME as a plain list ({@link TagRef}) parallel to the item's own
|
|
32
|
+
* direct tags. The item's OWN direct tags are excluded (they render on the
|
|
33
|
+
* `tags:` line, not here). Which tags are inherited is still computed — a
|
|
34
|
+
* heading cannot be tagged (TAGINH1, VM-verified), so the chain is
|
|
35
|
+
* heading → project → area — but no container source/provenance is emitted.
|
|
36
|
+
*
|
|
37
|
+
* ORDER: the merged inherited set is returned in the app's CANONICAL tag order
|
|
38
|
+
* — `TMTag."index"`, uuid tiebreak (TAGORD1) — the SAME comparator the direct
|
|
39
|
+
* pill row uses (fetchTagsForTasks), applied across the project+area union.
|
|
40
|
+
*/
|
|
41
|
+
export declare function inheritedTagsFor(db: DatabaseSync, row: TaskRow): TagRef[];
|
package/dist/read/tags.js
CHANGED
|
@@ -1,19 +1,57 @@
|
|
|
1
1
|
import { q, selectList } from "../db/schema.js";
|
|
2
2
|
import { fetchTagsForTasks, fetchTaskByUuid } from "./queries.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `things tags` listing: the tag TREE, depth-first, so a child always
|
|
5
|
+
* FOLLOWS its parent. Siblings (and roots) order by canonical `TMTag."index"`
|
|
6
|
+
* (the app's Tags-window rank), UUID tiebreak (TAGORD1 oracle — the Tags-window
|
|
7
|
+
* order for index-tied tags is uuid-ascending, not alphabetical; see
|
|
8
|
+
* fetchTagsForTasks).
|
|
9
|
+
*
|
|
10
|
+
* DFS is unambiguous here and matches the Tags window: this is a listing of the
|
|
11
|
+
* hierarchy itself, where the parent→child relationship is the structure. That
|
|
12
|
+
* is DISTINCT from a flat multi-tag pill row (fetchTagsForTasks / areaTags),
|
|
13
|
+
* where the ratified order is flat ascending `index` and the nested-tag
|
|
14
|
+
* interleave is an open question — see fetchTagsForTasks for that caveat.
|
|
15
|
+
*/
|
|
3
16
|
export function tagsView(db) {
|
|
4
17
|
const rows = db
|
|
5
|
-
.prepare(`SELECT ${selectList("TMTag")} FROM TMTag ORDER BY ${q("index")} ASC`)
|
|
18
|
+
.prepare(`SELECT ${selectList("TMTag")} FROM TMTag ORDER BY ${q("index")} ASC, uuid ASC`)
|
|
6
19
|
.all();
|
|
7
20
|
const byUuid = new Map(rows.map((r) => [r.uuid, r]));
|
|
8
|
-
|
|
21
|
+
// Children grouped under their parent uuid, each list already in index/title
|
|
22
|
+
// order (the SQL sort is stable and preserves the sibling ordering).
|
|
23
|
+
const childrenOf = new Map();
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
if (!row.parent)
|
|
26
|
+
continue;
|
|
27
|
+
(childrenOf.get(row.parent) ?? childrenOf.set(row.parent, []).get(row.parent)).push(row);
|
|
28
|
+
}
|
|
29
|
+
const out = [];
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const visit = (row) => {
|
|
32
|
+
if (seen.has(row.uuid))
|
|
33
|
+
return; // cycle guard (bad parent data can't loop)
|
|
34
|
+
seen.add(row.uuid);
|
|
9
35
|
const parentRow = row.parent ? byUuid.get(row.parent) : undefined;
|
|
10
|
-
|
|
11
|
-
uuid: row.uuid,
|
|
36
|
+
out.push({
|
|
12
37
|
title: row.title ?? "",
|
|
13
38
|
shortcut: row.shortcut,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
39
|
+
// Parent tag's NAME (null for a root) — the tree is reconstructable from
|
|
40
|
+
// names alone; tag uuids are never surfaced.
|
|
41
|
+
parent: parentRow ? (parentRow.title ?? "") : null,
|
|
42
|
+
});
|
|
43
|
+
for (const child of childrenOf.get(row.uuid) ?? [])
|
|
44
|
+
visit(child);
|
|
45
|
+
};
|
|
46
|
+
// Roots first (no parent, or an orphan whose parent uuid is absent), in index
|
|
47
|
+
// order; DFS pulls each subtree. A final pass emits any row a parent cycle
|
|
48
|
+
// left unreached, so the listing never silently drops a tag.
|
|
49
|
+
for (const row of rows)
|
|
50
|
+
if (!row.parent || !byUuid.has(row.parent))
|
|
51
|
+
visit(row);
|
|
52
|
+
for (const row of rows)
|
|
53
|
+
visit(row);
|
|
54
|
+
return out;
|
|
17
55
|
}
|
|
18
56
|
export function areasView(db) {
|
|
19
57
|
const rows = db
|
|
@@ -23,23 +61,41 @@ export function areasView(db) {
|
|
|
23
61
|
uuid: row.uuid,
|
|
24
62
|
title: row.title ?? "",
|
|
25
63
|
visible: row.visible !== 0,
|
|
26
|
-
tags
|
|
64
|
+
// Surface area tags by NAME only (tag uuids are internal).
|
|
65
|
+
tags: areaTags(db, row.uuid).map((t) => ({ title: t.title })),
|
|
27
66
|
}));
|
|
28
67
|
}
|
|
29
68
|
export function areaTags(db, areaUuid) {
|
|
69
|
+
// Canonical pill order: ascending TMTag."index" (the app's Tags-window rank),
|
|
70
|
+
// UUID tiebreak. Same comparator + nested-tag caveat as fetchTagsForTasks.
|
|
30
71
|
const rows = db
|
|
31
72
|
.prepare(`SELECT tg.uuid AS uuid, tg.title AS title
|
|
32
73
|
FROM TMAreaTag at JOIN TMTag tg ON tg.uuid = at.tags
|
|
33
|
-
WHERE at.areas = ? ORDER BY tg.
|
|
74
|
+
WHERE at.areas = ? ORDER BY tg.${q("index")}, tg.uuid`)
|
|
34
75
|
.all(areaUuid);
|
|
35
76
|
return rows.map((r) => ({ uuid: r.uuid, title: r.title ?? "" }));
|
|
36
77
|
}
|
|
37
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Tags inherited from ancestors (the heading's project, the project, the area),
|
|
80
|
+
* surfaced by NAME as a plain list ({@link TagRef}) parallel to the item's own
|
|
81
|
+
* direct tags. The item's OWN direct tags are excluded (they render on the
|
|
82
|
+
* `tags:` line, not here). Which tags are inherited is still computed — a
|
|
83
|
+
* heading cannot be tagged (TAGINH1, VM-verified), so the chain is
|
|
84
|
+
* heading → project → area — but no container source/provenance is emitted.
|
|
85
|
+
*
|
|
86
|
+
* ORDER: the merged inherited set is returned in the app's CANONICAL tag order
|
|
87
|
+
* — `TMTag."index"`, uuid tiebreak (TAGORD1) — the SAME comparator the direct
|
|
88
|
+
* pill row uses (fetchTagsForTasks), applied across the project+area union.
|
|
89
|
+
*/
|
|
38
90
|
export function inheritedTagsFor(db, row) {
|
|
91
|
+
// Work with uuid-carrying rows INTERNALLY (dedup + canonical ranking need the
|
|
92
|
+
// uuid); the surfaced TagRef carries the tag NAME only. First-seen wins on a
|
|
93
|
+
// duplicate — the nearer ancestor (project, collected before area) is kept.
|
|
39
94
|
const collected = new Map();
|
|
40
95
|
const addAll = (refs) => {
|
|
41
96
|
for (const ref of refs)
|
|
42
|
-
collected.
|
|
97
|
+
if (!collected.has(ref.uuid))
|
|
98
|
+
collected.set(ref.uuid, ref);
|
|
43
99
|
};
|
|
44
100
|
let projectUuid = row.project;
|
|
45
101
|
if (!projectUuid && row.heading) {
|
|
@@ -57,6 +113,21 @@ export function inheritedTagsFor(db, row) {
|
|
|
57
113
|
if (areaUuid)
|
|
58
114
|
addAll(areaTags(db, areaUuid));
|
|
59
115
|
const direct = new Set((fetchTagsForTasks(db, [row.uuid]).get(row.uuid) ?? []).map((t) => t.uuid));
|
|
60
|
-
|
|
116
|
+
const result = [...collected.values()].filter((ref) => !direct.has(ref.uuid));
|
|
117
|
+
if (result.length > 1) {
|
|
118
|
+
// Re-rank the project+area union in one canonical `index, uuid` pass so the
|
|
119
|
+
// merged inherited chips match the GUI's tag order (the per-source lists
|
|
120
|
+
// arrive already ordered, but their concatenation is not globally sorted).
|
|
121
|
+
const rank = new Map();
|
|
122
|
+
const uuids = result.map((ref) => ref.uuid);
|
|
123
|
+
const rows = db
|
|
124
|
+
.prepare(`SELECT uuid FROM TMTag WHERE uuid IN (${uuids.map(() => "?").join(", ")})
|
|
125
|
+
ORDER BY ${q("index")}, uuid`)
|
|
126
|
+
.all(...uuids);
|
|
127
|
+
rows.forEach((r, i) => rank.set(r.uuid, i));
|
|
128
|
+
result.sort((a, b) => (rank.get(a.uuid) ?? 0) - (rank.get(b.uuid) ?? 0));
|
|
129
|
+
}
|
|
130
|
+
// Surface the tag NAME only (uuid stays internal).
|
|
131
|
+
return result.map((ref) => ({ title: ref.title }));
|
|
61
132
|
}
|
|
62
133
|
//# sourceMappingURL=tags.js.map
|
package/dist/read/tags.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/read/tags.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAWlE,MAAM,UAAU,QAAQ,CAAC,EAAgB;IACvC,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,UAAU,UAAU,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/read/tags.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared list-view truncation: turn a full, filtered result into the rows a
|
|
3
|
+
* surface actually shows (default 50) plus the exact {@link Truncation}
|
|
4
|
+
* metadata every surface reports so nothing is ever silently dropped. The
|
|
5
|
+
* limit counts ITEMS in render order and truncates mid-group; grouped shapes
|
|
6
|
+
* (the Today split, sidebar sections) drop the trailing groups that fall
|
|
7
|
+
* entirely past the cut so no empty header survives. `limit === null` means
|
|
8
|
+
* "all rows" (the caller passed --all / all: true).
|
|
9
|
+
*/
|
|
10
|
+
import type { GroupedTruncation, Truncation } from "../contracts.ts";
|
|
11
|
+
import { type AreaView } from "./area-view.ts";
|
|
12
|
+
import { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT } from "../surface-copy.ts";
|
|
13
|
+
import type { SidebarSection, TodayView } from "./views.ts";
|
|
14
|
+
import { partitionSomedaySection, splitSectionBlocks, type GroupedLimits } from "./sections.ts";
|
|
15
|
+
export { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT };
|
|
16
|
+
export { partitionSomedaySection, splitSectionBlocks, type GroupedLimits };
|
|
17
|
+
/** Flat list: slice to the limit; total is the full filtered length. */
|
|
18
|
+
export declare function truncateList<T>(items: T[], limit: number | null): {
|
|
19
|
+
data: T[];
|
|
20
|
+
truncation: Truncation;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Today split: the cut runs across Today then This Evening in render order,
|
|
24
|
+
* so a limit smaller than the Today block trims Evening to nothing. The badge
|
|
25
|
+
* (a whole-view count summary) is preserved unchanged. The truncation carries
|
|
26
|
+
* a per-section (`today`/`evening`) shown/total breakdown so a renderer can
|
|
27
|
+
* keep This Evening honest under the single global cap without a pre-cap copy.
|
|
28
|
+
*/
|
|
29
|
+
export declare function truncateToday(view: TodayView, limit: number | null): {
|
|
30
|
+
data: TodayView;
|
|
31
|
+
truncation: Truncation;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Anytime: the block skeleton is ALWAYS complete — every area header and
|
|
35
|
+
* every project row survives — and the caps apply INDEPENDENTLY to each
|
|
36
|
+
* innermost item list: `limits.area` to the loose block and each area's
|
|
37
|
+
* direct to-dos, `limits.project` to each project's to-dos. Returns the
|
|
38
|
+
* per-block-truncated sections (project rows retained) plus the per-block
|
|
39
|
+
* counts and a top-level `truncated` flag.
|
|
40
|
+
*/
|
|
41
|
+
export declare function previewSections(sections: SidebarSection[], limits: GroupedLimits): {
|
|
42
|
+
data: SidebarSection[];
|
|
43
|
+
grouped: GroupedTruncation;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Someday preview: every group survives; `limits.area` (null = no cap)
|
|
47
|
+
* applies independently to each section's own block (project rows + direct
|
|
48
|
+
* to-dos are items alike there), `limits.project` to each active project's
|
|
49
|
+
* child group (the show-active-project-items toggle). Sections keep their
|
|
50
|
+
* capped children after the own block, still clustered per project.
|
|
51
|
+
*/
|
|
52
|
+
export declare function previewSomedaySections(sections: SidebarSection[], limits: GroupedLimits): {
|
|
53
|
+
data: SidebarSection[];
|
|
54
|
+
grouped: GroupedTruncation;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Sectioned cap for the `area show` detail view: its sections are containers,
|
|
58
|
+
* so there is no strict total limit — instead `limits.project` bounds the
|
|
59
|
+
* ACTIVE project-ROWS section and `limits.area` the direct-to-dos section
|
|
60
|
+
* (null = uncapped). The cap is render-aware: only the area's ACTIVE project
|
|
61
|
+
* rows are capped, while its future-scheduled and someday project rows always
|
|
62
|
+
* survive (the card renders them under its uncapped Upcoming/Someday sections),
|
|
63
|
+
* so the human view derives entirely from this bounded shape. The toggled
|
|
64
|
+
* later/logged lists and the trashed bucket pass through untouched. Counts ride
|
|
65
|
+
* the same grouped-block shape the sidebar catalogues emit (kind "projects" =
|
|
66
|
+
* the active project-rows section). `now` classifies the schedule split.
|
|
67
|
+
*/
|
|
68
|
+
export declare function capAreaSections(view: AreaView, limits: GroupedLimits, now?: Date): {
|
|
69
|
+
data: AreaView;
|
|
70
|
+
grouped: GroupedTruncation;
|
|
71
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { localToday } from "../model/dates.js";
|
|
2
|
+
import { isActiveProjectRow } from "./area-view.js";
|
|
3
|
+
import { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT } from "../surface-copy.js";
|
|
4
|
+
import { partitionSomedaySection, splitSectionBlocks } from "./sections.js";
|
|
5
|
+
// The per-block cap shape and the structural section splitters live in
|
|
6
|
+
// ./sections.ts (imported for capping here). Re-exported so existing importers
|
|
7
|
+
// — and the truncation unit test — keep one import site.
|
|
8
|
+
export { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT };
|
|
9
|
+
export { partitionSomedaySection, splitSectionBlocks };
|
|
10
|
+
const whole = (total, limit) => ({
|
|
11
|
+
shown: total,
|
|
12
|
+
total,
|
|
13
|
+
limit,
|
|
14
|
+
truncated: false,
|
|
15
|
+
});
|
|
16
|
+
/** Flat list: slice to the limit; total is the full filtered length. */
|
|
17
|
+
export function truncateList(items, limit) {
|
|
18
|
+
const total = items.length;
|
|
19
|
+
if (limit === null || total <= limit)
|
|
20
|
+
return { data: items, truncation: whole(total, limit) };
|
|
21
|
+
return {
|
|
22
|
+
data: items.slice(0, limit),
|
|
23
|
+
truncation: { shown: limit, total, limit, truncated: true },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Today split: the cut runs across Today then This Evening in render order,
|
|
28
|
+
* so a limit smaller than the Today block trims Evening to nothing. The badge
|
|
29
|
+
* (a whole-view count summary) is preserved unchanged. The truncation carries
|
|
30
|
+
* a per-section (`today`/`evening`) shown/total breakdown so a renderer can
|
|
31
|
+
* keep This Evening honest under the single global cap without a pre-cap copy.
|
|
32
|
+
*/
|
|
33
|
+
export function truncateToday(view, limit) {
|
|
34
|
+
const todayTotal = view.today.length;
|
|
35
|
+
const eveningTotal = view.evening.length;
|
|
36
|
+
const total = todayTotal + eveningTotal;
|
|
37
|
+
const sections = (shownToday, shownEvening) => [
|
|
38
|
+
{ key: "today", shown: shownToday, total: todayTotal },
|
|
39
|
+
{ key: "evening", shown: shownEvening, total: eveningTotal },
|
|
40
|
+
];
|
|
41
|
+
if (limit === null || total <= limit) {
|
|
42
|
+
return {
|
|
43
|
+
data: view,
|
|
44
|
+
truncation: { ...whole(total, limit), sections: sections(todayTotal, eveningTotal) },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const today = view.today.slice(0, limit);
|
|
48
|
+
const evening = view.evening.slice(0, Math.max(0, limit - today.length));
|
|
49
|
+
const shown = today.length + evening.length;
|
|
50
|
+
return {
|
|
51
|
+
data: { today, evening, badge: view.badge },
|
|
52
|
+
truncation: {
|
|
53
|
+
shown,
|
|
54
|
+
total,
|
|
55
|
+
limit,
|
|
56
|
+
truncated: true,
|
|
57
|
+
sections: sections(today.length, evening.length),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const takeUpTo = (items, limit) => limit === null ? items : items.slice(0, limit);
|
|
62
|
+
/**
|
|
63
|
+
* Anytime: the block skeleton is ALWAYS complete — every area header and
|
|
64
|
+
* every project row survives — and the caps apply INDEPENDENTLY to each
|
|
65
|
+
* innermost item list: `limits.area` to the loose block and each area's
|
|
66
|
+
* direct to-dos, `limits.project` to each project's to-dos. Returns the
|
|
67
|
+
* per-block-truncated sections (project rows retained) plus the per-block
|
|
68
|
+
* counts and a top-level `truncated` flag.
|
|
69
|
+
*/
|
|
70
|
+
export function previewSections(sections, limits) {
|
|
71
|
+
const outSections = [];
|
|
72
|
+
const blocks = [];
|
|
73
|
+
let truncated = false;
|
|
74
|
+
for (const section of sections) {
|
|
75
|
+
const { direct, projects } = splitSectionBlocks(section);
|
|
76
|
+
const shownDirect = takeUpTo(direct, limits.area);
|
|
77
|
+
if (direct.length > shownDirect.length)
|
|
78
|
+
truncated = true;
|
|
79
|
+
// Project item-lists nest inside their area/loose block.
|
|
80
|
+
const children = [];
|
|
81
|
+
const items = [...shownDirect];
|
|
82
|
+
for (const { project, items: kids } of projects) {
|
|
83
|
+
const shownChildren = takeUpTo(kids, limits.project);
|
|
84
|
+
if (kids.length > 0) {
|
|
85
|
+
if (kids.length > shownChildren.length)
|
|
86
|
+
truncated = true;
|
|
87
|
+
children.push({
|
|
88
|
+
kind: "project",
|
|
89
|
+
ref: project.uuid,
|
|
90
|
+
title: project.title,
|
|
91
|
+
shown: shownChildren.length,
|
|
92
|
+
total: kids.length,
|
|
93
|
+
limit: limits.project,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
items.push(project, ...shownChildren);
|
|
97
|
+
}
|
|
98
|
+
if (direct.length > 0 || children.length > 0) {
|
|
99
|
+
blocks.push({
|
|
100
|
+
kind: section.area === null ? "loose" : "area",
|
|
101
|
+
ref: section.area?.uuid ?? null,
|
|
102
|
+
title: section.area?.title ?? null,
|
|
103
|
+
shown: shownDirect.length,
|
|
104
|
+
total: direct.length,
|
|
105
|
+
limit: limits.area,
|
|
106
|
+
...(children.length > 0 && { children }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
outSections.push({ area: section.area, items });
|
|
110
|
+
}
|
|
111
|
+
return { data: outSections, grouped: { truncated, blocks } };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Someday preview: every group survives; `limits.area` (null = no cap)
|
|
115
|
+
* applies independently to each section's own block (project rows + direct
|
|
116
|
+
* to-dos are items alike there), `limits.project` to each active project's
|
|
117
|
+
* child group (the show-active-project-items toggle). Sections keep their
|
|
118
|
+
* capped children after the own block, still clustered per project.
|
|
119
|
+
*/
|
|
120
|
+
export function previewSomedaySections(sections, limits) {
|
|
121
|
+
const outSections = [];
|
|
122
|
+
const blocks = [];
|
|
123
|
+
let truncated = false;
|
|
124
|
+
for (const section of sections) {
|
|
125
|
+
const { own, children } = partitionSomedaySection(section);
|
|
126
|
+
const shownOwn = takeUpTo(own, limits.area);
|
|
127
|
+
if (own.length > shownOwn.length)
|
|
128
|
+
truncated = true;
|
|
129
|
+
// The active-project child groups nest inside this section's own block.
|
|
130
|
+
const childBlocks = [];
|
|
131
|
+
const items = [...shownOwn];
|
|
132
|
+
for (const group of children) {
|
|
133
|
+
const shown = takeUpTo(group.items, limits.project);
|
|
134
|
+
if (group.items.length > shown.length)
|
|
135
|
+
truncated = true;
|
|
136
|
+
childBlocks.push({
|
|
137
|
+
kind: "project",
|
|
138
|
+
ref: group.project.uuid,
|
|
139
|
+
title: group.project.title,
|
|
140
|
+
shown: shown.length,
|
|
141
|
+
total: group.items.length,
|
|
142
|
+
limit: limits.project,
|
|
143
|
+
});
|
|
144
|
+
items.push(...shown);
|
|
145
|
+
}
|
|
146
|
+
if (own.length > 0 || childBlocks.length > 0) {
|
|
147
|
+
const totalProjects = own.filter((i) => i.type === "project").length;
|
|
148
|
+
blocks.push({
|
|
149
|
+
kind: section.area === null ? "loose" : "area",
|
|
150
|
+
ref: section.area?.uuid ?? null,
|
|
151
|
+
title: section.area?.title ?? null,
|
|
152
|
+
shown: shownOwn.length,
|
|
153
|
+
total: own.length,
|
|
154
|
+
limit: limits.area,
|
|
155
|
+
totalProjects,
|
|
156
|
+
totalTodos: own.length - totalProjects,
|
|
157
|
+
...(childBlocks.length > 0 && { children: childBlocks }),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
outSections.push({ area: section.area, items });
|
|
161
|
+
}
|
|
162
|
+
return { data: outSections, grouped: { truncated, blocks } };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Sectioned cap for the `area show` detail view: its sections are containers,
|
|
166
|
+
* so there is no strict total limit — instead `limits.project` bounds the
|
|
167
|
+
* ACTIVE project-ROWS section and `limits.area` the direct-to-dos section
|
|
168
|
+
* (null = uncapped). The cap is render-aware: only the area's ACTIVE project
|
|
169
|
+
* rows are capped, while its future-scheduled and someday project rows always
|
|
170
|
+
* survive (the card renders them under its uncapped Upcoming/Someday sections),
|
|
171
|
+
* so the human view derives entirely from this bounded shape. The toggled
|
|
172
|
+
* later/logged lists and the trashed bucket pass through untouched. Counts ride
|
|
173
|
+
* the same grouped-block shape the sidebar catalogues emit (kind "projects" =
|
|
174
|
+
* the active project-rows section). `now` classifies the schedule split.
|
|
175
|
+
*/
|
|
176
|
+
export function capAreaSections(view, limits, now) {
|
|
177
|
+
const todayIso = localToday(now);
|
|
178
|
+
const blocks = [];
|
|
179
|
+
let truncated = false;
|
|
180
|
+
// Cap the ACTIVE project rows in place; scheduled/someday rows always survive.
|
|
181
|
+
const activeTotal = view.projects.filter((p) => isActiveProjectRow(p, todayIso)).length;
|
|
182
|
+
const shownActive = limits.project === null ? activeTotal : Math.min(activeTotal, limits.project);
|
|
183
|
+
let activeSeen = 0;
|
|
184
|
+
const projects = view.projects.filter((p) => {
|
|
185
|
+
if (!isActiveProjectRow(p, todayIso))
|
|
186
|
+
return true;
|
|
187
|
+
activeSeen += 1;
|
|
188
|
+
return limits.project === null || activeSeen <= limits.project;
|
|
189
|
+
});
|
|
190
|
+
if (activeTotal > 0) {
|
|
191
|
+
if (shownActive < activeTotal)
|
|
192
|
+
truncated = true;
|
|
193
|
+
blocks.push({
|
|
194
|
+
kind: "projects",
|
|
195
|
+
ref: view.area.uuid,
|
|
196
|
+
title: view.area.title,
|
|
197
|
+
shown: shownActive,
|
|
198
|
+
total: activeTotal,
|
|
199
|
+
limit: limits.project,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const active = limits.area === null ? view.active : view.active.slice(0, limits.area);
|
|
203
|
+
if (view.active.length > 0) {
|
|
204
|
+
if (active.length < view.active.length)
|
|
205
|
+
truncated = true;
|
|
206
|
+
blocks.push({
|
|
207
|
+
kind: "area",
|
|
208
|
+
ref: view.area.uuid,
|
|
209
|
+
title: view.area.title,
|
|
210
|
+
shown: active.length,
|
|
211
|
+
total: view.active.length,
|
|
212
|
+
limit: limits.area,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return { data: { ...view, projects, active }, grouped: { truncated, blocks } };
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=truncation.js.map
|