Skip to content

Commit 653a004

Browse files
committed
enhance(cli): display width in all list-* cmds
1 parent 9855011 commit 653a004

7 files changed

Lines changed: 445 additions & 32 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Logseq CLI List Title Display Width Implementation Plan
2+
3+
Goal: Make every `TITLE` column in `logseq list *` human output use a fixed maximum visual width with correct CJK-aware width handling via `string-width`.
4+
5+
Architecture: Keep list data retrieval unchanged in db-worker-node thread APIs and implement presentation-only changes in the CLI formatter layer.
6+
7+
Architecture: Introduce a display-width-aware truncation and padding path that is applied to list title cells before table rendering.
8+
9+
Tech Stack: ClojureScript, `logseq.cli.format`, `logseq.cli.command.list`, db-worker thread APIs in `frontend.worker.db-core`, JavaScript `string-width`.
10+
11+
Related: Builds on `docs/agent-guide/062-logseq-cli-list-default-sort-updated-at.md`, `docs/agent-guide/066-logseq-cli-list-property-cardinality-column.md`, `docs/agent-guide/078-logseq-cli-task-subcommands.md`, and `docs/cli/logseq-cli.md`.
12+
13+
## Problem statement
14+
15+
Current human table rendering in `src/main/logseq/cli/format.cljs` uses `(count text)` for width calculation and right padding.
16+
17+
`count` is code-point length, not terminal display width.
18+
19+
This breaks alignment when titles contain CJK or mixed-width text.
20+
21+
Current `list` human output also allows unbounded title length, which can push important columns out of view for `list page`, `list tag`, `list property`, and `list task`.
22+
23+
We need a stable, readable table contract where `TITLE` is capped to a reasonable visual width and aligned correctly for mixed-language content.
24+
25+
## Current baseline from implementation
26+
27+
The active CLI stack is the `src/main/logseq/cli/*` implementation, not the legacy `deps/cli` command stack.
28+
29+
List command data is produced by db-worker helpers and returned through thread APIs without formatter concerns.
30+
31+
The relevant path is:
32+
33+
```text
34+
logseq list <page|tag|property|task>
35+
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs
36+
-> transport/invoke :thread-api/cli-list-*
37+
-> /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs
38+
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/common/db_worker.cljs
39+
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljs
40+
```
41+
42+
`TITLE` columns are defined in formatter column configs in `src/main/logseq/cli/format.cljs`.
43+
44+
All table rendering goes through `render-table` and `format-counted-table` in the same file.
45+
46+
## Scope
47+
48+
In scope commands are:
49+
50+
| Command | Human output table has `TITLE` column | Source formatter function |
51+
| --- | --- | --- |
52+
| `logseq list page` | Yes | `format-list-page` |
53+
| `logseq list tag` | Yes | `format-list-tag` |
54+
| `logseq list property` | Yes | `format-list-property` |
55+
| `logseq list task` | Yes | `format-list-task` |
56+
57+
The default max visual width target for title is `40` columns.
58+
59+
The value is configurable in `cli.edn` via a new CLI config key.
60+
61+
The cap is measured by display width, not character count.
62+
63+
Out of scope commands include `search *`, `query list`, `graph list`, and `server list`.
64+
65+
Out of scope outputs include JSON and EDN payload shape changes.
66+
67+
## Design decisions
68+
69+
### 1) Keep db-worker-node payloads unchanged
70+
71+
No changes are required in `src/main/logseq/cli/common/db_worker.cljs` or thread API wiring in `src/main/frontend/worker/db_core.cljs`.
72+
73+
db-worker should continue returning full `:block/title` values.
74+
75+
Human formatting is the only layer that applies visual truncation.
76+
77+
### 2) Add explicit title max-width policy in formatter
78+
79+
Introduce a formatter default constant such as `list-human-title-max-display-width-default` with value `40`.
80+
81+
Read an optional override from resolved CLI config using a new `cli.edn` key `:list-title-max-display-width`.
82+
83+
Apply this policy only to `TITLE` extractors in list column definitions.
84+
85+
Truncated values append a suffix `` and remain within the max display width.
86+
87+
### 3) Use `string-width` for display width operations
88+
89+
Add npm interop in `src/main/logseq/cli/format.cljs` for `string-width`.
90+
91+
Use `string-width` for:
92+
93+
- Width measurement during truncation.
94+
- Width measurement during table column width calculation.
95+
- Padding logic in `pad-right` or equivalent.
96+
97+
This ensures CJK and mixed-width titles align with other columns.
98+
99+
### 4) Preserve existing list data semantics
100+
101+
Sorting, filtering, `--fields`, `--limit`, and `--offset` behavior in `src/main/logseq/cli/command/list.cljs` remain unchanged.
102+
103+
Only rendered human strings are affected.
104+
105+
### 5) Keep non-list command behavior stable
106+
107+
Prefer implementing title truncation in list-specific extractors.
108+
109+
If `render-table` is upgraded to display-width-aware padding globally, verify non-list tables are unaffected except improved alignment.
110+
111+
## Proposed implementation steps
112+
113+
1. Invoke `@test-driven-development` and add failing formatter tests for long ASCII title truncation in each list command.
114+
115+
2. Add failing formatter tests for long CJK and mixed CJK/ASCII titles in each list command.
116+
117+
3. Add failing config resolution tests in `src/test/logseq/cli/config_test.cljs` for `:list-title-max-display-width`, including default fallback to `40` and valid `cli.edn` override.
118+
119+
4. Add a focused formatter unit test for truncation helper behavior ensuring output display width is `<= 40` and suffix handling is correct.
120+
121+
5. Add a focused formatter unit test for width-aware padding behavior with CJK text so column alignment is deterministic.
122+
123+
6. Add config normalization helpers in `src/main/logseq/cli/config.cljs` to parse and validate `:list-title-max-display-width` as a positive integer.
124+
125+
7. Add `string-width` import and helper functions in `src/main/logseq/cli/format.cljs`.
126+
127+
8. Add title truncation helper and wire it into the `TITLE` extractor entries in `list-page-columns`, `list-tag-columns`, `list-property-columns`, and `list-task-columns`.
128+
129+
9. Replace `(count ...)`-based width and padding internals in table rendering with display-width-aware logic.
130+
131+
10. Run formatter and config tests and update exact spacing snapshots where needed.
132+
133+
11. Update user-facing docs in `docs/cli/logseq-cli.md` to mention that list human `TITLE` uses default width `40`, supports `cli.edn` override, and always truncates with ``.
134+
135+
12. Optionally add one CLI e2e non-sync human-output case for long mixed-width title rendering if we want package-level regression protection.
136+
137+
## Files to modify
138+
139+
- `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/config.cljs`.
140+
- `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/config_test.cljs`.
141+
- `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljs`.
142+
- `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs`.
143+
- `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md`.
144+
- `/Users/rcmerci/gh-repos/logseq/cli-e2e/spec/non_sync_cases.edn` (optional, only if we decide to cover human output end-to-end).
145+
- `/Users/rcmerci/gh-repos/logseq/package.json` (only if `string-width` is added as an explicit direct dependency).
146+
147+
## Edge cases to cover
148+
149+
A title with only CJK characters that exceeds max width.
150+
151+
A title with alternating CJK and ASCII characters.
152+
153+
A title exactly at width boundary.
154+
155+
A title one display cell over boundary.
156+
157+
A title containing emoji or variation selectors.
158+
159+
A nil title where current logic falls back to `-`.
160+
161+
A title containing newlines where current multiline row rendering is preserved.
162+
163+
A very short title where no extra truncation marker appears.
164+
165+
An invalid `cli.edn` width value such as `0`, negative numbers, or non-numeric values that must fall back to default `40`.
166+
167+
## Risks and mitigation
168+
169+
Risk: Moving width calculations to display width can shift spacing in non-list tables.
170+
171+
Mitigation: Add or adjust tests for affected command outputs and constrain behavioral changes to alignment only.
172+
173+
Risk: `string-width` import style may vary by bundling mode.
174+
175+
Mitigation: Verify in unit tests and CLI runtime, and choose the interop form that matches current CJS usage conventions in the repo.
176+
177+
Risk: New truncation can reduce discoverability of long titles.
178+
179+
Mitigation: Keep JSON and EDN outputs untruncated and document this behavior clearly.
180+
181+
Risk: Invalid `cli.edn` width values can produce inconsistent rendering if not normalized.
182+
183+
Mitigation: Parse as positive integer with fallback to default `40` and add config tests for invalid values.
184+
185+
## Open questions
186+
187+
No open question.
188+
189+
Decisions locked for this plan are default width `40`, `cli.edn` configurability, truncation suffix ``, and list-only scope.
190+
191+
## Testing Plan
192+
193+
I will add formatter behavior tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs` that verify title truncation and alignment for `list page`, `list tag`, `list property`, and `list task` human output.
194+
195+
I will add helper-level tests in the same test namespace to validate width-aware truncation and padding with CJK and mixed-width samples.
196+
197+
I will add config tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/config_test.cljs` to validate default value `40`, `cli.edn` override, and invalid-value fallback behavior.
198+
199+
I will run focused tests first with `bb dev:test -v logseq.cli.format-test/test-human-output-list-page`, `bb dev:test -v logseq.cli.format-test/test-human-output-list-tag-property`, and `bb dev:test -v logseq.cli.format-test/test-human-output-list-task`.
200+
201+
I will run broader CLI tests with `bb dev:test -v logseq.cli.format-test` and config tests with `bb dev:test -v logseq.cli.config-test`.
202+
203+
If e2e coverage is added, I will run `bb -f cli-e2e/bb.edn test --skip-build`.
204+
205+
I will finish with `bb dev:lint-and-test`.
206+
207+
NOTE: I will write *all* tests before I add any implementation behavior.
208+
209+
## Step-by-step execution checklist
210+
211+
| Step | Action | Verification |
212+
| --- | --- | --- |
213+
| 1 | Add failing list title truncation tests. | Tests fail with current unbounded title output. |
214+
| 2 | Add failing CJK width alignment tests. | Tests fail due current `count`-based width logic. |
215+
| 3 | Add failing config tests for `:list-title-max-display-width`. | Tests fail before config parsing is implemented. |
216+
| 4 | Implement config parsing with default `40` and invalid fallback. | Config tests pass for default and override behavior. |
217+
| 5 | Implement `string-width` helpers. | Helper tests pass. |
218+
| 6 | Implement title truncation in list column extractors. | List tests show bounded title width and `` suffix. |
219+
| 7 | Switch table padding/width to display-width-aware logic. | CJK alignment tests pass. |
220+
| 8 | Update docs and optional e2e. | Docs mention truncation behavior and tests stay green. |
221+
| 9 | Run full regression. | `bb dev:lint-and-test` passes. |
222+
223+
## Testing Details
224+
225+
The tests validate observable CLI behavior by asserting final human output strings for list commands, including truncation marker presence and table alignment under mixed-width input.
226+
227+
The tests do not assert internal helper data structures beyond what is needed to validate final output semantics.
228+
229+
## Implementation Details
230+
231+
- Keep default max title width as formatter constant `40`.
232+
- Allow override from `cli.edn` key `:list-title-max-display-width`.
233+
- Parse config width as positive integer and fall back to default on invalid values.
234+
- Truncate by display width instead of character count.
235+
- Keep truncation policy limited to list `TITLE` columns.
236+
- Always use `` as truncation suffix in human list output.
237+
- Preserve db-worker payload contracts and thread API names.
238+
- Preserve JSON and EDN full title output.
239+
- Ensure multiline table behavior is not regressed.
240+
- Document human-output-only truncation and config override in CLI docs.
241+
242+
## Question
243+
244+
No blocking question for implementation.
245+
246+
---

