From 322d31448eb9b80f5178dfe049ed3c043ea94c8e Mon Sep 17 00:00:00 2001 From: rcmerci Date: Sat, 31 Jan 2026 15:58:35 +0800 Subject: [PATCH] 024-logseq-cli-show-updates.md --- .../024-logseq-cli-show-updates.md | 99 ++++++++++++ src/main/logseq/cli/command/show.cljs | 46 ++++-- src/test/logseq/cli/commands_test.cljs | 146 +++++++++++++++++- 3 files changed, 274 insertions(+), 17 deletions(-) create mode 100644 docs/agent-guide/024-logseq-cli-show-updates.md diff --git a/docs/agent-guide/024-logseq-cli-show-updates.md b/docs/agent-guide/024-logseq-cli-show-updates.md new file mode 100644 index 0000000000..4bc6d1d19e --- /dev/null +++ b/docs/agent-guide/024-logseq-cli-show-updates.md @@ -0,0 +1,99 @@ +# Logseq CLI Show Output & Linked References Options Plan + +Goal: Update the `logseq show` command to (1) render the ID column in human output with lighter styling (matching tree glyphs), (2) include `:db/ident` on block entities in JSON/EDN output when present, and (3) add an option to toggle Linked References (default enabled). +Architecture: Keep the existing logseq-cli → db-worker-node HTTP transport and thread-api calls; adjust show command selectors and output formatting only. +Tech Stack: ClojureScript, babashka.cli, db-worker-node transport, Logseq pull selectors, CLI styling helpers. +Related: Builds on `logseq.cli.command.show` tree rendering and linked references fetch; see `docs/agent-guide/010-logseq-cli-show-linked-references.md` and `docs/agent-guide/023-logseq-cli-help-show-styling.md`. + +## Problem Statement + +The `show` command currently renders the ID column with the same styling as regular text, does not expose `:db/ident` in JSON/EDN output for blocks, and always fetches/prints Linked References. We need to make the ID column visually lighter, surface `:db/ident` when it exists, and add a switch to disable linked references (defaulting to true). + +## Non-Goals + +- Changing db-worker-node APIs or transport protocols. +- Altering the show command’s existing tree structure or block ordering. +- Changing JSON/EDN output structure beyond adding `:db/ident` when present. + +## Current Behavior (Key Points) + +- Human output is rendered by `tree->text` in `src/main/logseq/cli/command/show.cljs`, with tree glyphs styled via `style/dim` but IDs unstyled. +- JSON/EDN output pulls specific selectors via `tree-block-selector` / `linked-ref-selector` and strips only `:block/uuid`. +- Linked References are always fetched and rendered via `fetch-linked-references` + `tree->text-with-linked-refs`. + +## Proposed Changes + +1) **Lighter ID column in human output** + - Style the padded ID column with the same dim styling used for tree glyphs. + - Apply the change in `tree->text` so both root and child rows render IDs dimmed. + +2) **Include `:db/ident` in JSON/EDN for blocks (when present)** + - Add `:db/ident` to block pull selectors: + - `tree-block-selector` (children) + - `linked-ref-selector` (linked references) + - root entity pulls in `fetch-tree` for `:id`, `:uuid`, and `:page` + - No post-processing is required because absent attributes will not appear in the pulled maps. + +3) **Optional Linked References (default true)** + - Add a boolean option `--linked-references` to `show-spec`, defaulting to true. + - Pass the option through `build-action` and into `build-tree-data`. + - When disabled: + - Skip `fetch-linked-references` entirely. + - Omit the `Linked References` section from human output. + - Omit `:linked-references` from JSON/EDN output. + +## Implementation Plan + +1) **Add show option wiring** + - Update `show-spec` in `src/main/logseq/cli/command/show.cljs` with a new boolean option (choose name + description, note default true). + - Extend `build-action` to include the option in `:action` (e.g., `:linked-references?`). + - Update `invalid-options?` if needed for any new validation. + +2) **Gate linked references fetching/rendering** + - Update `build-tree-data` to honor the new option: + - If enabled, keep current logic. + - If disabled, skip `fetch-linked-references`, avoid UUID label resolution based on linked refs, and avoid `tree->text-with-linked-refs`. + - Update `execute-show` to select `tree->text` vs `tree->text-with-linked-refs` based on the option. + - Ensure JSON/EDN output omits `:linked-references` when disabled. + +3) **Add `:db/ident` to selectors** + - Update `tree-block-selector`, `linked-ref-selector`, and root entity pull vectors in `fetch-tree` to include `:db/ident`. + - Ensure this does not add nil values (pull should omit missing attributes). + +4) **Dim ID column in human output** + - In `tree->text`, apply `style/dim` to the padded ID string (e.g., `pad-id` output) for both root and child rows. + - Keep existing branch/prefix dim styling unchanged. + +5) **Help output updates** + - Ensure `logseq show --help` lists the new linked references option (covered automatically by `show-spec`). + +## Testing Plan + +- Update or add unit tests in: + - `src/test/logseq/cli/format_test.cljs` to verify: + - ANSI dim styling is applied to the ID column in human output when color is enabled. + - No regressions in strip-ansi output. + - `src/test/logseq/cli/commands_test.cljs` to verify: + - `logseq show --help` includes the new linked references option. +- Add show-specific tests for JSON/EDN output: + - Ensure `:db/ident` appears when present (use a stubbed tree map or test fixtures if available). + - Ensure `:linked-references` is omitted when the option disables them. + +## Edge Cases + +- Blocks with no `:db/ident` should not gain a nil or empty key in output. +- When linked refs are disabled, UUID label resolution should not depend on linked refs (ensure `collect-uuid-refs` handles empty refs). +- Multi-id output should still honor the linked references option per target and not render linked refs when disabled. + +## Files to Touch + +- `src/main/logseq/cli/command/show.cljs` (options, selectors, tree output, linked refs gating) +- `src/test/logseq/cli/format_test.cljs` (ID dim styling test) +- `src/test/logseq/cli/commands_test.cljs` (help output) +- Possibly `src/test/logseq/cli/command_show_test.cljs` or similar if a dedicated show test file exists + +## Open Questions + +- None. + +--- diff --git a/src/main/logseq/cli/command/show.cljs b/src/main/logseq/cli/command/show.cljs index 29f56ef0b3..5ba7dc9d78 100644 --- a/src/main/logseq/cli/command/show.cljs +++ b/src/main/logseq/cli/command/show.cljs @@ -14,6 +14,8 @@ {:id {:desc "Block db/id or EDN vector of ids"} :uuid {:desc "Block UUID"} :page {:desc "Page name"} + :linked-references {:desc "Include linked references (default true)" + :coerce :boolean} :level {:desc "Limit tree depth (default 10)" :coerce :long}}) @@ -38,6 +40,7 @@ (def ^:private tree-block-selector [:db/id + :db/ident :block/uuid :block/title :block/content @@ -48,6 +51,7 @@ (def ^:private linked-ref-selector [:db/id + :db/ident :block/uuid :block/title :block/content @@ -342,7 +346,7 @@ (cond (some? id) (p/let [entity (transport/invoke config :thread-api/pull false - [repo [:db/id :block/name :block/uuid :block/title + [repo [:db/id :db/ident :block/name :block/uuid :block/title {:logseq.property/status [:db/ident :block/name :block/title]} {:block/page [:db/id :block/title]} {:block/tags [:db/id :block/name :block/title :block/uuid]}] id])] @@ -360,7 +364,7 @@ (if-not (common-util/uuid-string? uuid-str) (p/rejected (ex-info "block must be a uuid" {:code :invalid-block})) (p/let [entity (transport/invoke config :thread-api/pull false - [repo [:db/id :block/name :block/uuid :block/title + [repo [:db/id :db/ident :block/name :block/uuid :block/title {:logseq.property/status [:db/ident :block/name :block/title]} {:block/page [:db/id :block/title]} {:block/tags [:db/id :block/name :block/title :block/uuid]}] @@ -368,7 +372,7 @@ entity (if (:db/id entity) entity (transport/invoke config :thread-api/pull false - [repo [:db/id :block/name :block/uuid :block/title + [repo [:db/id :db/ident :block/name :block/uuid :block/title {:logseq.property/status [:db/ident :block/name :block/title]} {:block/page [:db/id :block/title]} {:block/tags [:db/id :block/name :block/title :block/uuid]}] @@ -385,7 +389,7 @@ (seq page) (p/let [page-entity (transport/invoke config :thread-api/pull false - [repo [:db/id :block/uuid :block/title + [repo [:db/id :db/ident :block/uuid :block/title {:logseq.property/status [:db/ident :block/name :block/title]} {:block/tags [:db/id :block/name :block/title :block/uuid]}] [:block/name page]])] @@ -414,7 +418,7 @@ (let [id-str (str (node-id node)) padding (max 0 (- id-width (count id-str)))] (str id-str (apply str (repeat padding " "))))) - id-padding (apply str (repeat (inc id-width) " ")) + id-padding (style/dim (apply str (repeat (inc id-width) " "))) split-lines (fn [value] (string/split (or value "") #"\n")) style-glyph (fn [value] @@ -430,7 +434,7 @@ rows (split-lines (label child)) first-row (first rows) rest-rows (rest rows) - line (str (pad-id child) " " + line (str (style/dim (pad-id child)) " " (style-glyph prefix) (style-glyph branch) first-row)] @@ -441,7 +445,7 @@ (let [rows (split-lines (label root)) first-row (first rows) rest-rows (rest rows)] - (swap! lines conj (str (pad-id root) " " first-row)) + (swap! lines conj (str (style/dim (pad-id root)) " " first-row)) (doseq [row rest-rows] (swap! lines conj (str id-padding row)))) (walk root "") @@ -483,6 +487,9 @@ :id (when (and (seq ids) (not multi-id?)) (first ids)) :ids ids :multi-id? multi-id? + :linked-references? (if (contains? options :linked-references) + (:linked-references options) + true) :uuid (:uuid options) :page (:page options) :level (:level options)}}))))) @@ -491,14 +498,16 @@ [config action] (p/let [tree-data (fetch-tree config action) root-id (get-in tree-data [:root :db/id]) - linked-refs (if root-id - (fetch-linked-references config (:repo action) root-id) - {:count 0 :blocks []}) - uuid-refs (collect-uuid-refs tree-data linked-refs) + linked-enabled? (not= false (:linked-references? action)) + linked-refs (when (and linked-enabled? root-id) + (fetch-linked-references config (:repo action) root-id)) + linked-refs* (if linked-enabled? + (or linked-refs {:count 0 :blocks []}) + {:count 0 :blocks []}) + uuid-refs (collect-uuid-refs tree-data linked-refs*) uuid->label (fetch-uuid-labels config (:repo action) uuid-refs) - tree-data (assoc tree-data - :linked-references linked-refs - :uuid->label uuid->label) + tree-data (cond-> (assoc tree-data :uuid->label uuid->label) + linked-enabled? (assoc :linked-references linked-refs*)) tree-data (resolve-uuid-refs-in-tree-data tree-data uuid->label)] tree-data)) @@ -576,6 +585,9 @@ results)) sanitize-tree (fn [tree] (strip-block-uuid tree)) + render-tree (if (false? (:linked-references? action)) + tree->text + tree->text-with-linked-refs) payload (case format :edn {:status :ok @@ -599,7 +611,7 @@ :data {:message (string/join multi-id-delimiter (map (fn [{:keys [ok? tree id error]}] (if ok? - (tree->text-with-linked-refs tree) + (render-tree tree) (multi-id-error-message id error))) results))}})] payload) @@ -618,4 +630,6 @@ :output-format :json}) {:status :ok - :data {:message (tree->text-with-linked-refs tree-data)}})))))) + :data {:message (if (false? (:linked-references? action)) + (tree->text tree-data) + (tree->text-with-linked-refs tree-data))}})))))) diff --git a/src/test/logseq/cli/commands_test.cljs b/src/test/logseq/cli/commands_test.cljs index a537401968..1588219715 100644 --- a/src/test/logseq/cli/commands_test.cljs +++ b/src/test/logseq/cli/commands_test.cljs @@ -301,6 +301,22 @@ "4 └── Child B") (strip-ansi output)))))) +(deftest test-tree->text-dims-id-column + (testing "show tree text dims the id column" + (let [tree->text #'show-command/tree->text + tree-data {:root {:db/id 1 + :block/title "Root" + :block/children [{:db/id 2 + :block/title "Child"}]}} + output (binding [style/*color-enabled?* true] + (tree->text tree-data))] + (is (contains-ansi? output)) + (is (string/includes? output (style/dim "1"))) + (is (string/includes? output (style/dim "2"))) + (is (= (str "1 Root\n" + "2 └── Child") + (strip-ansi output)))))) + (deftest test-tree->text-aligns-mixed-id-widths (testing "show tree text aligns glyph column with mixed-width ids" (let [tree->text #'show-command/tree->text @@ -488,6 +504,129 @@ (is (= 1 (get-in stripped [:root :db/id]))) (is (= 2 (get-in stripped [:root :block/children 0 :db/id])))))) +(deftest test-fetch-tree-includes-db-ident + (async done + (let [fetch-tree #'show-command/fetch-tree + selectors (atom []) + orig-invoke transport/invoke] + (set! transport/invoke (fn [_ method _ args] + (when (= method :thread-api/pull) + (swap! selectors conj (second args))) + (case method + :thread-api/pull (p/resolved {:db/id 1 + :block/page {:db/id 2}}) + :thread-api/q (p/resolved []) + (p/resolved nil)))) + (-> (p/let [_ (fetch-tree {} {:repo "demo" :id 1})] + (is (some #(some #{:db/ident} %) @selectors))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (set! transport/invoke orig-invoke) + (done))))))) + +(deftest test-fetch-blocks-for-page-includes-db-ident + (async done + (let [fetch-blocks-for-page #'show-command/fetch-blocks-for-page + selectors (atom []) + orig-invoke transport/invoke] + (set! transport/invoke (fn [_ method _ args] + (when (= method :thread-api/q) + (let [[_ [query _]] args + pull-form (second query) + selector (nth pull-form 2)] + (swap! selectors conj selector))) + (p/resolved []))) + (-> (p/let [_ (fetch-blocks-for-page {} "demo" 1)] + (is (some #(some #{:db/ident} %) @selectors))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (set! transport/invoke orig-invoke) + (done))))))) + +(deftest test-fetch-linked-references-includes-db-ident + (async done + (let [fetch-linked-references #'show-command/fetch-linked-references + selectors (atom []) + orig-invoke transport/invoke] + (set! transport/invoke (fn [_ method _ args] + (case method + :thread-api/get-block-refs (p/resolved [{:db/id 10}]) + :thread-api/pull (let [[_ selector _] args] + (swap! selectors conj selector) + (p/resolved {:db/id 10})) + (p/resolved nil)))) + (-> (p/let [_ (fetch-linked-references {} "demo" 1)] + (is (some #(and (some #{:db/ident} %) + (some (fn [entry] + (and (map? entry) + (contains? entry :block/page))) + %)) + @selectors))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (set! transport/invoke orig-invoke) + (done))))))) + +(deftest test-build-tree-data-linked-references-disabled + (async done + (let [build-tree-data #'show-command/build-tree-data + linked-called? (atom false) + orig-fetch-tree show-command/fetch-tree + orig-fetch-linked show-command/fetch-linked-references + orig-collect-uuid-refs show-command/collect-uuid-refs + orig-fetch-uuid-labels show-command/fetch-uuid-labels] + (set! show-command/fetch-tree (fn [_ _] + (p/resolved {:root {:db/id 1}}))) + (set! show-command/fetch-linked-references (fn [& _] + (reset! linked-called? true) + (p/resolved {:count 1 :blocks []}))) + (set! show-command/collect-uuid-refs (fn [_ _] [])) + (set! show-command/fetch-uuid-labels (fn [& _] (p/resolved {}))) + (-> (p/let [result (build-tree-data {} {:repo "demo" + :linked-references? false})] + (is (false? @linked-called?)) + (is (not (contains? result :linked-references)))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (set! show-command/fetch-tree orig-fetch-tree) + (set! show-command/fetch-linked-references orig-fetch-linked) + (set! show-command/collect-uuid-refs orig-collect-uuid-refs) + (set! show-command/fetch-uuid-labels orig-fetch-uuid-labels) + (done))))))) + +(deftest test-build-tree-data-linked-references-enabled + (async done + (let [build-tree-data #'show-command/build-tree-data + linked-called? (atom false) + linked {:count 1 :blocks []} + orig-fetch-tree show-command/fetch-tree + orig-fetch-linked show-command/fetch-linked-references + orig-collect-uuid-refs show-command/collect-uuid-refs + orig-fetch-uuid-labels show-command/fetch-uuid-labels] + (set! show-command/fetch-tree (fn [_ _] + (p/resolved {:root {:db/id 1}}))) + (set! show-command/fetch-linked-references (fn [& _] + (reset! linked-called? true) + (p/resolved linked))) + (set! show-command/collect-uuid-refs (fn [_ _] [])) + (set! show-command/fetch-uuid-labels (fn [& _] (p/resolved {}))) + (-> (p/let [result (build-tree-data {} {:repo "demo" + :linked-references? true})] + (is (true? @linked-called?)) + (is (= linked (:linked-references result)))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (set! show-command/fetch-tree orig-fetch-tree) + (set! show-command/fetch-linked-references orig-fetch-linked) + (set! show-command/collect-uuid-refs orig-collect-uuid-refs) + (set! show-command/fetch-uuid-labels orig-fetch-uuid-labels) + (done))))))) + (deftest test-tree->text-uuid-ref-recursion-limit (testing "show tree text limits uuid ref replacement depth" (let [tree->text #'show-command/tree->text @@ -758,7 +897,12 @@ (testing "show rejects format option" (let [result (commands/parse-args ["show" "--format" "json" "--page" "Home"])] (is (false? (:ok? result))) - (is (= :invalid-options (get-in result [:error :code])))))) + (is (= :invalid-options (get-in result [:error :code]))))) + + (testing "show help lists linked references option" + (let [summary (:summary (binding [style/*color-enabled?* true] + (commands/parse-args ["show" "--help"])))] + (is (string/includes? (strip-ansi summary) "--linked-references"))))) (deftest test-verb-subcommand-parse-query (testing "query shows group help"