Skip to content

feat(children): add --space to list a whole space - #124

Merged
willkg merged 7 commits into
mainfrom
feat/children-space
Aug 31, 2026
Merged

feat(children): add --space to list a whole space#124
willkg merged 7 commits into
mainfrom
feat/children-space

Conversation

@willkg

@willkg willkg commented Aug 31, 2026

Copy link
Copy Markdown
Member

Closes #98.

children grows a --space KEY flag that lists a whole space instead of a page, so "show me everything in this space" no longer requires knowing CQL. PAGE becomes optional; exactly one of PAGE / --space is required, checked before credentials are resolved.

What depth 1 means, and why it isn't the homepage's children

Depth 1 under --space is the space's root pages, so the homepage is a row and its children are depth 2. Seeding the walk from the space's homepageId was the obvious design and it is wrong: a space can have more than one root page (AIM on our instance has two, and markfluence create with parent: null produces exactly that shape), so starting at the homepage would drop a root page and its entire subtree — silently, the same way v2 /children drops folders.

There is no root-level folder to miss: POST /wiki/api/v2/folders with a spaceId and no parentId creates the folder under the homepage, reporting parentType: "page". That also resolves an open "Unverified" bullet in docs/confluence/folders.md.

All of the probing behind this — the v1 depth=root route and the fact that its bare rows are already ChildNode-shaped, archived roots being excluded, the unknown-key 404, the four routes that do not list root folders, and why the tempting flat /api/v2/spaces/{id}/pages sweep is unusable — is written up in the new docs/confluence/spaces.md.

Other decisions worth a look

  • An unknown key is resolved, not inferred from a 404. ResolveSpaceID runs first and an unknown key is a usage error (exit 2, VALIDATION) worded exactly as find/search word it. The v1 route's own 404 names the key, but a rejected credential is also a 404, and that is the misreport RejectedCredential exists to prevent. The cost is one request whose result goes unused.
  • parent_id widens to stringOrNull. A root page hangs off no node, and the space is not one, so null rather than "" or the space id.
  • A --depth reminder in human output. A space's top level is usually one row, which reads like the whole answer; the line prints only when --depth was left at its default, and --json never sees it.
  • --space is a key, not a URL, matching find and search.
  • pagetree.Walk and the new WalkSpace now share one walker, so the depth rule and the visited guard exist in one copy.

Verification

