feat(picker): one TreeList for both pickers, tree or list by module and param, the page field's value row #357

Merged
dominik.polakovics merged 3 commits from afk/348 into main 2026-10-31 16:42:04 +01:00

Closes #348

Stacked on #356 (issue #347), which is itself stacked on #355. This branch merges origin/afk/347, because the picker reads tree: hidden from GET /api/modules. Merge #356 first. After that, this PR's own change is d249146.

What changes

One tree component

  • TreeBrowser.tsx is deleted. findChain, matchesQuery (the filter rule) and nodeAddress now live in TreeList.tsx.
  • Each TreeList row now shows:
    • its address: mono, muted, right-aligned; it moves under the title in a narrow container;
    • house / folder / file / eye-off icons;
    • a "Disabled" warn badge on a struck title, or a neutral "Hidden" badge.
  • Rows that cannot be chosen are muted: muted text at 500 weight, aria-disabled, the reason as a tooltip, and a caret that still opens the row. They use no opacity.
  • Untranslated rows reuse .item__label--untranslated and .item__lang.
  • The link browser's Page tab gets the address column and the muted rows.
  • pagemanage.disabledInLanguage is now the one owner of "is this node disabled here". TreePane, LinkPicker and PagePicker all call it.

The page picker (PagePicker.tsx)

  • Dialog: the lg tier. The head reads "Choose a page", with a sub line naming the page type by its label and a close button. Dialog gains optional description and closeButton props.
  • Toolbar:
    • a search field;
    • a .toggle for Tree / List;
    • a .choice checkbox "Only pages", which folds the tree to eligible pages and their ancestors. It is saved per viewer in localStorage, with try/catch.
  • Which view opens is decided by one owner, web/src/pickerview.ts:
    1. the param's picker setting pins a view and hides the toggle;
    2. otherwise, a page type owned by a tree: hidden module (read from GET /api/modules) opens the list with no Tree option;
    3. otherwise, the tree opens with the toggle.
  • List view:
    • Each row has a radio, the address and a git badge (GitBadge).
    • Disabled pages are struck, wear the warn badge, and their radio is disabled.
    • Where the rows come from: if a module owns the type, they are its entries from GET /api/module/entries, built through modulequery for each moduleContainers container. Otherwise they are the tree, flattened.
  • Choosing: eligibility is still the one eligible check over effectiveNodeType, and the reason text comes from that result. Disabled pages cannot be selected.
  • Current value: the stored page is pre-selected and wears a "Current" chip.
  • Foot: "Chosen:
