things-api 0.8.0 → 0.9.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.
Files changed (91) hide show
  1. package/README.md +3 -2
  2. package/dist/cli/commands/area.d.ts +9 -0
  3. package/dist/cli/commands/area.js +62 -5
  4. package/dist/cli/commands/area.js.map +1 -1
  5. package/dist/cli/commands/project.d.ts +5 -0
  6. package/dist/cli/commands/project.js +34 -5
  7. package/dist/cli/commands/project.js.map +1 -1
  8. package/dist/cli/commands/reads.d.ts +6 -125
  9. package/dist/cli/commands/reads.js +337 -611
  10. package/dist/cli/commands/reads.js.map +1 -1
  11. package/dist/cli/commands/show.d.ts +1 -1
  12. package/dist/cli/commands/show.js +145 -17
  13. package/dist/cli/commands/show.js.map +1 -1
  14. package/dist/cli/commands/snapshot.js +1 -1
  15. package/dist/cli/commands/snapshot.js.map +1 -1
  16. package/dist/cli/commands/todo.js +14 -3
  17. package/dist/cli/commands/todo.js.map +1 -1
  18. package/dist/cli/did-you-mean.d.ts +29 -0
  19. package/dist/cli/did-you-mean.js +53 -0
  20. package/dist/cli/did-you-mean.js.map +1 -0
  21. package/dist/cli/glyphs.d.ts +28 -5
  22. package/dist/cli/glyphs.js +123 -10
  23. package/dist/cli/glyphs.js.map +1 -1
  24. package/dist/cli/main.js +9 -1
  25. package/dist/cli/main.js.map +1 -1
  26. package/dist/cli/period.d.ts +32 -0
  27. package/dist/cli/period.js +129 -0
  28. package/dist/cli/period.js.map +1 -0
  29. package/dist/cli/read-driver.d.ts +76 -0
  30. package/dist/cli/read-driver.js +181 -0
  31. package/dist/cli/read-driver.js.map +1 -0
  32. package/dist/cli/render.d.ts +182 -0
  33. package/dist/cli/render.js +585 -0
  34. package/dist/cli/render.js.map +1 -0
  35. package/dist/cli/resolve-invocation.d.ts +78 -0
  36. package/dist/cli/resolve-invocation.js +178 -0
  37. package/dist/cli/resolve-invocation.js.map +1 -0
  38. package/dist/cli/shell-quote.d.ts +9 -0
  39. package/dist/cli/shell-quote.js +12 -0
  40. package/dist/cli/shell-quote.js.map +1 -0
  41. package/dist/client.d.ts +13 -4
  42. package/dist/client.js +2 -1
  43. package/dist/client.js.map +1 -1
  44. package/dist/contracts.d.ts +66 -1
  45. package/dist/contracts.js +1 -1
  46. package/dist/contracts.js.map +1 -1
  47. package/dist/mcp/server.js +208 -44
  48. package/dist/mcp/server.js.map +1 -1
  49. package/dist/model/entities.d.ts +9 -0
  50. package/dist/model/entities.js.map +1 -1
  51. package/dist/model/mappers.js +13 -1
  52. package/dist/model/mappers.js.map +1 -1
  53. package/dist/model/occurrences.d.ts +6 -1
  54. package/dist/model/occurrences.js +9 -5
  55. package/dist/model/occurrences.js.map +1 -1
  56. package/dist/model/recurrence.d.ts +17 -8
  57. package/dist/model/recurrence.js.map +1 -1
  58. package/dist/read/caps.d.ts +14 -0
  59. package/dist/read/caps.js +21 -0
  60. package/dist/read/caps.js.map +1 -0
  61. package/dist/read/log-boundary.d.ts +9 -4
  62. package/dist/read/log-boundary.js.map +1 -1
  63. package/dist/read/pagination.d.ts +104 -0
  64. package/dist/read/pagination.js +208 -0
  65. package/dist/read/pagination.js.map +1 -0
  66. package/dist/read/predicates.d.ts +31 -0
  67. package/dist/read/predicates.js +48 -0
  68. package/dist/read/predicates.js.map +1 -0
  69. package/dist/read/queries.d.ts +7 -2
  70. package/dist/read/queries.js +11 -7
  71. package/dist/read/queries.js.map +1 -1
  72. package/dist/read/search-rank.d.ts +36 -0
  73. package/dist/read/search-rank.js +31 -0
  74. package/dist/read/search-rank.js.map +1 -0
  75. package/dist/read/show-target.d.ts +13 -4
  76. package/dist/read/show-target.js +11 -2
  77. package/dist/read/show-target.js.map +1 -1
  78. package/dist/read/sidebar-order.d.ts +19 -0
  79. package/dist/read/sidebar-order.js +92 -0
  80. package/dist/read/sidebar-order.js.map +1 -0
  81. package/dist/read/views.d.ts +39 -5
  82. package/dist/read/views.js +142 -162
  83. package/dist/read/views.js.map +1 -1
  84. package/dist/surface-copy.d.ts +23 -0
  85. package/dist/surface-copy.js +23 -0
  86. package/dist/surface-copy.js.map +1 -1
  87. package/dist/write/capabilities.js +1 -0
  88. package/dist/write/capabilities.js.map +1 -1
  89. package/dist/write/undo.js +1 -0
  90. package/dist/write/undo.js.map +1 -1
  91. package/package.json +1 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The read driver: open the client, stamp the envelope meta, and emit either
