# Menu techniques: brand-006 RJ Laravel Admin (P1)

**Board:** https://menu.pina-brand-006-rj-laravel-admin.pages.dev/ (tabs: Overview · Depth 1 · Depth 2 · Depth 3 · Cross-cutting · Phone · EN/中文). The phone board is at `/m/`. The earlier M1–M3 menu board is kept at `/options/`.

**Status:** reference board. It does not make a decision. Each depth has a recommended default.

## Scope (Frank, 2026-10-18)

- The menu's **content** belongs to the server: `AdminMenu.php`, served as `GET /api/v1/admin/me/menu` (WP-F-02).
- Regrouping that content is a later exercise, led by evidence and done with backend. It is **out of design scope**.
- This board covers **mechanics** only: how a menu of depth 1, 2 or 3 opens, nests and stays findable in the final 4b·R shell. The 4b·R shell is:
  - a white header with a white RJ HQ corner
  - the DTC navy sidebar
  - a 76px collapsed rail with labels under the icons
  - 2b's collapse toggle and 2b's density
  - a light tape motif
- Every frame uses **sample items**. The labels come from the live menu, but **the grouping is illustrative, not a proposal**.

## Evidence used

- **Live super-admin tree:** `phase-1/menu/src/menu_data.py`, from `AdminMenu.php`.
  - 8 sections, **52 top-level entries** and 111 links.
  - The largest group is Materials, with 13 children.
  - Measured on the board's 1:1 strip, only **15 of the 52** entries sit above a 900px fold.
- **WP-F-02 golden fixtures:** super 109 links, operations 54, edge-quirks 7, none 3. The server already filters by permission.
- **cn.json:** only 29 of 119 labels have a cn key. A missing key returns English (`phase-1/menu/MENU-OPTIONS.md`).

## Techniques

| Depth | Technique | Expanded (232px) | Collapsed (76px rail) | Phone | Good for | Weak at | Default |
|---|---|---|---|---|---|---|---|
| 1 | Flat labelled list | Plain list | Labelled rail | Drawer list | ≤ ~14 entries per login | 52 live entries, of which 15 sit above the fold | **Default while ≤14** |
| 2 | T-a Accordion | Groups open in place | Clicking a group re-expands the sidebar | Drawer accordion, one open | Reading the whole tree | Long groups push the rest down | **Default (expanded)** |
| 2 | T-b Rail + flyout | Accordion (T-a) | Hover (~140ms intent) or click opens a 240px panel | Drawer accordion | Fast hops; the rail stays 76px | Hover precision (touch and keyboard twins required) | **Default (collapsed)** |
| 2 | T-c Rail + pinned column | Rail + 200px column for the current area | Column hides, rail falls back to T-b | Drawer accordion | Long sessions in one area | Costs 200px all the time | Alternative |
| 3 | T-d Mega flyout | Accordion with level-2 headings | Wide flyout, level-2 heading → level-3 links, 1–3 columns | Accordion with headings | One glance at a whole area | Dense past ~25 links | **Default (collapsed)** |
| 3 | T-e Column + in-page tabs | Column = level 2, tabs = level 3 | Column hides, flyout shows level 2 | Accordion to level 2 + chip tabs | Moving sideways between siblings | Siblings must share a frame; scrolls past ~7 tabs | **Default (tabs part)** |
| 3 | T-f Header section switcher | Breadcrumb ▾ opens the sibling list | Same | Breadcrumb opens a sheet | Deep links, small screens | Hidden until known | Complement |

## Recommended defaults

1. **Depth 1:** a labelled rail, with the expanded sidebar as a plain list. This holds while a login sees about 14 entries or fewer.
2. **Depth 2:**
   - Collapsed: rail + flyout (T-b).
   - Expanded: accordion (T-a).
   - It is the same tree shown two ways, and the 2b toggle switches between them.