make check is green. New tests cover the route (path, depth=root, a ~personal key escaped), WalkSpace (two roots, folder descent, ParentID "", the depth bound costing no requests below it), and the command (both usage errors, unknown key, the hint's presence and absence, parent_id: null in --json).

Live, against mozilla-hub:

$ markfluence children --space AIM
TYPE  ID       TITLE
page  2097154  Africa Innovation Mradi Home
page  2097185  What is Africa Mradi?

    Showing the space's top level. Use --depth 2, or --depth all for the whole tree.

$ markfluence children --space '~60c36d0718e9f60071326951' --depth all | head -6
TYPE    ID          TITLE
page    76646878    Things
folder  2876047392    Articles
page    1621327887      Maintaining Confluence content with Markdown
page    2842755298      Confluence, Claude, and Images
page    2725249040      Writing self-reflection with Claude (May 2026)

$ markfluence children --space '~60c36d0718e9f60071326951' --depth all --json | jq '.summary'
{ "failed": 0, "succeeded": 36, "total": 36 }

$ markfluence children --space NOSUCHSPACEXYZ ; echo "exit $?"
  ✗ space "NOSUCHSPACEXYZ" not found
exit 2

$ markfluence children 2848423944 --space AIM ; echo "exit $?"
  ✗ PAGE and --space cannot be combined: --space lists a whole space
exit 2

$ markfluence children ; echo "exit $?"
  ✗ no page given: pass a PAGE, or --space KEY to list a whole space
exit 2

The 36 rows are 33 current pages plus 3 folders; the space's 34th page is archived, which depth=root and the v1 child routes both exclude.

Not done here

The issue's alternative suggestion — documenting the CQL space-listing pattern in search --help — is deliberately skipped: pointing users at CQL for the task children --space now covers works against the point. search --help is unchanged.

@willkg

willkg commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Follow-up from a code-review pass, three commits on top:

A failing --space walk no longer reports a page_id. It was emitting results[0] as a singleOpFailure whose page_id was the space key — an id that resolves to nothing, so a consumer doing .results[0].page_id | markfluence info would get a spurious not-found. It now writes an errorObject on stderr, which is what find and search already do in the same situation ("there is no page id to name"). CLAUDE.md's claim that find shares that shape "with search and nothing else" is corrected in the same commit.

The --depth hint moved to stderr, behind a new ui.Hint. It was appended to stdout after the table, which the table's own no-trailing-whitespace/aligned-columns design exists to keep clean — children --space ENG | awk '{print $2}' was getting a blank line and a prose line in the data. ui.Info (stdout) vs ui.Hint (stderr) is now the difference between the two helpers.

Offset paging on the root-pages route is measured rather than assumed. listV1 pages by start/limit, and CLAUDE.md warns that picking the wrong one of the three schemes truncates silently — worse here, a route that ignored start would make listV1 re-read page one forever rather than truncate. Against AIM's two roots:

?depth=root&limit=1&start=0  ->  Africa Innovation Mradi Home
?depth=root&limit=1&start=1  ->  What is Africa Mradi?
?depth=root&limit=1&start=2  ->  no rows

start is honoured and a past-the-end page is empty, so the loop terminates. Recorded in docs/confluence/spaces.md and pinned by a test.

make check still green.

willkg added 7 commits August 31, 2026 10:42
Records what was measured for #98: the v1 depth=root route and the fact that
its rows are already ChildNode-shaped, that a space can have more than one root
page (AIM has two, so walking from homepageId would silently drop a subtree),
that an unknown key must be detected by resolving it rather than by reading a
404 body, and that a folder created with no parentId lands under the homepage
rather than at the root -- which resolves an Unverified bullet in folders.md.
client.ListSpaceRootPages reads the v1 depth=root collection, whose rows are
already ChildNode-shaped, and pagetree.WalkSpace seeds the existing traversal
with them. The seed is the space's root *pages*, not its homepage: a space can
have several roots, so starting from homepageId would drop one and its whole
subtree. A root page reports ParentID "", since the space it sits in is not a
node. Walk and WalkSpace now share one walker, so the depth rule and the
visited guard exist in one copy.
PAGE becomes optional and exactly one of PAGE / --space is required, checked
before credentials are resolved. Depth 1 under --space is the space's root
pages -- usually just the homepage, which is why human output adds a line
pointing at --depth when the caller left it alone.

An unknown key is reported as a typo (exit 2, VALIDATION) after resolving it
with the v2 spaces route, rather than by reading the v1 404's body: a rejected
credential answers 404 too.

childrenResult.parent_id widens to stringOrNull, since a page at a space root
hangs off no node and the space is not one.
README gains a --space subsection under children (what depth 1 means, the
null parent_id, the unknown-key error) and the space-key resolve row in the
scope table now names children. CLAUDE.md records the design in the children
and pagetree bullets, and corrects search's claim to being the only
Flags().Changed caller in cmd/.
Two things a review caught, both about what lands on stdout:

A --space walk that fails names no page, so reporting it as a results[0]
failure put the space key in page_id -- an id that resolves to nothing. It now
writes an error object on stderr, as find and search already do for the same
reason.

The --depth hint moves to stderr behind a new ui.Hint, so `children --space ENG
| awk` still gets nothing but table rows.

Also pins the root-page route's offset paging, measured against AIM: start is
honoured and a past-the-end page is empty, so listV1 terminates. A route that
ignored start would have re-read page one forever.
@willkg
willkg force-pushed the feat/children-space branch from 5c92840 to 61e2974 Compare August 31, 2026 14:42
@willkg
willkg merged commit f730e42 into main Aug 31, 2026
1 check passed
@willkg
willkg deleted the feat/children-space branch August 31, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a friendlier way to list/browse all pages in a space

1 participant