things-api 0.9.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.
Files changed (214) hide show
  1. package/README.md +30 -11
  2. package/dist/audit/log.js +56 -2
  3. package/dist/audit/log.js.map +1 -1
  4. package/dist/audit/schema.d.ts +26 -8
  5. package/dist/audit/schema.js +42 -1
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/cli/commands/area.d.ts +26 -3
  8. package/dist/cli/commands/area.js +125 -70
  9. package/dist/cli/commands/area.js.map +1 -1
  10. package/dist/cli/commands/doctor.d.ts +1 -1
  11. package/dist/cli/commands/doctor.js +44 -5
  12. package/dist/cli/commands/doctor.js.map +1 -1
  13. package/dist/cli/commands/mcp.js +23 -4
  14. package/dist/cli/commands/mcp.js.map +1 -1
  15. package/dist/cli/commands/project.d.ts +18 -1
  16. package/dist/cli/commands/project.js +69 -31
  17. package/dist/cli/commands/project.js.map +1 -1
  18. package/dist/cli/commands/reads.js +423 -157
  19. package/dist/cli/commands/reads.js.map +1 -1
  20. package/dist/cli/commands/repeat-flags.d.ts +20 -0
  21. package/dist/cli/commands/repeat-flags.js +76 -0
  22. package/dist/cli/commands/repeat-flags.js.map +1 -0
  23. package/dist/cli/commands/setup.js +2 -3
  24. package/dist/cli/commands/setup.js.map +1 -1
  25. package/dist/cli/commands/show.js +47 -24
  26. package/dist/cli/commands/show.js.map +1 -1
  27. package/dist/cli/commands/todo.d.ts +1 -1
  28. package/dist/cli/commands/todo.js +7 -6
  29. package/dist/cli/commands/todo.js.map +1 -1
  30. package/dist/cli/commands/writes.js +378 -148
  31. package/dist/cli/commands/writes.js.map +1 -1
  32. package/dist/cli/did-you-mean.d.ts +1 -1
  33. package/dist/cli/excess-args.d.ts +15 -0
  34. package/dist/cli/excess-args.js +51 -0
  35. package/dist/cli/excess-args.js.map +1 -0
  36. package/dist/cli/glyphs.d.ts +39 -2
  37. package/dist/cli/glyphs.js +91 -21
  38. package/dist/cli/glyphs.js.map +1 -1
  39. package/dist/cli/help.d.ts +54 -0
  40. package/dist/cli/help.js +355 -0
  41. package/dist/cli/help.js.map +1 -0
  42. package/dist/cli/main.js +32 -25
  43. package/dist/cli/main.js.map +1 -1
  44. package/dist/cli/period.d.ts +7 -0
  45. package/dist/cli/period.js +12 -0
  46. package/dist/cli/period.js.map +1 -1
  47. package/dist/cli/read-driver.d.ts +26 -19
  48. package/dist/cli/read-driver.js +67 -33
  49. package/dist/cli/read-driver.js.map +1 -1
  50. package/dist/cli/render.d.ts +92 -28
  51. package/dist/cli/render.js +318 -97
  52. package/dist/cli/render.js.map +1 -1
  53. package/dist/cli/resolve-invocation.d.ts +42 -4
  54. package/dist/cli/resolve-invocation.js +97 -14
  55. package/dist/cli/resolve-invocation.js.map +1 -1
  56. package/dist/cli/tag-filters.d.ts +59 -0
  57. package/dist/cli/tag-filters.js +57 -0
  58. package/dist/cli/tag-filters.js.map +1 -0
  59. package/dist/cli/verb-hint.d.ts +25 -0
  60. package/dist/cli/verb-hint.js +138 -0
  61. package/dist/cli/verb-hint.js.map +1 -0
  62. package/dist/cli/width.d.ts +135 -0
  63. package/dist/cli/width.js +313 -0
  64. package/dist/cli/width.js.map +1 -0
  65. package/dist/client.d.ts +137 -24
  66. package/dist/client.js +105 -16
  67. package/dist/client.js.map +1 -1
  68. package/dist/config.d.ts +11 -0
  69. package/dist/config.js +3 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/contracts.d.ts +87 -17
  72. package/dist/contracts.js +44 -1
  73. package/dist/contracts.js.map +1 -1
  74. package/dist/db/fingerprint.d.ts +12 -0
  75. package/dist/db/fingerprint.js +15 -1
  76. package/dist/db/fingerprint.js.map +1 -1
  77. package/dist/db/locate.js +1 -1
  78. package/dist/db/locate.js.map +1 -1
  79. package/dist/diagnose.d.ts +63 -0
  80. package/dist/diagnose.js +38 -1
  81. package/dist/diagnose.js.map +1 -1
  82. package/dist/index.d.ts +30 -4
  83. package/dist/index.js +35 -2
  84. package/dist/index.js.map +1 -1
  85. package/dist/mcp/server.d.ts +9 -1
  86. package/dist/mcp/server.js +691 -133
  87. package/dist/mcp/server.js.map +1 -1
  88. package/dist/model/entities.d.ts +31 -7
  89. package/dist/model/entities.js.map +1 -1
  90. package/dist/model/mappers.d.ts +7 -0
  91. package/dist/model/mappers.js +7 -2
  92. package/dist/model/mappers.js.map +1 -1
  93. package/dist/model/serialize.d.ts +28 -0
  94. package/dist/model/serialize.js +80 -0
  95. package/dist/model/serialize.js.map +1 -0
  96. package/dist/model/when-sugar.d.ts +47 -0
  97. package/dist/model/when-sugar.js +45 -0
  98. package/dist/model/when-sugar.js.map +1 -0
  99. package/dist/read/area-view.d.ts +8 -1
  100. package/dist/read/area-view.js +49 -13
  101. package/dist/read/area-view.js.map +1 -1
  102. package/dist/read/filter-contract.d.ts +117 -0
  103. package/dist/read/filter-contract.js +78 -0
  104. package/dist/read/filter-contract.js.map +1 -0
  105. package/dist/read/predicates.d.ts +30 -0
  106. package/dist/read/predicates.js +30 -0
  107. package/dist/read/predicates.js.map +1 -1
  108. package/dist/read/project-view.d.ts +12 -1
  109. package/dist/read/project-view.js +39 -8
  110. package/dist/read/project-view.js.map +1 -1
  111. package/dist/read/queries.d.ts +149 -6
  112. package/dist/read/queries.js +250 -31
  113. package/dist/read/queries.js.map +1 -1
  114. package/dist/read/sections.d.ts +51 -0
  115. package/dist/read/sections.js +37 -0
  116. package/dist/read/sections.js.map +1 -0
  117. package/dist/read/sidebar-order.js +2 -1
  118. package/dist/read/sidebar-order.js.map +1 -1
  119. package/dist/read/tags.d.ts +27 -3
  120. package/dist/read/tags.js +83 -12
  121. package/dist/read/tags.js.map +1 -1
  122. package/dist/read/truncation.d.ts +71 -0
  123. package/dist/read/{pagination.js → truncation.js} +103 -94
  124. package/dist/read/truncation.js.map +1 -0
  125. package/dist/read/views.d.ts +140 -16
  126. package/dist/read/views.js +264 -67
  127. package/dist/read/views.js.map +1 -1
  128. package/dist/surface-copy.d.ts +19 -0
  129. package/dist/surface-copy.js +32 -0
  130. package/dist/surface-copy.js.map +1 -1
  131. package/dist/sync-health.d.ts +78 -0
  132. package/dist/sync-health.js +312 -0
  133. package/dist/sync-health.js.map +1 -0
  134. package/dist/write/accessibility-probe.d.ts +12 -0
  135. package/dist/write/accessibility-probe.js +63 -0
  136. package/dist/write/accessibility-probe.js.map +1 -0
  137. package/dist/write/automation-probe.d.ts +7 -0
  138. package/dist/write/automation-probe.js +8 -1
  139. package/dist/write/automation-probe.js.map +1 -1
  140. package/dist/write/batch.js +4 -2
  141. package/dist/write/batch.js.map +1 -1
  142. package/dist/write/capabilities.d.ts +8 -0
  143. package/dist/write/capabilities.js +13 -6
  144. package/dist/write/capabilities.js.map +1 -1
  145. package/dist/write/commands.d.ts +2 -0
  146. package/dist/write/commands.js +562 -48
  147. package/dist/write/commands.js.map +1 -1
  148. package/dist/write/edit-checklist.js +3 -2
  149. package/dist/write/edit-checklist.js.map +1 -1
  150. package/dist/write/guards.d.ts +2 -7
  151. package/dist/write/guards.js +64 -6
  152. package/dist/write/guards.js.map +1 -1
  153. package/dist/write/heading.js +2 -0
  154. package/dist/write/heading.js.map +1 -1
  155. package/dist/write/lock.d.ts +40 -2
  156. package/dist/write/lock.js +91 -14
  157. package/dist/write/lock.js.map +1 -1
  158. package/dist/write/make-repeating-project.d.ts +4 -0
  159. package/dist/write/make-repeating-project.js +253 -0
  160. package/dist/write/make-repeating-project.js.map +1 -0
  161. package/dist/write/operations.d.ts +145 -1
  162. package/dist/write/operations.js +48 -0
  163. package/dist/write/operations.js.map +1 -1
  164. package/dist/write/pipeline.d.ts +19 -0
  165. package/dist/write/pipeline.js +109 -26
  166. package/dist/write/pipeline.js.map +1 -1
  167. package/dist/write/pre-state.d.ts +68 -3
  168. package/dist/write/pre-state.js +102 -3
  169. package/dist/write/pre-state.js.map +1 -1
  170. package/dist/write/reopen.js +3 -2
  171. package/dist/write/reopen.js.map +1 -1
  172. package/dist/write/reorder.js +27 -17
  173. package/dist/write/reorder.js.map +1 -1
  174. package/dist/write/repeat-rule.d.ts +22 -0
  175. package/dist/write/repeat-rule.js +258 -0
  176. package/dist/write/repeat-rule.js.map +1 -0
  177. package/dist/write/reversibility.js +49 -0
  178. package/dist/write/reversibility.js.map +1 -1
  179. package/dist/write/tag-refs.d.ts +47 -0
  180. package/dist/write/tag-refs.js +126 -0
  181. package/dist/write/tag-refs.js.map +1 -0
  182. package/dist/write/undo.d.ts +74 -5
  183. package/dist/write/undo.js +493 -80
  184. package/dist/write/undo.js.map +1 -1
  185. package/dist/write/vectors/registry.d.ts +17 -1
  186. package/dist/write/vectors/registry.js +18 -2
  187. package/dist/write/vectors/registry.js.map +1 -1
  188. package/dist/write/vectors/types.d.ts +119 -3
  189. package/dist/write/vectors/ui-certification.d.ts +48 -0
  190. package/dist/write/vectors/ui-certification.js +46 -0
  191. package/dist/write/vectors/ui-certification.js.map +1 -0
  192. package/dist/write/vectors/ui-drag.d.ts +188 -0
  193. package/dist/write/vectors/ui-drag.js +1095 -0
  194. package/dist/write/vectors/ui-drag.js.map +1 -0
  195. package/dist/write/vectors/ui-recipes.d.ts +70 -0
  196. package/dist/write/vectors/ui-recipes.js +558 -0
  197. package/dist/write/vectors/ui-recipes.js.map +1 -0
  198. package/dist/write/vectors/ui.d.ts +137 -0
  199. package/dist/write/vectors/ui.js +656 -0
  200. package/dist/write/vectors/ui.js.map +1 -0
  201. package/dist/write/verify/delta.d.ts +31 -3
  202. package/dist/write/verify/delta.js +22 -6
  203. package/dist/write/verify/delta.js.map +1 -1
  204. package/dist/write/verify/poller.js +1 -0
  205. package/dist/write/verify/poller.js.map +1 -1
  206. package/package.json +1 -1
  207. package/dist/cli/exit-codes.d.ts +0 -26
  208. package/dist/cli/exit-codes.js +0 -26
  209. package/dist/cli/exit-codes.js.map +0 -1
  210. package/dist/cli/output.d.ts +0 -42
  211. package/dist/cli/output.js +0 -16
  212. package/dist/cli/output.js.map +0 -1
  213. package/dist/read/pagination.d.ts +0 -104
  214. package/dist/read/pagination.js.map +0 -1