3. **Depth 3:**
   - Collapsed: mega flyout (T-d), with level-2 groups as headings over level-3 links.
   - Expanded: accordion, with level 2 shown as headings inside the open group.
   - On the page: level-3 siblings show as **tabs** (T-e).
   - Never an accordion inside an accordion.
4. **The rule:** the shell supports **any depth up to 3**. Anything deeper becomes **in-page tabs** owned by the level-3 page, never a fourth nav tier.
5. **Phone:**
   - Depth 1: drawer list.
   - Depth 2: drawer accordion with one group open.
   - Depth 3: accordion with level-2 headings, plus chip tabs on the page for level 3.
   - Alternative: a drill-down drawer that opens at the active page's parent.

## Cross-cutting (every depth)

- **⌘K Find:**
  - Opens from ⌘K or Ctrl K, the top-bar search, or the rail magnifier.
  - Searches every page the login can reach, at any depth, matching the label, the path and the cn label.
  - Each result shows its path, and the empty state lists recent pages first.
  - Searching orders and records is separate and out of scope here.
- **Pins and recent pages:**
  - Collapsed: a "Pinned" rail item comes first, and its flyout shows pins plus the last 4 pages.
  - Expanded: "Pinned" and "Recent" sections sit above the tree.
  - A pin is a page **key**, so it follows renames. A pin to a page the login has lost disappears quietly.
  - Keep the list to 8 or fewer.
- **Active-state wayfinding:** one route match lights the whole path:
  - the rail item, with a 3px tick and a bright icon
  - the child link, in the flyout, accordion or column
  - the active tab
  - the breadcrumb
  - the page title, which names the group when the leaf label is generic ("Products · List")
  - When the sidebar is expanded and a group is open, the group row stays bright but unfilled, and the child carries the fill.
- **CJK labels under the 76px icon:**
  - The label box is 62px wide at 10px, with up to 2 lines. CJK breaks between characters, about 6 per line.
  - English never breaks inside a word. A word that is too wide shrinks once to 9px; if it still does not fit it is clamped with an ellipsis.
  - The tooltip always carries the full EN and cn label.
  - A missing cn key shows English, which matches the server.
- **Permission shaping:** the server filters, and the shell adds three guards:
  - an empty group disappears
  - an empty section disappears
  - a single-child group becomes a direct link
  - No disabled rows are drawn for missing permissions.

## The contract the shell follows

1. The shell draws exactly what `GET /api/v1/admin/me/menu` returns: `sections[] → items[] → children[]`, where `children` is recursive (the same node shape again). It hard-codes no page list or grouping.
2. **A node's depth picks its form:**
   - Level 1 is a rail item, or an expanded row.
   - Level 2 is a flyout, accordion or column link, or a heading when it has children.
   - Level 3 is a link under its level-2 heading, and also a tab on its page.
   - **Level 4 and deeper is not drawn in the nav.** The shell links to the level-3 ancestor, and that page owns the deeper view.
3. **Guards:**
   - drop empty groups and sections
   - promote a single-child group to a direct link
   - ignore unknown keys
4. **Active state** comes from a route match (longest href prefix) mapped to the node path. Every lit signal derives from that one path.
5. **Optional `badge`:** where present, it shows as a count pill when expanded and a dot on the rail icon. Today only `system.failed-jobs` sends it. Widening it is the earlier board's proposal.
6. **View preferences are per staff:** collapsed or expanded, open groups, pins and recent pages. They are stored locally until the backend offers them on `/me/preferences`, which is a **backend proposal** and not a current contract.

## Files

- `src/build_techniques.py` builds `index.html` and `m/index.html`. It imports `../menu/src/menu.css`, `icons_menu.py` and `menu_data.py` read-only.
- `src/app.js` is the live renderer. It holds one sample tree and draws every technique, with hover, click, keyboard and touch support.
- `src/techniques.css` holds this board's additions to the shell CSS.
- `src/shoot.mjs` takes the headless proof shots. They are in `shots/`: `local--*` for local and `live--*` for the deployed URL.
