Skip to content

Implement directory tree UI #156

Description

@YusukeHirao

Priority (from #103 — absolute, consult whenever unsure)

  1. Viewer compatibility — never break packages/@nitpicker/viewer/web/'s existing frontend/API contract. Verify actual frontend consumers (web/api/, web/routes/) before changing any response shape.
  2. Read performance — all read-model builds happen at crawl completion or via the explicit nitpicker viewer-build command, never inline during a read-only open (see Remove on-open opportunistic read model build (corrects #112) #177, which corrects the Define persistent viewer read model build timing #112 on-open build that violated this).
  3. Archive DB storage reduction.

These are non-negotiable and ranked in this order. No half-measures: pursue maximum read performance without compromise. Research prior art, benchmark with real measurements (including real large archives, not just synthetic data), and do not let implementation effort or time be a reason to settle for a suboptimal design.


AS IS

The backend directory tree read model and API landed in #107 (GET /api/directory-tree, /api/directory-tree/children, /api/directory-tree/pages). There is no frontend surface for it yet — the viewer has no way to browse the crawled URL space as a directory tree.

TO BE

Add a directory tree view to the viewer frontend (packages/@nitpicker/viewer/web/), following the usage pattern the backend API was designed around (docs/viewer-sql-query-plan.md's directory-tree section):

  1. On load, call GET /api/directory-tree and render the flat { roots: [{ rootKey, nodes }] } response as a nested tree per root, using each node's parentNodeId to build parent/child relationships client-side (the backend never nests server-side).
  2. Each node shows name, and a badge using childCount (directChildDirCount + directPageCount). Only render an expand affordance when hasChildren is true (child directories exist — direct pages are shown separately, not as additional tree rows).
  3. Clicking an unexpanded node with hasChildren: true and depth === 3 (the initial load's cutoff) calls GET /api/directory-tree/children?nodeId= to fetch its direct child directories dynamically.
  4. Selecting a directory node shows its direct pages (not descendants) via GET /api/directory-tree/pages?nodeId=&cursor=&limit=, cursor-paginated (nextCursor only, no prevCursor — forward-only, same contract as other infinite-scroll views in the viewer).
  5. Add a nav entry / route consistent with the other list views (pages / resources / images / etc.), and i18n labels (en/ja) per web/i18n/translations.ts.

Must

  • Multi-root archives: render one tree per rootKey (most archives will have exactly one).
  • Respect descendantPageCount / internalDescendantPageCount / externalDescendantPageCount if the UI wants to show subtree size without fetching descendants.
  • No client-side recursive fetch of the whole tree — only expand on demand via the children endpoint.

Don't

  • Don't fetch or render descendant pages from the tree expansion endpoints — direct pages only, via the dedicated pages endpoint.
  • Don't treat hasChildren as "has anything" — it reflects child directories only (see Implement directory tree read model and API #107's design note in ARCHITECTURE.md).

Acceptance

  • Directory tree view renders the initial depth ≤ 3 tree on load.
  • Clicking an unexpanded node loads its children dynamically without a full page reload.
  • Selecting a node shows its direct pages with working pagination.
  • E2E smoke test (viewer-e2e) covers initial render + expand + page list.

Backend reference: #107.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions