impl 010-logseq-cli-show-linked-references.md (2)

This commit is contained in:
rcmerci
2026-01-20 22:35:46 +08:00
parent 6b23c5926b
commit f6e529e7ef
4 changed files with 118 additions and 40 deletions

View File

@@ -2,7 +2,7 @@
Goal: Add task status prefixes and block tag rendering to show output, replace inline `[[<uuid>]]` references with `[[<referenced block content>]]`, and include linked references for the shown block or page.
Architecture: The CLI show command will fetch marker data with the tree and will build a display label that prefixes the marker before the block content, replaces inline `[[<uuid>]]` with `[[<referenced block content>]]`, and then appends inline block tags (e.g. `#RTC #Task`) to the content display.
Architecture: The CLI show command will fetch status data via `:logseq.property/status` with the tree and will build a display label that prefixes the status before the block content, replaces inline `[[<uuid>]]` with `[[<referenced block content>]]`, and then appends inline block tags (e.g. `#RTC #Task`) to the content display.
Architecture: The CLI show command will also call db-worker-node thread-api/get-block-refs to fetch linked references and append them to text output in a tree form (db/id in the first column), while returning structured data in JSON and EDN output.
Architecture: Data flow will remain CLI -> db-worker-node -> db-core with no new worker endpoints, reusing existing thread-api functions.
@@ -13,9 +13,9 @@ Related: Builds on docs/agent-guide/009-cli-add-pos-show-tree-align.md.
## Testing Plan
I will follow @test-driven-development and write failing tests before any production changes.
I will add a unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that asserts tree->text prefixes :block/marker before the block title for TODO and CANCELED blocks.
I will add a unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that asserts tree->text prefixes :logseq.property/status before the block title for TODO and CANCELED blocks.
I will add a unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that asserts tree->text appends :block/tags in `#Tag` format to the rendered block content.
I will add a unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that asserts tree->text keeps multiline alignment when the marker prefix is present.
I will add a unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that asserts tree->text keeps multiline alignment when the status prefix is present.
I will add an integration test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs that creates a page and a referencing block, runs show with --format json, and asserts that linked references are present and include the referencing block uuid and page title.
I will run the new unit tests with bb dev:test -v logseq.cli.commands-test and the new integration test namespace with bb dev:test -v logseq.cli.integration-test to confirm failures, then again to confirm passing.
I will run bb dev:lint-and-test after implementation to ensure no regressions.
@@ -23,7 +23,7 @@ NOTE: I will write *all* tests before I add any implementation behavior.
## Problem statement
The current show command prints only the block title or page name without the task marker, which hides task status context in CLI usage.
The current show command prints only the block title or page name without the task status, which hides task status context in CLI usage.
The show command also does not include linked references for the shown block or page, forcing users to query references separately.
We need to enhance the show output to include task status prefixes and linked references while keeping existing formats and db-worker-node integration stable.
@@ -32,27 +32,27 @@ We need to enhance the show output to include task status prefixes and linked re
1. Read /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs to identify the tree-block selector, label construction, and output formatting paths.
2. Read /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs to confirm the behavior and return shape of :thread-api/get-block-refs.
3. Read the existing show tree tests in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs to align new expectations with current formatting rules.
4. Add a failing unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that builds a tree with :block/marker values and asserts the marker prefix appears before the block title in tree->text output.
5. Add a failing unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that covers multiline block titles with markers and asserts continuation lines still align under the glyph column.
4. Add a failing unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that builds a tree with :logseq.property/status values and asserts the status prefix appears before the block title in tree->text output.
5. Add a failing unit test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs that covers multiline block titles with statuses and asserts continuation lines still align under the glyph column.
6. Add a failing integration test in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs that creates a page, adds a block referencing that page, runs show for the page in JSON, and asserts the response contains linked references with block uuid and page title.
7. Run the two new unit tests and the integration test to confirm failures for the expected reasons.
8. Update the tree-block selector in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs to pull :block/marker alongside :block/title and :block/name.
9. Add a small helper in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs that builds the display label by prefixing :block/marker followed by a space when a marker is present, while falling back to :block/name or :block/uuid when :block/title is missing, and appending `#Tag` strings for :block/tags.
10. Update tree->text in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs to use the new label helper for both root and child nodes so that marker prefixes appear in all output lines.
8. Update the tree-block selector in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs to pull :logseq.property/status alongside :block/title and :block/name.
9. Add a small helper in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs that builds the display label by prefixing :logseq.property/status followed by a space when a status is present, while falling back to :block/name or :block/uuid when :block/title is missing, and appending `#Tag` strings for :block/tags.
10. Update tree->text in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs to use the new label helper for both root and child nodes so that status prefixes appear in all output lines.
11. Add a linked references fetch step in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/show.cljs that calls transport/invoke with :thread-api/get-block-refs using the root db/id.
12. Normalize linked references by pulling a minimal selector for each ref block, including :db/id, :block/uuid, :block/title, :block/marker, and {:block/page [:db/id :block/name :block/title :block/uuid]}, so CLI output is predictable and lightweight.
12. Normalize linked references by pulling a minimal selector for each ref block, including :db/id, :block/uuid, :block/title, :logseq.property/status, and {:block/page [:db/id :block/name :block/title :block/uuid]}, so CLI output is predictable and lightweight.
13. Extend the show tree data structure to include :linked-references with a list of normalized blocks and a :count, and ensure this structure is returned for JSON and EDN output paths.
14. For text output, append a Linked References section after the tree that renders each referencing block in tree form with db/id in the first column (aligned to the glyph column), include the marker-prefixed label, and show a count line when references exist.
15. Update the unit test expectations in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs to match the marker-prefixed text output.
14. For text output, append a Linked References section after the tree that renders each referencing block in tree form with db/id in the first column (aligned to the glyph column), include the status-prefixed label, and show a count line when references exist.
15. Update the unit test expectations in /Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs to match the status-prefixed text output.
16. Update the new integration test assertions to match the final JSON structure for linked references.
17. Run the targeted tests again, then bb dev:lint-and-test, to verify all changes pass.
## Edge cases
Blocks without :block/marker should render exactly as before with no extra spacing.
Blocks without :logseq.property/status should render exactly as before with no extra spacing.
Blocks with :block/tags should render tag names as `#Tag` appended after the content.
Blocks containing inline `[[<uuid>]]` references should render those tokens replaced with `[[<referenced block content>]]`.
Blocks with :block/title nil should still render using :block/name or :block/uuid with the marker prefix applied only when a title exists.
Blocks with :block/title nil should still render using :block/name or :block/uuid with the status prefix applied only when a title exists.
Linked references can be empty, in which case the Linked References section should be omitted from text output and :linked-references should contain a zero count in JSON and EDN.
Linked reference blocks that are missing a page or title should still render using their uuid fallback.
Linked references should render using the same tree layout rules as the main show tree, including db/id in the first column.
@@ -60,13 +60,13 @@ Show by block id and show by page name should both resolve linked references usi
## Testing Details
The unit tests exercise tree->text output formatting to ensure marker prefixes appear on the first line and multiline alignment is preserved, which validates CLI-visible behavior.
The unit tests exercise tree->text output formatting to ensure status prefixes appear on the first line and multiline alignment is preserved, which validates CLI-visible behavior.
The integration test uses db-worker-node to create actual referencing blocks and verifies that show output includes linked references in the JSON response without inspecting internal worker details.
## Implementation Details
- Pull :block/marker in the tree selector so task status is available for label rendering.
- Build a label helper that prefixes markers without changing existing fallback logic for titles and names, replaces inline `[[<uuid>]]` tokens with `[[<referenced block content>]]`, and appends block tags (e.g. `#RTC #Task`) after the content.
- Pull :logseq.property/status in the tree selector so task status is available for label rendering.
- Build a label helper that prefixes status values without changing existing fallback logic for titles and names, replaces inline `[[<uuid>]]` tokens with `[[<referenced block content>]]`, and appends block tags (e.g. `#RTC #Task`) after the content.
- Append a Linked References section in text output with a header, count, and tree-formatted block labels (db/id in the first column).
- Use :thread-api/get-block-refs for reference discovery and re-pull a minimal selector for stable CLI output.
- Return linked references in JSON and EDN outputs as {:linked-references {:count n :blocks [...]}}.
@@ -74,8 +74,8 @@ The integration test uses db-worker-node to create actual referencing blocks and
## Question
Should the marker prefix use the stored uppercase value (for example CANCELED) or should it be title-cased to match the example with Canceled.
Answer: Use the stored uppercase marker value (for example CANCELED).
Should the status prefix use the stored uppercase value (for example CANCELED) or should it be title-cased to match the example with Canceled.
Answer: Use the stored uppercase status value (for example CANCELED).
Should linked references be grouped by page in text output, or listed as a flat list with page labels.
Answer: Group linked references by page in text output.