Closes #348 **Stacked on #356 (issue #347), which is itself stacked on #355.** This branch merges `origin/afk/347`, because the picker reads `tree: hidden` from GET /api/modules. Merge #356 first. After that, this PR's own change is `d249146`. ## What changes **One tree component** - `TreeBrowser.tsx` is deleted. `findChain`, `matchesQuery` (the filter rule) and `nodeAddress` now live in `TreeList.tsx`. - Each TreeList row now shows: - its address: mono, muted, right-aligned; it moves under the title in a narrow container; - house / folder / file / eye-off icons; - a "Disabled" warn badge on a struck title, or a neutral "Hidden" badge. - Rows that cannot be chosen are muted: muted text at 500 weight, `aria-disabled`, the reason as a tooltip, and a caret that still opens the row. They use no opacity. - Untranslated rows reuse `.item__label--untranslated` and `.item__lang`. - The link browser's Page tab gets the address column and the muted rows. - `pagemanage.disabledInLanguage` is now the one owner of "is this node disabled here". TreePane, LinkPicker and PagePicker all call it. **The page picker** (`PagePicker.tsx`) - **Dialog:** the lg tier. The head reads "Choose a page", with a sub line naming the page type by its label and a close button. `Dialog` gains optional `description` and `closeButton` props. - **Toolbar:** - a search field; - a `.toggle` for Tree / List; - a `.choice` checkbox "Only <type> pages", which folds the tree to eligible pages and their ancestors. It is saved per viewer in `localStorage`, with try/catch. - **Which view opens** is decided by one owner, `web/src/pickerview.ts`: 1. the param's `picker` setting pins a view and hides the toggle; 2. otherwise, a page type owned by a `tree: hidden` module (read from GET /api/modules) opens the list with no Tree option; 3. otherwise, the tree opens with the toggle. - **List view:** - Each row has a radio, the address and a git badge (`GitBadge`). - Disabled pages are struck, wear the warn badge, and their radio is disabled. - Where the rows come from: if a module owns the type, they are its entries from GET /api/module/entries, built through `modulequery` for each `moduleContainers` container. Otherwise they are the tree, flattened. - **Choosing:** eligibility is still the one `eligible` check over `effectiveNodeType`, and the reason text comes from that result. Disabled pages cannot be selected. - **Current value:** the stored page is pre-selected and wears a "Current" chip. - **Foot:** "Chosen: <title> <address>", then a ghost Cancel and a primary Choose. Choose stays disabled until an eligible page is selected. **Engine: `picker: tree | list`** - A `page` param (element `params:` or page-type `fields:`) may set it. On any other type, or with any other value, it is a load error. - It appears in the canonical key order after `pageType`, and in the field settings as `{pageType?, picker?}`. **The `page` field** (`PagePickerControl`) - **Empty:** a dashed row "No page chosen" with a "Choose a page…" button. - **Set:** a value row with an icon, the title, the address in mono and the fallback note, plus "Change…" and a ghost "Clear". - **Hint:** names the page type when the param sets one. - **Disabled page:** a Disabled badge and an `.error` line, "This page is disabled and will not be built. Choose another or enable it." - **Server side:** GET /api/link/resolve now adds `disabled: true` for a found page or element target. It reports the page's own state in the asked language, or the default-language page's state when the answer falls back. ## Documents, skills, owners - **Documents:** - `scaffold/site/AGENTS.md` "Field types": how `pageType` filters, the two views, the default view, and the `picker` setting. `agentsdoc_test` now checks that `picker`, `tree` and `list` are named. - `docs/api.md`: the `pagePicker` settings, the Param shape, and `disabled` on GET /api/link/resolve. No route changes. The README is unchanged. - **Styleguide:** the picker tree row stays product-only in `styles.css`; no shared sample and no new token. - **Skills:** - Changed: `add-element` (a `page` param's `pageType` and `picker`) and `create-feature` (pinning the picker on a listing element's section param). - Checked and unchanged: `edit-content` and the rest, since none of them describes the picker. - **Rule owners:** - eligibility: `PagePicker.eligible` - disabled: the tree's `enabled`, read through `disabledInLanguage`, and on the field the server's `disabled` - hidden: the server's `hidden` - module owning a type: GET /api/modules, read through `pickerview.moduleOwningType` - list rows: GET /api/module/entries through `modulequery` - the type's display name: `pagemanage.constraintTypeName` - **i18n:** `pagePicker.*`, `status.hidden`, and `widget.page.change` / `disabledError` / `typeHint`, in EN and DE. ## Notes and known gaps - **No git badge on the field's value row.** Answering it from /api/link/resolve would need the badges computation to move into that handler. The list view does show git badges, from GET /api/badges. - **Default selection.** With no stored value, or a stale one, nothing is selected any more; before, the root was. - **Hidden badge.** It appears only in the tree view, not on list rows, where every form would wear it. - **Title stays generic.** The dialog says "Choose a page"; the sub line names the type. - **Shared rules copied.** `styles.css` still does not import `components.css`, so the shared `.toggle`, `.choice`, `.close`, `.actions` and `.error` rules are copied into its "until E1" block, as #346 and #347 did. - **Not checked in a browser.** The screens are covered by component tests only. ## Verification - `tsc --noEmit` reports no errors, and `vite build` succeeds. - vitest: 138 files, 3456 tests passed. - `go vet ./...` reports nothing, and `go test ./...` passes (including `docs`, `scaffold`, `internal/element`, `internal/server`, `cmd/harness`). - `check-contrast.py`: 48 pairs, 0 failing. - `grep -rn TreeBrowser web/src` finds nothing. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Merge pull request 'feat(styleguide): the element card becomes the shared item row, plus the search field' (#355) from afk/346 into main
All checks were successful
govulncheck / govulncheck (push) Successful in 1m25s
ci / build-and-test (push) Successful in 4m32s
ci / release (push) Successful in 1m15s
17f53ad064
feat(picker): one TreeList for both pickers, tree or list by module and param, the page field's value row
All checks were successful
ci / build-and-test (pull_request) Successful in 3m57s
ci / release (pull_request) Has been skipped
d249146eec
- TreeBrowser is retired; TreeList (findChain, matchesQuery, nodeAddress)
  serves the page picker and the link browser's Page tab, with an address
  column, muted ineligible rows with a reason tooltip (no opacity), house /
  folder / file / eye-off icons, Disabled and Hidden badges.
- PagePicker: lg dialog with "Choose a page" and a sub line naming the page
  type, search, Tree/List toggle, "Only <type> pages" (per viewer), a list
  view (a module's entries via modulequery, else the tree flattened), the
  Current chip, refused disabled pages and a "Chosen:" foot. Which view
  opens is pickerview.ts's one rule: the param's picker, else a type owned
  by a tree: hidden module opens the list, else the tree with the toggle.
- Engine: a page param may set `picker: tree | list` (load error on another
  type or value), served in the field settings and canonical key order.
- GET /api/link/resolve adds `disabled: true` for a found page or element
  switched off where the link renders; the page field's value row shows the
  Disabled badge and an error line for it.
- AGENTS.md "Field types", add-element and create-feature skills,
  docs/api.md, i18n EN and DE.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

[autoland] verdict: pass
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!357
No description provided.