3
+ * the `--json` envelope or human lines (with the TTY-only title preamble,
4
+ * normalized-form echo, and truncation hint). Plus the shared `--limit`/`--all`
5
+ * cap parsing and the invocation-echo helpers every read command reuses. No
6
+ * commander dependency — command registration lives in the command modules.
7
+ */
8
+ import { openThings } from "../client.js";
9
+ import { ThingsDbNotFoundError } from "../db/locate.js";
10
+ import { ThingsDbOpenError } from "../db/connection.js";
11
+ import { getInvocation } from "./resolve-invocation.js";
12
+ import { dim } from "./style.js";
13
+ import { viewHeaderLines } from "./render.js";
14
+ import { candidatesJson, DidYouMeanError, renderDidYouMean } from "./did-you-mean.js";
15
+ import { errorEnvelope, ExitCode, okEnvelope, } from "../contracts.js";
16
+ import { resolveCap } from "../read/caps.js";
17
+ import { DEFAULT_LIST_LIMIT } from "../read/pagination.js";
18
+ /**
19
+ * The shared read driver: open the client, stamp the envelope meta (including
20
+ * fingerprint + optional pagination), and either emit the `--json` envelope or
21
+ * render human lines. When `hintBase` is given and the result was truncated,
22
+ * the muted "N more items" hint (reconstructing the user's own invocation) is
23
+ * appended to the human output — never to `--json`. When `header` names a view,
24
+ * its title preamble leads the human output on a TTY only (viewHeaderLines).
25
+ */
26
+ export function runRead(opts, kind, fn, render, hintBase, header) {
27
+ const started = Date.now();
28
+ // An empty --db would silently fall through to the default database path —
29
+ // reject it loudly instead of reading somewhere the caller did not name.
30
+ if (opts.db !== undefined && opts.db.trim() === "") {
31
+ process.stderr.write("error: --db requires a non-empty path\n");
32
+ process.exitCode = ExitCode.Usage;
33
+ return;
34
+ }
35
+ let client = null;
36
+ try {
37
+ client = openThings(opts.db ? { dbPath: opts.db } : {});
38
+ const fp = client.fingerprint();
39
+ const { data, pagination, grouped, lines: precomputed } = fn(client);
40
+ // The canonical command a sugar invocation normalized to — known now that
41
+ // `fn` has resolved any reference. Present only for the routing sugars
42
+ // (bare noun, keyword-in-show, uuid/share-link routing); null otherwise.
43
+ const resolvedCommand = getInvocation()?.canonical ?? null;
44
+ const meta = {
45
+ dbVersion: fp.observation.databaseVersion,
46
+ fingerprint: fp.kind === "ok" ? "ok" : fp.kind === "drift" ? "drift" : "unknown",
47
+ elapsedMs: Date.now() - started,
48
+ ...(pagination !== undefined && { pagination }),
49
+ ...(grouped !== undefined && { grouped }),
50
+ ...(resolvedCommand !== null && { resolvedCommand }),
51
+ };
52
+ if (fp.kind !== "ok") {
53
+ process.stderr.write(`warning: schema fingerprint ${meta.fingerprint} — reads best-effort, writes disabled (run \`things doctor\`)\n`);
54
+ }
55
+ if (opts.json) {
56
+ process.stdout.write(`${JSON.stringify(okEnvelope(kind, data, meta))}\n`);
57
+ }
58
+ else {
59
+ const lines = precomputed ?? render(data);
60
+ if (pagination !== undefined && hintBase !== undefined) {
61
+ const hint = truncationHint(hintBase, pagination);
62
+ if (hint !== null)
63
+ lines.push("", hint);
64
+ }
65
+ // The view title preamble is a TTY-only affordance (`things inbox | grep`
66
+ // must stay clean) and never rides --json — both gates already hold here.
67
+ const withHeader = header !== undefined && process.stdout.isTTY === true
68
+ ? [...viewHeaderLines(header), ...lines]
69
+ : lines;
70
+ // The normalized-form echo: one dim line naming the canonical command a
71
+ // sugar invocation resolved to, adjacent to the header. Same gates as the
72
+ // preamble (TTY-only, never in --json) — canonical invocations echo
73
+ // nothing because `resolvedCommand` is null for them.
74
+ const out = resolvedCommand !== null && process.stdout.isTTY === true
75
+ ? [dim(`≡ ${resolvedCommand}`), ...withHeader]
76
+ : withHeader;
77
+ process.stdout.write(`${out.join("\n")}\n`);
78
+ }
79
+ process.exitCode = ExitCode.Ok;
80
+ }
81
+ catch (err) {
82
+ const meta = {
83
+ dbVersion: null,
84
+ fingerprint: "unknown",
85
+ elapsedMs: Date.now() - started,
86
+ };
87
+ // An unresolved show/bare-noun subject carries did-you-mean candidates: a
88
+ // usage-level failure (exit 2) with a lite title-search fallback, not the
89
+ // generic unexpected path.
90
+ if (err instanceof DidYouMeanError) {
91
+ if (opts.json) {
92
+ process.stdout.write(`${JSON.stringify(errorEnvelope({
93
+ code: "not-found",
94
+ message: err.message,
95
+ details: { candidates: candidatesJson(err) },
96
+ }, meta))}\n`);
97
+ }
98
+ else {
99
+ process.stderr.write(`${renderDidYouMean(err).join("\n")}\n`);
100
+ }
101
+ process.exitCode = ExitCode.Usage;
102
+ return;
103
+ }
104
+ const isEnv = err instanceof ThingsDbNotFoundError || err instanceof ThingsDbOpenError;
105
+ const message = err instanceof Error ? err.message : String(err);
106
+ if (opts.json) {
107
+ process.stdout.write(`${JSON.stringify(errorEnvelope({ code: isEnv ? "environment" : "unexpected", message }, meta))}\n`);
108
+ }
109
+ else {
110
+ process.stderr.write(`error: ${message}\n`);
111
+ }
112
+ process.exitCode = isEnv ? ExitCode.Environment : ExitCode.Unexpected;
113
+ }
114
+ finally {
115
+ client?.close();
116
+ }
117
+ }
118
+ /**
119
+ * Structured single-payload read: `fn` produces the whole payload, `render`
120
+ * turns it into human lines. Generic so `fn`'s return type flows into
121
+ * `render`'s parameter — the compiler verifies the renderer matches the data.
122
+ */
123
+ export function withClient(opts, kind, fn, render) {
124
+ runRead(opts, kind, (client) => ({ data: fn(client) }), render);
125
+ }
126
+ /**
127
+ * Resolve the shared `--limit`/`--all` pair (flat views) into a row cap
128
+ * (null = no cap), writing a loud usage error and setting the exit code on
129
+ * bad input: `--limit` must be a positive integer, and it may not combine
130
+ * with `--all`.
131
+ */
132
+ export function parseLimit(opts) {
133
+ return parseCap("--limit", opts.limit, DEFAULT_LIST_LIMIT, opts.all === true);
134
+ }
135
+ /**
136
+ * Resolve one cap flag (`--limit`, `--area-limit`, `--project-limit`) against
137
+ * `--all`: positive integer required, `--all` conflicts with an explicit
138
+ * value and otherwise lifts the cap (null). The conflict/default decision is
139
+ * the shared {@link resolveCap}; this surface adds the string→integer
140
+ * validation and the usage-error emission.
141
+ */
142
+ export function parseCap(flag, value, defaultLimit, all) {
143
+ const n = value === undefined ? undefined : Number(value);
144
+ const decision = resolveCap(n, all, defaultLimit);
145
+ // Conflict takes precedence over value validation (an explicit value beside
146
+ // --all is rejected before we scrutinize the value itself).
147
+ if (decision === "conflict") {
148
+ process.stderr.write(`error: ${flag} and --all are mutually exclusive\n`);
149
+ process.exitCode = ExitCode.Usage;
150
+ return { ok: false };
151
+ }
152
+ if (n !== undefined && (!Number.isInteger(n) || n < 1)) {
153
+ process.stderr.write(`error: ${flag} must be a positive integer\n`);
154
+ process.exitCode = ExitCode.Usage;
155
+ return { ok: false };
156
+ }
157
+ return { ok: true, limit: decision };
158
+ }
159
+ export { shellQuote } from "./shell-quote.js";
160
+ /** Reconstruct `things <name> <flags…>`, dropping falsy/empty parts. */
161
+ export function invocation(name, parts) {
162
+ return [
163
+ "things",
164
+ name,
165
+ ...parts.filter((p) => typeof p === "string" && p !== ""),
166
+ ].join(" ");
167
+ }
168
+ /**
169
+ * The unified truncation hint: a muted `── N more items — see more: … · all:
170
+ * … ──` line whose commands echo the user's actual invocation, so a bigger
171
+ * `--limit` or `--all` is one copy-paste away. Returns null when nothing was
172
+ * dropped or the caller already asked for every row.
173
+ */
174
+ export function truncationHint(base, pagination) {
175
+ if (!pagination.truncated || pagination.limit === null)
176
+ return null;
177
+ const more = pagination.total - pagination.shown;
178
+ const bigger = pagination.limit * 2;
179
+ return dim(`── ${more} more item${more === 1 ? "" : "s"} — see more: \`${base} --limit ${bigger}\` · all: \`${base} --all\` ──`);
180
+ }
181
+ //# sourceMappingURL=read-driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-driver.js","sourceRoot":"","sources":["../../src/cli/read-driver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * The pure human-rendering family for the read views: the glyph-language item
3
+ * formatter, the flat and grouped list renderers, the sidebar/upcoming/logbook
4
+ * layouts, the anytime/someday previews, and the legend. No commander and no
5
+ * database access — every function maps already-fetched data to output lines,
6
+ * so the whole module is unit-testable in isolation. UUIDs are always shown
7
+ * (agents and humans both need stable references); colors engage on a TTY only
8
+ * (../cli/style.ts).
9
+ */
10
+ import { type ListItem, type SidebarSection, type TodayView } from "../read/views.ts";
11
+ import { type GroupedLimits } from "../read/pagination.ts";
12
+ /**
13
+ * A view's TTY-only title preamble: bold view name + its dim Things deep link,
14
+ * then a blank line — e.g. `Anytime (things:///show?id=anytime)`. The id is the
15
+ * app's documented show id (identical to the command name for these views).
16
+ * Suppressed off a TTY so `things inbox | grep …` stays clean, and never part
17
+ * of `--json`; the caller gates on both.
18
+ */
19
+ export declare function viewHeaderLines(view: string): string[];
20
+ export declare const stripAnsi: (s: string) => string;
21
+ /**
22
+ * The `things legend` layout: the visual language grouped into sections
23
+ * (`── To-dos ──`, …), each row the glyph as it actually renders (color on a
24
+ * TTY) followed by its meaning. Content comes straight from glyphs.ts's LEGEND
25
+ * table, so it can never drift from what the list renderers emit.
26
+ */
27
+ export declare function renderLegend(): string[];
28
+ export interface FormatOpts {
29
+ /**
30
+ * Render a grouped project TITLE row: bold+underlined title (the GUI's
31
+ * project-header look) — the circle glyph and count chip still apply.
32
+ */
33
+ projectTitle?: boolean;
34
+ /** Container uuids already implied by surrounding output — their context suffix is dropped. */
35
+ suppressProject?: string | null;
36
+ suppressArea?: string | null;
37
+ /** Reference instant for date-relative tokens (tests pin this; defaults to now). */
38
+ now?: Date;
39
+ /** Pre-styled Today/Evening mark (★/⏾), rendered right after the box — GUI position. */
40
+ mark?: string | null;
41
+ /** Dim status word after the box (the GUI's waiting/paused/ended chips on repeating templates). */
42
+ statusWord?: string;
43
+ /** Suppress the ‹date› chip (rows under a day header already carry the date). */
44
+ hideDateChip?: boolean;
45
+ }
46
+ /**
47
+ * One item line:
48
+ * `<uuid-prefix> <box> [★|⏾] [logged-date] [‹chip›] <title> [‹n›] [⍾] [≡] [≔] (container) #tags [⚑ deadline]`.
49
+ * Repeating templates seat ↻ INSIDE the box (`[↻]`/`(↻)`) rather than as a
50
+ * separate mark; open project rows render their circle and title blue.
51
+ * The box is the glyph-language state carrier (../glyphs.ts): `[ ]`-family
52
+ * for to-dos, `( )`-family for projects — state survives with color
53
+ * stripped. Completed titles dim; canceled titles dim+strike (the `[×]`
54
+ * mark keeps the state when strike/ANSI is unavailable). Human output shows
55
+ * a SHORTENED uuid prefix (every command accepts unique prefixes >= 6
56
+ * chars); `uuidWidth` is the display length from uuidDisplayWidth — never
57
+ * below 8 so a copied prefix stays unique across the whole database, not
58
+ * just the rendered list. Tags follow the title (`#`-prefixed, green — GUI
59
+ * color), after the count chip (projects), notes marker, and container.
60
+ * Heading-nested to-dos label their parent PROJECT (via headingProject),
61
+ * never the heading — GUI behavior. Colors engage on a TTY only
62
+ * (../style.ts); `--json` always carries full uuids.
63
+ */
64
+ export declare function formatItem(item: ListItem, uuidWidth?: number, opts?: FormatOpts): string;
65
+ /**
66
+ * Row prefix in today-aware views: yellow ★ for Today members, cyan ⏾ for
67
+ * effective This-Evening members (raw evening assignment counts only while
68
+ * startDate is exactly today — the UI's daily expiry), null otherwise.
69
+ */
70
+ export declare function todayMark(item: ListItem, now?: Date): string | null;
71
+ /**
72
+ * Display width for a list's uuid column: the shortest prefix that is
73
+ * unique WITHIN the list, floored at UUID_DISPLAY_MIN (list-local
74
+ * uniqueness at 2–3 chars would still collide database-wide).
75
+ */
76
+ export declare function uuidDisplayWidth(items: Array<{
77
+ uuid: string;
78
+ }>): number;
79
+ /**
80
+ * Fit a uuid into the shared id column: truncate to `width` when longer, pad
81
+ * to it when shorter, so ids line up under one another regardless of prefix
82
+ * length. `width` is the value from uuidDisplayWidth.
83
+ */
84
+ export declare function uuidCol(uuid: string, width: number): string;
85
+ export declare function renderList(items: ListItem[]): string[];
86
+ /**
87
+ * The `things today` split. The membership glyph lives in the SECTION HEADER,
88
+ * not on every row — a yellow ★ in the Today header (which also carries the
89
+ * sidebar badge split) and a blue ⏾ in the This Evening header — so the rows
90
+ * drop the redundant per-item marker (the same convention that suppresses a
91
+ * `(project)` context inside that project's own view). Every OTHER view keeps
92
+ * the per-row ★/⏾, where the marker still carries information.
93
+ *
94
+ * This Evening mirrors the GUI: it renders ONLY when evening items exist —
95
+ * a truly-empty evening has no header at all. `full` is the pre-cap view and
96
+ * `shown` the rows that survived the global `--limit`; the split lets the
97
+ * section stay honest under truncation. When the cap hid some or all evening
98
+ * rows, an honest muted hint counts the hidden ones and names the `--limit`
99
+ * that reveals them — never the misleading `(empty)` a truncated evening used
100
+ * to show. `base` is the user's own invocation (flags echoed). The global
101
+ * footer (row driver) still reports the whole-view remainder separately.
102
+ */
103
+ export declare function renderToday(full: TodayView, shown: TodayView, base: string): string[];
104
+ /**
105
+ * Search rows: a standard list row, plus — for a project surfaced by a HEADING
106
+ * title match — a muted `(via heading "…")` suffix crediting the heading whose
107
+ * text matched (the parent project row stands in for the heading; the GUI has
108
+ * no bare heading row). Ordinary title/notes matches render as plain rows.
109
+ */
110
+ export declare function renderSearch(items: ListItem[]): string[];
111
+ /** Hidden-later counts per sidebar group (null area = the loose block). */
112
+ export interface LaterHints {
113
+ /** Sidebar-ordered, INCLUDING groups whose every project is later. */
114
+ groups: Array<{
115
+ area: {
116
+ uuid: string;
117
+ title: string;
118
+ } | null;
119
+ hidden: number;
120
+ }>;
121
+ }
122
+ /**
123
+ * The sidebar mirror for `things projects`: loose projects first (the GUI
124
+ * lists them above the areas), then a `── ⬡ Area ──` header per area with
125
+ * its projects beneath (the redundant `(Area)` suffix suppressed). Items
126
+ * arrive from projectsView already in sidebar order — this only inserts the
127
+ * headers. Denser than renderSections on purpose: no title styling and no
128
+ * blank line per project (every row here IS a project). With `hints`,
129
+ * default-hidden later projects are never silent: each group trails a muted
130
+ * `…n later projects` count (a later-only area still gets its header), and
131
+ * the output ends with the flag that reveals them.
132
+ */
133
+ export declare function renderProjectsSidebar(items: ListItem[], hints?: LaterHints): string[];
134
+ /**
135
+ * Upcoming rows under GUI-style date headers (empty periods are simply
136
+ * absent), with the trailing Repeating To-Dos section: templates with no
137
+ * set next occurrence, carrying their waiting/paused/ended status word and
138
+ * the bare ⚑ when the rule will assign a deadline per occurrence.
139
+ */
140
+ export declare function renderUpcoming(items: ListItem[], now?: Date): string[];
141
+ /**
142
+ * Logbook rows under GUI-style date headings, month granularity throughout
143
+ * — `── July ──` within the current year, `── March 2025 ──` beyond (finer
144
+ * than the GUI's bare per-year buckets, deliberately). Truncation past the
145
+ * row limit is reported by the shared hint the command appends, not here.
146
+ */
147
+ export declare function renderLogbook(items: ListItem[], now?: Date): string[];
148
+ /**
149
+ * Sidebar-grouped views (anytime/someday), rendered the way the GUI reads:
150
+ * the area-less block headerless first, then one `── <area> ──` header per
151
+ * area; inside a section, loose to-dos first, then each project GROUP — a
152
+ * blank line, the project's bold+underlined title row, then its members.
153
+ * Container names implied by the grouping are not repeated on member rows
154
+ * (an area header covers its rows; a project title row covers the to-dos
155
+ * beneath it — a clustered child whose project row is absent, e.g. under a
156
+ * tag filter, keeps its `(project)` suffix). `star` prefixes each item line
157
+ * with the Today-membership mark (★, or ⏾ for This-Evening members).
158
+ */
159
+ export declare function renderSections(sections: SidebarSection[], star?: boolean): string[];
160
+ /**
161
+ * The anytime preview: the FULL block skeleton — every area header and every
162
+ * project row — always renders; `limits.area` caps the loose block and each
163
+ * area's direct to-dos, `limits.project` each project's to-dos. A truncated
164
+ * block trails a muted `… N more — \`things (project|area) show '…'\``
165
+ * drill-down (the loose block has no container, so it shows only the count),
166
+ * and the view ends with one line escalating the caps that hit. Today members
167
+ * are starred. Mirrors renderSections' layout exactly.
168
+ */
169
+ export declare function renderAnytimePreview(sections: SidebarSection[], limits: GroupedLimits, base: string): string[];
170
+ /**
171
+ * The someday preview, mirroring the GUI (side-by-side, 2026-07-12): inside
172
+ * each group the project rows render as PLAIN items — `(~)` circle, count
173
+ * chip, no header styling, no surrounding blank lines — listed before the
174
+ * direct to-dos; `limits.area` caps each group's combined list. With
175
+ * `showActive` (the --show-active-project-items toggle) the someday to-dos
176
+ * living inside active projects append as a separate trailing
177
+ * `── From active projects ──` section — a flat run of project-header blocks
178
+ * (no area grouping), each capped at `limits.project` (null = every item).
179
+ * When the toggle is off and such items exist, a muted bottom hint counts
180
+ * them and names the flag.
181
+ */
182
+ export declare function renderSomedayPreview(sections: SidebarSection[], limits: GroupedLimits, base: string, showActive: boolean, hiddenActiveItems: number): string[];