View File

@@ -40,7 +40,7 @@
[:db/id
:block/uuid
:block/title
:block/marker
{:logseq.property/status [:db/ident :block/name :block/title]}
:block/order
{:block/parent [:db/id]}
{:block/tags [:db/id :block/name :block/title :block/uuid]}])
@@ -49,7 +49,7 @@
[:db/id
:block/uuid
:block/title
:block/marker
{:logseq.property/status [:db/ident :block/name :block/title]}
{:block/tags [:db/id :block/name :block/title :block/uuid]}
{:block/page [:db/id :block/name :block/title :block/uuid]}
{:block/parent [:db/id
@@ -86,13 +86,32 @@
(when (seq labels)
(string/join " " (map #(str "#" %) labels)))))
(defn- status-from-ident
[ident]
(let [name* (name ident)
parts (string/split name* #"\.")
status (or (last parts) name*)]
(string/upper-case status)))
(defn- status-label
[node]
(let [status (:logseq.property/status node)]
(cond
(string? status) (when (seq status) status)
(keyword? status) (status-from-ident status)
(map? status) (or (:block/title status)
(:block/name status)
(when-let [ident (:db/ident status)]
(status-from-ident ident)))
:else nil)))
(defn- block-label
[node]
(let [title (:block/title node)
marker (:block/marker node)
status (status-label node)
uuid->label (:uuid->label node)
base (cond
(and title (seq marker)) (str marker " " title)
(and title (seq status)) (str status " " title)
title title
(:block/name node) (:block/name node)
(:block/uuid node) (some-> (:block/uuid node) str))
@@ -121,15 +140,22 @@
ref-ids))
[])]
(let [blocks (vec (remove nil? pulled))
page-lookup-key (fn [value]
(cond
(map? value) (or (:db/id value)
(when-let [uuid (:block/uuid value)]
[:block/uuid uuid]))
(number? value) value
(uuid? value) [:block/uuid value]
(and (string? value) (common-util/uuid-string? value)) [:block/uuid (uuid value)]
:else nil))
page-id-from (fn [block]
(let [page (:block/page block)
parent (:block/parent block)
parent-page (:block/page parent)]
(or (when (map? page) (:db/id page))
(when (number? page) page)
(when (map? parent-page) (:db/id parent-page))
(when (number? parent-page) parent-page)
(when (map? parent) (:db/id parent)))))
(or (page-lookup-key page)
(page-lookup-key parent-page)
(page-lookup-key parent))))
page-ids (->> blocks
(keep page-id-from)
distinct
@@ -257,7 +283,8 @@
(cond
(some? id)
(p/let [entity (transport/invoke config :thread-api/pull false
[repo [:db/id :block/name :block/uuid :block/title :block/marker
[repo [:db/id :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])]
(if-let [page-id (get-in entity [:block/page :db/id])]
@@ -274,14 +301,16 @@
(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 :block/marker
[repo [:db/id :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]}]
[:block/uuid (uuid uuid-str)]])
entity (if (:db/id entity)
entity
(transport/invoke config :thread-api/pull false
[repo [:db/id :block/name :block/uuid :block/title :block/marker
[repo [:db/id :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]}]
[:block/uuid uuid-str]]))]
@@ -297,7 +326,8 @@
(seq page-name)
(p/let [page-entity (transport/invoke config :thread-api/pull false
[repo [:db/id :block/uuid :block/title :block/marker
[repo [:db/id :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-name]])]
(if-let [page-id (:db/id page-entity)]

View File

@@ -204,27 +204,30 @@
"175 └── cccc")
(tree->text tree-data))))))
(deftest test-tree->text-prefixes-marker
(testing "show tree text prefixes markers before block titles"
(deftest test-tree->text-prefixes-status
(testing "show tree text prefixes status before block titles"
(let [tree->text #'show-command/tree->text
tree-data {:root {:db/id 1
:block/title "Root"
:block/marker "TODO"
:logseq.property/status {:db/ident :logseq.property/status.todo
:block/title "TODO"}
:block/children [{:db/id 2
:block/title "Child"
:block/marker "CANCELED"}]}}]
:logseq.property/status {:db/ident :logseq.property/status.canceled
:block/title "CANCELED"}}]}}]
(is (= (str "1 TODO Root\n"
"2 └── CANCELED Child")
(tree->text tree-data))))))
(deftest test-tree->text-marker-multiline-alignment
(testing "show tree text keeps multiline alignment when marker prefix is present"
(deftest test-tree->text-status-multiline-alignment
(testing "show tree text keeps multiline alignment when status prefix is present"
(let [tree->text #'show-command/tree->text
tree-data {:root {:db/id 1
:block/title "Root"
:block/children [{:db/id 22
:block/title "line1\nline2"
:block/marker "TODO"}]}}]
:logseq.property/status {:db/ident :logseq.property/status.todo
:block/title "TODO"}}]}}]
(is (= (str "1 Root\n"
"22 └── TODO line1\n"
" line2")
@@ -238,7 +241,8 @@
:linked-references {:count 2
:blocks [{:db/id 10
:block/title "Ref A"
:block/marker "TODO"
:logseq.property/status {:db/ident :logseq.property/status.todo
:block/title "TODO"}
:block/page {:db/id 100
:block/title "Page A"}}
{:db/id 11

View File

@@ -125,6 +125,50 @@
(is false (str "unexpected error: " e))
(done)))))))
(deftest test-cli-show-linked-references-json
(async done
(let [data-dir (node-helper/create-tmp-dir "db-worker-linked-refs")]
(-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn")
_ (fs/writeFileSync cfg-path "{:output-format :json}")
_ (run-cli ["graph" "create" "--repo" "linked-refs-graph"] data-dir cfg-path)
_ (run-cli ["--repo" "linked-refs-graph" "add" "page" "--page" "TargetPage"] data-dir cfg-path)
_ (run-cli ["--repo" "linked-refs-graph" "add" "page" "--page" "SourcePage"] data-dir cfg-path)
target-show-before (run-cli ["--repo" "linked-refs-graph" "show" "--page-name" "TargetPage" "--format" "json"] data-dir cfg-path)
target-before-payload (parse-json-output target-show-before)
target-uuid (or (get-in target-before-payload [:data :root :block/uuid])
(get-in target-before-payload [:data :root :uuid]))
ref-content (str "See [[" target-uuid "]]")
_ (run-cli ["--repo" "linked-refs-graph" "add" "block" "--target-page-name" "SourcePage" "--content" ref-content] data-dir cfg-path)
source-show (run-cli ["--repo" "linked-refs-graph" "show" "--page-name" "SourcePage" "--format" "json"] data-dir cfg-path)
source-payload (parse-json-output source-show)
ref-node (find-block-by-title (get-in source-payload [:data :root]) ref-content)
ref-uuid (or (:block/uuid ref-node) (:uuid ref-node))
target-show (run-cli ["--repo" "linked-refs-graph" "show" "--page-name" "TargetPage" "--format" "json"] data-dir cfg-path)
target-payload (parse-json-output target-show)
linked-refs (get-in target-payload [:data :linked-references])
linked-blocks (:blocks linked-refs)
linked-uuids (set (map (fn [block]
(or (:block/uuid block) (:uuid block)))
linked-blocks))
linked-page-titles (set (keep (fn [block]
(or (get-in block [:block/page :block/title])
(get-in block [:block/page :block/name])
(get-in block [:page :title])
(get-in block [:page :name])))
linked-blocks))
stop-result (run-cli ["server" "stop" "--repo" "linked-refs-graph"] data-dir cfg-path)
stop-payload (parse-json-output stop-result)]
(is (some? target-uuid))
(is (= "ok" (:status target-payload)))
(is (some? ref-uuid))
(is (contains? linked-uuids ref-uuid))
(is (contains? linked-page-titles "SourcePage"))
(is (= "ok" (:status stop-payload)))
(done))
(p/catch (fn [e]
(is false (str "unexpected error: " e))
(done)))))))
(deftest test-cli-move-block
(async done
(let [data-dir (node-helper/create-tmp-dir "db-worker-move")]