@@ -0,0 +1,138 @@
1
+ import { errorEnvelope, ExitCode, openThings } from "../index.js";
2
+ import { indexPastLeadingFlags, subcommandsOf, WRITE_GROUP_ORDER } from "./resolve-invocation.js";
3
+ import { shellQuote } from "./shell-quote.js";
4
+ /** The type-group a resolved reference maps to a write namespace by. */
5
+ const KIND_GROUP = { "to-do": "todo", project: "project", area: "area" };
6
+ /** Split the raw argv into verb, ref, echo-tail, and the global read opts. */
7
+ function parse(args) {
8
+ const at = indexPastLeadingFlags(args) ?? 0;
9
+ const verb = args[at] ?? "";
10
+ const after = args.slice(at + 1);
11
+ let json = false;
12
+ let db;
13
+ let ref = null;
14
+ const tail = [];
15
+ for (let i = 0; i < after.length; i++) {
16
+ const tok = after[i] ?? "";
17
+ if (tok === "--json") {
18
+ json = true;
19
+ }
20
+ else if (tok === "--db") {
21
+ db = after[++i];
22
+ }
23
+ else if (tok.startsWith("--db=")) {
24
+ db = tok.slice("--db=".length);
25
+ }
26
+ else if (!tok.startsWith("-") && ref === null) {
27
+ ref = tok; // the first positional is the write target
28
+ }
29
+ else {
30
+ tail.push(tok);
31
+ }
32
+ }
33
+ // A leading --json/--db (before the verb) still governs output/target.
34
+ for (let i = 0; i < at; i++) {
35
+ const tok = args[i] ?? "";
36
+ if (tok === "--json")
37
+ json = true;
38
+ else if (tok === "--db")
39
+ db = args[++i];
40
+ else if (tok.startsWith("--db="))
41
+ db = tok.slice("--db=".length);
42
+ }
43
+ return { verb, ref, tail, json, db };
44
+ }
45
+ /** Build `things <group> <verb> <ref?> <trailing flags…>` for the suggestion echo. */
46
+ function typedForm(group, verb, ref, tail) {
47
+ const parts = ["things", group, verb, ...(ref !== null ? [shellQuote(ref)] : []), ...tail];
48
+ return parts.join(" ");
49
+ }
50
+ /** The write groups (in a stable order) whose registered verbs include `verb`. */
51
+ function groupsWithVerb(program, verb) {
52
+ return WRITE_GROUP_ORDER.filter((g) => subcommandsOf(program, g).has(verb));
53
+ }
54
+ /** Classify the ref's type with the read client; null when it does not uniquely resolve. */
55
+ function resolvedGroup(db, ref) {
56
+ let client = null;
57
+ try {
58
+ client = openThings(db ? { dbPath: db } : {});
59
+ const target = client.read.showTarget(ref);
60
+ return KIND_GROUP[target.kind] ?? null;
61
+ }
62
+ catch {
63
+ // Ambiguous, unresolvable, or an unreadable db all fall to the generic hint.
64
+ return null;
65
+ }
66
+ finally {
67
+ client?.close();
68
+ }
69
+ }
70
+ /**
71
+ * Compose the suggestion set for a bare verb. The first line is the best guess;
72
+ * the rest are the other namespaced forms worth offering.
73
+ */
74
+ function suggestionsFor(program, p) {
75
+ const verbLower = p.verb.toLowerCase();
76
+ // add/create: the target is a NEW title, so a name never "resolves" — offer
77
+ // to-do first (the common intent), then the container namespaces.
78
+ if (verbLower === "add" || verbLower === "create") {
79
+ if (p.ref === null)
80
+ return [];
81
+ return [
82
+ typedForm("todo", "add", p.ref, p.tail),
83
+ typedForm("project", "add", p.ref, p.tail),
84
+ typedForm("area", "add", p.ref, p.tail),
85
+ ];
86
+ }
87
+ const applicable = groupsWithVerb(program, verbLower);
88
+ // A ref that uniquely resolves to an area/project/to-do gets ONE concrete
89
+ // suggestion — but only when that type actually offers the verb.
90
+ if (p.ref !== null) {
91
+ const group = resolvedGroup(p.db, p.ref);
92
+ if (group !== null && applicable.includes(group)) {
93
+ return [typedForm(group, p.verb, p.ref, p.tail)];
94
+ }
95
+ }
96
+ // Generic: every namespace that offers the verb (fall back to all write
97
+ // groups if the verb is a synonym none register directly).
98
+ const groups = applicable.length > 0 ? applicable : WRITE_GROUP_ORDER;
99
+ return groups.map((g) => typedForm(g, p.verb, p.ref, p.tail));
100
+ }
101
+ /** Human render: the error line, the suggestion(s), and the writes signpost. */
102
+ function renderHuman(verb, suggestions) {
103
+ const lines = [];
104
+ if (suggestions.length === 0) {
105
+ lines.push(`error: \`things ${verb}\` is a write verb but names no target — writes are namespaced.`);
106
+ }
107
+ else if (suggestions.length === 1) {
108
+ lines.push(`error: \`things ${verb} …\` is not a command — did you mean:`);
109
+ lines.push(` ${suggestions[0]}`);
110
+ }
111
+ else {
112
+ lines.push(`error: \`things ${verb} …\` is not a command — writes are namespaced. Try:`);
113
+ for (const s of suggestions)
114
+ lines.push(` ${s}`);
115
+ }
116
+ lines.push("", "See `things help writes` for the full write grammar.");
117
+ return lines.join("\n");
118
+ }
119
+ /**
120
+ * Run the bare-verb hint: emit the suggestion(s) and set the Usage exit code.
121
+ * Never opens the write pipeline — this only reads to classify the ref.
122
+ */
123
+ export function runVerbHint(program, args) {
124
+ const p = parse(args);
125
+ const suggestions = suggestionsFor(program, p);
126
+ if (p.json) {
127
+ const meta = { dbVersion: null, fingerprint: "unknown", elapsedMs: 0 };
128
+ const message = suggestions.length === 1
129
+ ? `\`things ${p.verb} …\` is not a command — did you mean \`${suggestions[0]}\``
130
+ : `\`things ${p.verb} …\` is not a command — writes are namespaced (see \`things help writes\`)`;
131
+ process.stdout.write(`${JSON.stringify(errorEnvelope({ code: "usage", message, details: { suggestions } }, meta))}\n`);
132
+ }
133
+ else {
134
+ process.stderr.write(`${renderHuman(p.verb, suggestions)}\n`);
135
+ }
136
+ process.exitCode = ExitCode.Usage;
137
+ }
138
+ //# sourceMappingURL=verb-hint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verb-hint.js","sourceRoot":"","sources":["../../src/cli/verb-hint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Width-aware TTY row fitting (docs/design/width-aware-tty.md). Terminal list
3
+ * rows never wrap: like the Things GUI, an over-long row truncates its title
4
+ * with a trailing `…` and folds its tags, keeping the always-present metadata
5
+ * (uuid, box, chips, tail markers, the full deadline token) intact. Everything
6
+ * here is pure and takes the target width explicitly, so it is unit-testable
7
+ * without a TTY and byte-stable off one: fitting engages ONLY when the driver
8
+ * resolves a positive width (setFitWidth) — the default is null (no fitting),
9
+ * so pipes, grep, and `--json` are untouched by construction.
10
+ *
11
+ * No runtime dependency (guest e2e bundles ship node + dist + commander only),
12
+ * so the display-width math is a small vendored wcwidth: SGR escapes strip to
13
+ * zero, East-Asian-Wide + emoji-presentation codepoints count two cells, the
14
+ * rest count one. ZWJ-sequence emoji can be off by a cell (accepted — worst
15
+ * case a row runs a hair short); the glyph vocabulary (★ ⏾ ⚑ ↻ ⍾ ≡ ◷) is all
16
+ * ambiguous/narrow class and counts one, so the layout math matches the render.
17
+ */
18
+ /** Drop every SGR (color/dim/bold/…) escape, leaving the visible characters. */
19
+ export declare function stripSgr(s: string): string;
20
+ /** Terminal cells a single codepoint occupies: 0 (combining/zero-width), 2 (wide), else 1. */
21
+ export declare function charWidth(cp: number): number;
22
+ /** Visible terminal width of a string: SGR-stripped, summed per codepoint. */
23
+ export declare function visibleWidth(s: string): number;
24
+ /**
25
+ * Truncate PLAIN text (no SGR) to at most `cols` terminal cells, never splitting
26
+ * a wide codepoint across the boundary. Used to clip a raw title before its
27
+ * styling wrappers are re-applied, so the ellipsis/clip boundary lands OUTSIDE
28
+ * the SGR runs (the fitter never cuts an escape sequence).
29
+ */
30
+ export declare function clipPlain(s: string, cols: number): string;
31
+ /**
32
+ * The title's protected minimum, in columns. NOT a per-row clamp: it is the
33
+ * input to the derived floors (render.ts FULL_FIT_FLOOR / COMPACT_FIT_FLOOR =
34
+ * worst-case furniture + TITLE_MIN) and the threshold the sacrifice order
35
+ * protects the title down to. Because the driver fits every row to at least the
36
+ * compact floor (max(width, COMPACT_FIT_FLOOR)),
37
+ * the worst-furniture row's title lands at exactly TITLE_MIN and every lighter
38
+ * row's title is automatically wider — so this floor is always satisfiable and
39
+ * there is no sub-floor clip (below the floor the terminal wraps, losing nothing).
40
+ */
41
+ export declare const TITLE_MIN = 16;
42
+ /**
43
+ * The named, ordered stages of a row's collapse — the ratified sacrifice order
44
+ * (docs/design/width-aware-tty.md, GUI-measured oracle + the CLI-only container
45
+ * rule). Reordering the sacrifice is editing this list plus {@link fitRow}.
46
+ */
47
+ export type FitStage = "full" | "both-shrink" | "container-drop" | "tags-bare";
48
+ /**
49
+ * The segments of a row the fitter composes. Fixed parts are pre-styled strings
50
+ * (measured, never altered); the two collapsible parts arrive as raw material
51
+ * plus their styling closures so truncation happens on plain text and the SGR
52
+ * wraps the result:
53
+ * - `left` uuid column + box + meta chips (measured; the space before the
54
+ * title is added by the fitter)
55
+ * - `rawTitle` + `styleTitle` — the plain title text and its wrapper
56
+ * - `tail` trailing markers (count chip, ◷ ≡ ≔) — pre-styled incl. its
57
+ * leading space, or ""
58
+ * - `tagNames` raw tag names (no `#`) + `styleTags` wrapping a `#a #b` form
59
+ * (incl. the leading space) — the fitter folds the list
60
+ * - `context` the ` (container)` suffix — pre-styled incl. leading space, or ""
61
+ * - `deadline` the full ⚑ token — pre-styled incl. leading space, or ""
62
+ * - `full` the fully-composed row (the byte-identical no-fit output)
63
+ */
64
+ export interface RowSegments {
65
+ left: string;
66
+ rawTitle: string;
67
+ styleTitle: (text: string) => string;
68
+ tail: string;
69
+ tagNames: string[];
70
+ styleTags: (form: string) => string;
71
+ context: string;
72
+ deadline: string;
73
+ full: string;
74
+ }
75
+ /**
76
+ * Fit one row into `width` terminal cells per the ratified collapse order. The
77
+ * always-present parts (uuid, box, meta chips, tail markers, full deadline)
78
+ * never shrink; title and tags divvy up what remains LAZILY — tags fold to the
79
+ * widest progressive level that fits and the 4:1 ratio arbitrates only when both
80
+ * genuinely contend (slack an under-share side leaves flows to the other). Then
81
+ * the CLI-only container is sacrificed WHOLE (before the last tag folds), then
82
+ * the last tag folds to bare `#…`, and the title bottoms at TITLE_MIN. `width` is
83
+ * the caller's effective width (already max'd with COMPACT_FIT_FLOOR), so the final
84
+ * stage always satisfies the floor; a sub-floor width would simply let the
85
+ * terminal wrap (the title is kept whole rather than clipped). A row that already
86
+ * fits keeps its full content; the only always-applied transform is the
87
+ * experimental deadline gutter ({@link alignDeadline}), which right-pins the `⚑`.
88
+ */
89
+ export declare function fitRow(seg: RowSegments, width: number): string;
90
+ /**
91
+ * The resolved fit for a view: the effective width every row is fitted to, plus
92
+ * whether deadline tokens render in the COMPACT form (the Things iOS narrow-
93
+ * width oracle — `8/12`, `58d ago` — docs/design/width-aware-tty.md § Compact
94
+ * deadline forms). `compact` is decided ONCE from the effective width against
95
+ * the two derived floors, never per row, so a view's right-pinned gutter never
96
+ * mixes `Aug 12` and `8/12`.
97
+ */
98
+ export interface EffectiveFit {
99
+ /** Width to fit each row to — the raw fit width clamped up to the compact floor. */
100
+ width: number;
101
+ /** True when the effective width sits below the full-form floor. */
102
+ compact: boolean;
103
+ }
104
+ /**
105
+ * Resolve the effective fit from the once-resolved fit width and the two
106
+ * glyph-derived floors (`render.ts`: FULL_FIT_FLOOR / COMPACT_FIT_FLOOR). This
107
+ * is the SINGLE decision point for the full-vs-compact deadline form:
108
+ * - `width ≥ full` → full forms, fit to width (today's behavior);
109
+ * - `compact ≤ width < full` → all deadlines compact, fit to width;
110
+ * - `width < compact` → clamp to the compact floor and wrap (sub-floor
111
+ * rule at the lower floor), still compact.
112
+ * The floors live in render.ts because their derivation needs the glyph
113
+ * inventory; the branching lives here so it is pure and unit-testable.
114
+ */
115
+ export declare function resolveFit(fitWidth: number, fullFloor: number, compactFloor: number): EffectiveFit;
116
+ /**
117
+ * Set the process-wide fit width (columns), resolved ONCE by the CLI driver at
118
+ * startup. `null` disables fitting (the default — every non-TTY path stays
119
+ * byte-stable). Tests set this explicitly and reset it afterward.
120
+ */
121
+ export declare function setFitWidth(width: number | null): void;
122
+ /** The current fit width, or null when fitting is disabled. */
123
+ export declare function getFitWidth(): number | null;
124
+ /**
125
+ * Resolve the fit width from the environment and stdout: `THINGS_WIDTH` wins
126
+ * when set (a positive integer forces that width; `0` disables fitting), else
127
+ * the terminal's column count when stdout is a TTY, else null (pipes, grep,
128
+ * captured output — no fitting, byte-stable). A malformed `THINGS_WIDTH` is
129
+ * ignored (falls through to the TTY/null resolution).
130
+ */
131
+ export declare function resolveWidth(opts: {
132
+ env: Record<string, string | undefined>;
133
+ columns: number | undefined;
134
+ isTTY: boolean;
135
+ }): number | null;
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Width-aware TTY row fitting (docs/design/width-aware-tty.md). Terminal list
3
+ * rows never wrap: like the Things GUI, an over-long row truncates its title
4
+ * with a trailing `…` and folds its tags, keeping the always-present metadata
5
+ * (uuid, box, chips, tail markers, the full deadline token) intact. Everything
6
+ * here is pure and takes the target width explicitly, so it is unit-testable
7
+ * without a TTY and byte-stable off one: fitting engages ONLY when the driver
8
+ * resolves a positive width (setFitWidth) — the default is null (no fitting),
9
+ * so pipes, grep, and `--json` are untouched by construction.
10
+ *
11
+ * No runtime dependency (guest e2e bundles ship node + dist + commander only),
12
+ * so the display-width math is a small vendored wcwidth: SGR escapes strip to
13
+ * zero, East-Asian-Wide + emoji-presentation codepoints count two cells, the
14
+ * rest count one. ZWJ-sequence emoji can be off by a cell (accepted — worst
15
+ * case a row runs a hair short); the glyph vocabulary (★ ⏾ ⚑ ↻ ⍾ ≡ ◷) is all
16
+ * ambiguous/narrow class and counts one, so the layout math matches the render.
17
+ */
18
+ const SGR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
19
+ /** Drop every SGR (color/dim/bold/…) escape, leaving the visible characters. */
20
+ export function stripSgr(s) {
21
+ return s.replace(SGR, "");
22
+ }
23
+ // Codepoint ranges that occupy TWO terminal cells: the East-Asian Wide/Fullwidth
24
+ // core plus the default-emoji-presentation set (so "⭐️" counts 2 while the
25
+ // ambiguous-class "★" counts 1 — the property that keeps our glyph set narrow).
26
+ const WIDE_RANGES = [
27
+ [0x1100, 0x115f], // Hangul Jamo
28
+ [0x2e80, 0x303e], // CJK radicals … Kangxi
29
+ [0x3041, 0x33ff], // Hiragana … CJK compat
30
+ [0x3400, 0x4dbf], // CJK Ext A
31
+ [0x4e00, 0x9fff], // CJK Unified
32
+ [0xa000, 0xa4cf], // Yi
33
+ [0xac00, 0xd7a3], // Hangul Syllables
34
+ [0xf900, 0xfaff], // CJK Compat Ideographs
35
+ [0xfe30, 0xfe4f], // CJK Compat Forms
36
+ [0xff00, 0xff60], // Fullwidth Forms
37
+ [0xffe0, 0xffe6], // Fullwidth signs
38
+ // Emoji-presentation (default-wide) — the codepoints Unicode marks
39
+ // Emoji_Presentation=Yes outside the SMP blocks below.
40
+ [0x231a, 0x231b],
41
+ [0x23e9, 0x23ec],
42
+ [0x23f0, 0x23f0],
43
+ [0x23f3, 0x23f3],
44
+ [0x25fd, 0x25fe],
45
+ [0x2614, 0x2615],
46
+ [0x2648, 0x2653],
47
+ [0x267f, 0x267f],
48
+ [0x2693, 0x2693],
49
+ [0x26a1, 0x26a1],
50
+ [0x26aa, 0x26ab],
51
+ [0x26bd, 0x26be],
52
+ [0x26c4, 0x26c5],
53
+ [0x26ce, 0x26ce],
54
+ [0x26d4, 0x26d4],
55
+ [0x26ea, 0x26ea],
56
+ [0x26f2, 0x26f3],
57
+ [0x26f5, 0x26f5],
58
+ [0x26fa, 0x26fa],
59
+ [0x26fd, 0x26fd],
60
+ [0x2705, 0x2705],
61
+ [0x270a, 0x270b],
62
+ [0x2728, 0x2728],
63
+ [0x274c, 0x274c],
64
+ [0x274e, 0x274e],
65
+ [0x2753, 0x2755],
66
+ [0x2757, 0x2757],
67
+ [0x2795, 0x2797],
68
+ [0x27b0, 0x27b0],
69
+ [0x27bf, 0x27bf],
70
+ [0x2b1b, 0x2b1c],
71
+ [0x2b50, 0x2b50], // ⭐ WHITE MEDIUM STAR (Emoji_Presentation — 2 cells)
72
+ [0x2b55, 0x2b55],
73
+ [0x1f000, 0x1faff], // SMP emoji blocks (mahjong … symbols & pictographs ext)
74
+ [0x20000, 0x3fffd], // CJK Ext B+ (SIP/TIP)
75
+ ];
76
+ // Zero-width: combining marks, ZW(SP/NJ/J), and variation selectors. Treating
77
+ // VS16 (FE0F) as zero-width means an emoji-presentation base already counted 2
78
+ // stays 2 (rather than 3) — the "⭐️" case.
79
+ const ZERO_RANGES = [
80
+ [0x0300, 0x036f], // combining diacritics
81
+ [0x200b, 0x200f], // ZWSP … RLM (incl. ZWJ 200d)
82
+ [0xfe00, 0xfe0f], // variation selectors
83
+ ];
84
+ function inRanges(cp, ranges) {
85
+ for (const [lo, hi] of ranges)
86
+ if (cp >= lo && cp <= hi)
87
+ return true;
88
+ return false;
89
+ }
90
+ /** Terminal cells a single codepoint occupies: 0 (combining/zero-width), 2 (wide), else 1. */
91
+ export function charWidth(cp) {
92
+ if (cp === 0)
93
+ return 0;
94
+ if (inRanges(cp, ZERO_RANGES))
95
+ return 0;
96
+ if (inRanges(cp, WIDE_RANGES))
97
+ return 2;
98
+ return 1;
99
+ }
100
+ /** Visible terminal width of a string: SGR-stripped, summed per codepoint. */
101
+ export function visibleWidth(s) {
102
+ let w = 0;
103
+ for (const ch of stripSgr(s))
104
+ w += charWidth(ch.codePointAt(0) ?? 0);
105
+ return w;
106
+ }
107
+ /**
108
+ * Truncate PLAIN text (no SGR) to at most `cols` terminal cells, never splitting
109
+ * a wide codepoint across the boundary. Used to clip a raw title before its
110
+ * styling wrappers are re-applied, so the ellipsis/clip boundary lands OUTSIDE
111
+ * the SGR runs (the fitter never cuts an escape sequence).
112
+ */
113
+ export function clipPlain(s, cols) {
114
+ if (cols <= 0)
115
+ return "";
116
+ let w = 0;
117
+ let out = "";
118
+ for (const ch of s) {
119
+ const cw = charWidth(ch.codePointAt(0) ?? 0);
120
+ if (w + cw > cols)
121
+ break;
122
+ out += ch;
123
+ w += cw;
124
+ }
125
+ return out;
126
+ }
127
+ // ── Row fitting ─────────────────────────────────────────────────────────────
128
+ /**
129
+ * TITLE : TAGS relative max-width ratio (tunable — ONE place). It arbitrates the
130
+ * collapsible budget ONLY under contention — when BOTH segments' natural widths
131
+ * exceed their shares. Otherwise the fit is lazy: the side within its share keeps
132
+ * its natural width and the other takes everything left (see {@link fitRow}). 4:1
133
+ * keeps the title dominant when they do genuinely contend.
134
+ */
135
+ const TITLE_RATIO = 4;
136
+ const TAGS_RATIO = 1;
137
+ /**
138
+ * The title's protected minimum, in columns. NOT a per-row clamp: it is the
139
+ * input to the derived floors (render.ts FULL_FIT_FLOOR / COMPACT_FIT_FLOOR =
140
+ * worst-case furniture + TITLE_MIN) and the threshold the sacrifice order
141
+ * protects the title down to. Because the driver fits every row to at least the
142
+ * compact floor (max(width, COMPACT_FIT_FLOOR)),
143
+ * the worst-furniture row's title lands at exactly TITLE_MIN and every lighter
144
+ * row's title is automatically wider — so this floor is always satisfiable and
145
+ * there is no sub-floor clip (below the floor the terminal wraps, losing nothing).
146
+ */
147
+ export const TITLE_MIN = 16;
148
+ /** Overflow marker for folded tags (dim styling applied by the row's styleTags). */
149
+ const TAG_OVERFLOW = "#…";
150
+ /**
151
+ * Plain form (no styling, no leading space) of the tag run showing the first `k`
152
+ * real tags; when `k` is below the total, the dropped tags fold into the dim
153
+ * `#…` marker. `k === 0` is the bare marker.
154
+ */
155
+ function tagForm(names, k) {
156
+ const shown = names.slice(0, k).map((n) => `#${n}`);
157
+ if (k < names.length)
158
+ shown.push(TAG_OVERFLOW);
159
+ return shown.join(" ");
160
+ }
161
+ /** Visible width of the rendered tag run at level `k` (its leading space + form). */
162
+ function tagWidth(names, k) {
163
+ if (names.length === 0)
164
+ return 0;
165
+ return 1 + visibleWidth(tagForm(names, k));
166
+ }
167
+ /** Widest tag level (most real tags) whose render fits `cap`, floored at `floorK`. */
168
+ function pickTagLevel(names, cap, floorK) {
169
+ for (let k = names.length; k >= floorK; k--) {
170
+ if (tagWidth(names, k) <= cap)
171
+ return k;
172
+ }
173
+ return floorK;
174
+ }
175
+ /**
176
+ * EXPERIMENTAL — the right-aligned deadline gutter (GUI flag-column parity). When
177
+ * fitting is active, every fitted row's `⚑` token is pushed flush to the effective
178
+ * width so all deadlines in a view line up in one gutter, exactly like the app's
179
+ * right-pinned flag column. `body` is the composed row WITHOUT its deadline and
180
+ * `deadline` the pre-styled ` ⚑ …` segment (its leading space + token) or "".
181
+ * The padding is plain spaces OUTSIDE the styled token, and there is always ≥ 1
182
+ * space — so a row that already fills the width is byte-identical to the inline
183
+ * (unguttered) form, and a row with no deadline is returned untouched (ragged
184
+ * right, as the GUI leaves flag-less rows). This is only ever reached from
185
+ * {@link fitRow} (fitting active), so the byte-stable null path never sees it.
186
+ * Reverting the experiment is deleting this function and its two `fitRow` calls.
187
+ */
188
+ function alignDeadline(body, deadline, width) {
189
+ if (deadline === "")
190
+ return body;
191
+ // deadline = " " + token; swap that single inline space for the gutter run.
192
+ const pad = width - visibleWidth(body) - visibleWidth(deadline) + 1;
193
+ return `${body}${" ".repeat(Math.max(1, pad))}${deadline.slice(1)}`;
194
+ }
195
+ /**
196
+ * Fit one row into `width` terminal cells per the ratified collapse order. The
197
+ * always-present parts (uuid, box, meta chips, tail markers, full deadline)
198
+ * never shrink; title and tags divvy up what remains LAZILY — tags fold to the
199
+ * widest progressive level that fits and the 4:1 ratio arbitrates only when both
200
+ * genuinely contend (slack an under-share side leaves flows to the other). Then
201
+ * the CLI-only container is sacrificed WHOLE (before the last tag folds), then
202
+ * the last tag folds to bare `#…`, and the title bottoms at TITLE_MIN. `width` is
203
+ * the caller's effective width (already max'd with COMPACT_FIT_FLOOR), so the final
204
+ * stage always satisfies the floor; a sub-floor width would simply let the
205
+ * terminal wrap (the title is kept whole rather than clipped). A row that already
206
+ * fits keeps its full content; the only always-applied transform is the
207
+ * experimental deadline gutter ({@link alignDeadline}), which right-pins the `⚑`.
208
+ */
209
+ export function fitRow(seg, width) {
210
+ const names = seg.tagNames;
211
+ const wLeft = visibleWidth(seg.left) + 1; // + the space before the title
212
+ const wTail = visibleWidth(seg.tail);
213
+ const wCtx = visibleWidth(seg.context);
214
+ const wDead = visibleWidth(seg.deadline);
215
+ const wTitle = visibleWidth(seg.rawTitle);
216
+ // A naturally short title never floors at TITLE_MIN — it simply fits whole.
217
+ const titleNeed = Math.min(TITLE_MIN, wTitle);
218
+ const fixed = (ctx) => wLeft + wTail + wDead + (ctx ? wCtx : 0);
219
+ const fitTitle = (budget) => wTitle <= budget
220
+ ? seg.styleTitle(seg.rawTitle)
221
+ : seg.styleTitle(`${clipPlain(seg.rawTitle, budget - 1)}…`);
222
+ const renderRow = (title, k, ctx) => {
223
+ const tags = names.length > 0 ? seg.styleTags(tagForm(names, k)) : "";
224
+ const body = `${seg.left} ${title}${seg.tail}${tags}${ctx ? seg.context : ""}`;
225
+ return alignDeadline(body, seg.deadline, width);
226
+ };
227
+ // Stage "full": everything fits with tags whole and the container present.
228
+ // (The deadline is still pushed to the gutter — GUI flags pin right even on a
229
+ // short row — but the null path returns seg.full before reaching the fitter.)
230
+ const budgetFull = width - fixed(true);
231
+ if (wTitle + tagWidth(names, names.length) <= budgetFull) {
232
+ const body = seg.full.slice(0, seg.full.length - seg.deadline.length);
233
+ return alignDeadline(body, seg.deadline, width);
234
+ }
235
+ // Stage "both-shrink": tags fold LAZILY — to the WIDEST progressive level that
236
+ // fits, not an eager 1/5 cap. The 4:1 ratio arbitrates ONLY under contention
237
+ // (both natural widths exceed their shares of the collapsible budget). When the
238
+ // title is within its 4/5 share it keeps its natural width and the tags get
239
+ // everything left (their widest fitting level); when instead the tags are
240
+ // within their 1/5 share they stay whole and the title gets the rest. Either
241
+ // way, slack a discrete tag level does not consume flows BACK to the title, and
242
+ // neither side is shrunk below what the budget actually forces. Container kept.
243
+ const tagShare = Math.floor((budgetFull * TAGS_RATIO) / (TITLE_RATIO + TAGS_RATIO));
244
+ const titleShare = budgetFull - tagShare;
245
+ const tagCap = wTitle <= titleShare ? budgetFull - wTitle : tagShare;
246
+ const k = pickTagLevel(names, tagCap, 1);
247
+ const titleBudgetB = budgetFull - tagWidth(names, k);
248
+ if (titleBudgetB >= titleNeed)
249
+ return renderRow(fitTitle(titleBudgetB), k, true);
250
+ // Stage "container-drop": the CLI-only container yields WHOLE (a truncated
251
+ // parenthetical is worse than none) BEFORE the last tag folds — tags hold at
252
+ // `#first #…`, the reclaimed width goes to the title.
253
+ const budgetNoCtx = width - fixed(false);
254
+ const titleBudgetC = budgetNoCtx - tagWidth(names, 1);
255
+ if (titleBudgetC >= titleNeed)
256
+ return renderRow(fitTitle(titleBudgetC), 1, false);
257
+ // Stage "tags-bare": the last tag folds to the bare `#…` marker (container
258
+ // already gone), the title floors at TITLE_MIN. No end-clip: at an effective
259
+ // width this budget clears the floor; a sub-floor width keeps the title whole
260
+ // and lets the terminal wrap (never dropping the deadline).
261
+ const titleBudgetD = budgetNoCtx - tagWidth(names, 0);
262
+ const dTitle = titleBudgetD >= 1 ? fitTitle(titleBudgetD) : seg.styleTitle(seg.rawTitle);
263
+ return renderRow(dTitle, 0, false);
264
+ }
265
+ /**
266
+ * Resolve the effective fit from the once-resolved fit width and the two
267
+ * glyph-derived floors (`render.ts`: FULL_FIT_FLOOR / COMPACT_FIT_FLOOR). This
268
+ * is the SINGLE decision point for the full-vs-compact deadline form:
269
+ * - `width ≥ full` → full forms, fit to width (today's behavior);
270
+ * - `compact ≤ width < full` → all deadlines compact, fit to width;
271
+ * - `width < compact` → clamp to the compact floor and wrap (sub-floor
272
+ * rule at the lower floor), still compact.
273
+ * The floors live in render.ts because their derivation needs the glyph
274
+ * inventory; the branching lives here so it is pure and unit-testable.
275
+ */
276
+ export function resolveFit(fitWidth, fullFloor, compactFloor) {
277
+ const width = Math.max(fitWidth, compactFloor);
278
+ return { width, compact: width < fullFloor };
279
+ }
280
+ // ── Width resolution + the module-level fit width ────────────────────────────
281
+ let fitWidth = null;
282
+ /**
283
+ * Set the process-wide fit width (columns), resolved ONCE by the CLI driver at
284
+ * startup. `null` disables fitting (the default — every non-TTY path stays
285
+ * byte-stable). Tests set this explicitly and reset it afterward.
286
+ */
287
+ export function setFitWidth(width) {
288
+ fitWidth = width;
289
+ }
290
+ /** The current fit width, or null when fitting is disabled. */
291
+ export function getFitWidth() {
292
+ return fitWidth;
293
+ }
294
+ /**
295
+ * Resolve the fit width from the environment and stdout: `THINGS_WIDTH` wins
296
+ * when set (a positive integer forces that width; `0` disables fitting), else
297
+ * the terminal's column count when stdout is a TTY, else null (pipes, grep,
298
+ * captured output — no fitting, byte-stable). A malformed `THINGS_WIDTH` is
299
+ * ignored (falls through to the TTY/null resolution).
300
+ */
301
+ export function resolveWidth(opts) {
302
+ const raw = opts.env["THINGS_WIDTH"];
303
+ if (raw !== undefined) {
304
+ const n = Number(raw);
305
+ if (Number.isInteger(n) && n >= 0)
306
+ return n === 0 ? null : n;
307
+ // malformed — fall through to the TTY resolution
308
+ }
309
+ if (opts.isTTY && typeof opts.columns === "number" && opts.columns > 0)
310
+ return opts.columns;
311
+ return null;
312
+ }
313
+ //# sourceMappingURL=width.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"width.js","sourceRoot":"","sources":["../../src/cli/width.ts"],"names":[],"mappings":"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"}