docs/cli/logseq-cli.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Supported keys include:
5757
- `:data-dir`
5858
- `:timeout-ms`
5959
- `:output-format` (use `:json` or `:edn` for scripting)
60+
- `:list-title-max-display-width` (human `list *` TITLE max display width, default `40`)
6061
- sync config persisted via `sync config set|get|unset`: `:ws-url`, `:http-base`
6162

6263
Legacy migration note:
@@ -288,7 +289,7 @@ Output formats:
288289
139ms └── cli.execute-action
289290
129ms └── transport.invoke:thread-api/cli-list-pages
290291
```
291-
- Human output is plain text. List/search commands render tables with a final `Count: N` line. For list and search subcommands, the ID column uses `:db/id` (not UUID). If `:db/ident` exists, an `IDENT` column is included. `list property` includes dedicated `TYPE` and `CARDINALITY` columns. Search table columns are `ID` and `TITLE`. Block titles can include multiple lines; multi-line rows align additional lines under the `TITLE` column. Times such as list `UPDATED-AT`/`CREATED-AT` and `graph info` `Created at` are shown in human-friendly relative form. Errors include error codes and may include a `Hint:` line. Use `--output json|edn` for structured output.
292+
- Human output is plain text. List/search commands render tables with a final `Count: N` line. For list and search subcommands, the ID column uses `:db/id` (not UUID). If `:db/ident` exists, an `IDENT` column is included. `list property` includes dedicated `TYPE` and `CARDINALITY` columns. Search table columns are `ID` and `TITLE`. For `list page|tag|property|task` in human output, the `TITLE` column is display-width-aware (CJK-safe), defaults to max width `40`, and truncates overflow with `…`; set `:list-title-max-display-width` in `cli.edn` to override. JSON/EDN outputs keep full titles (no truncation). Block titles can include multiple lines; multi-line rows align additional lines under the `TITLE` column. Times such as list `UPDATED-AT`/`CREATED-AT` and `graph info` `Created at` are shown in human-friendly relative form. Errors include error codes and may include a `Hint:` line. Use `--output json|edn` for structured output.
292293
- `example` human output includes `Selector`, `Matched commands`, and `Examples` sections. Structured output (`json`/`edn`) includes `selector`, `matched-commands`, `examples`, and `message` fields under `data`.
293294
- `sync download` progress lines are streamed to stdout only when progress is enabled. In `json`/`edn` mode, progress is disabled by default unless `--progress true` is provided.
294295
- JSON machine output preserves namespaced keyword semantics:

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
"remove-accents": "0.5.0",
187187
"sanitize-filename": "1.6.4",
188188
"send-intent": "^7.0.0",
189+
"string-width": "8.2.0",
189190
"url": "^0.11.4",
190191
"util": "^0.12.5"
191192
},

src/main/logseq/cli/config.cljs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@
1313
(when (and (some? value) (not (string/blank? value)))
1414
(js/parseInt value 10)))
1515

16+
(def ^:private list-title-max-display-width-default 40)
17+
18+
(defn- parse-positive-int
19+
[value]
20+
(cond
21+
(and (number? value)
22+
(integer? value)
23+
(pos? value))
24+
value
25+
26+
(string? value)
27+
(let [trimmed (string/trim value)]
28+
(when (re-matches #"[1-9]\d*" trimmed)
29+
(js/parseInt trimmed 10)))
30+
31+
:else nil))
32+
1633
(def ^:private output-formats
1734
#{:human :json :edn})
1835

@@ -98,6 +115,7 @@
98115
(let [defaults {:timeout-ms 10000
99116
:login-timeout-ms 300000
100117
:logout-timeout-ms 120000
118+
:list-title-max-display-width list-title-max-display-width-default
101119
:output-format nil
102120
:data-dir (common-graph/get-default-graphs-dir)
103121
:ws-url "wss://api-staging.logseq.io/sync/%s"
@@ -114,6 +132,9 @@
114132
(parse-output-format (:output env))
115133
(parse-output-format (:output-format file-config))
116134
(parse-output-format (:output file-config)))
117-
merged (merge defaults file-config env opts {:config-path config-path})]
135+
merged (merge defaults file-config env opts {:config-path config-path})
136+
list-title-max-display-width (or (parse-positive-int (:list-title-max-display-width merged))
137+
list-title-max-display-width-default)]
118138
(cond-> merged
119-
output-format (assoc :output-format output-format))))
139+
output-format (assoc :output-format output-format)
140+
true (assoc :list-title-max-display-width list-title-max-display-width))))

0 commit comments

Comments
 (0)