2.5.19 Work-item quick-view modal in `/issues` (URL-driven peek + "Open full page")
Estimate: 15m · Depends on: 2.5.3, 2.5.8, 2.5.18
Add a quick view to /issues: a per-row trigger opens the work item in a modal over the list (a "peek"), so a user can scan an item without losing their place in the tree/list; the modal carries a prominent "Open full page →" link to /issues/[key]. Build it per 2.5.18's design, composing the existing components/ui/Modal + the detail field vocabulary — do NOT rebuild the whole 2.4 detail page.
Durable shape: the peek lives in the URL (e.g. ?peek=<key>), exactly like 2.5.4's filter and 2.5.8's view/sort params — shareable, reload-safe, and Esc/back closes it by clearing the param. The Server Component reads searchParams.peek; when present it calls the shipped getIssueDetail(identifier, ctx) (2.4) for that key and renders the IssueQuickView modal over the list — so the workspace/membership gate and the not-found/no-access path are inherited from that read, not re-implemented. A stale or forbidden peek key renders the design's not-found state, not a crash.
The trigger is the non-nested row affordance 2.5.18 draws (a quick-view icon button in the trailing row-actions cell, outside the whole-row /issues/[key] link — no <button> inside an <a>). It lives in the shared row cells so it appears in BOTH the Tree and the List view; activating it pushes ?peek=<key>.
Scope guard. v1 = open the peek, show the condensed fields read-only, "Open full page", and close. Editing inside the peek is out of scope (inline edits are 2.5.5 on the rows; full edit is the detail page) — keep the modal a read surface so we don't fork the edit paths (no complexity for nothing).
Acceptance criteria
- Each row (Tree and List) shows the quick-view trigger from 2.5.18; activating it opens the modal and sets
?peek=<key>(shareable / reload-safe). A direct load of/issues?peek=<key>opens with the modal already shown. - The modal renders the condensed fields per
quick-view.mock.htmlfromgetIssueDetail(reused, not duplicated); the "Open full page →" control and the header identifier navigate to/issues/[key].×/Esc/ backdrop close it by clearing?peek=, with focus returned to the trigger. - The trigger is NOT nested inside the row's
/issues/[key]link (no nested interactive); the whole-row link still works; colours via--el-*, status viaPilltones. - A stale / cross-workspace / deleted
peekkey renders the not-found/no-access state (no leak, no crash) — inherited fromgetIssueDetail's gate; 4-layer respected, no new read added unless a lighter projection is justified. - Component test (trigger sets
?peek+ the modal renders the item + "Open full page" href is/issues/[key]+ close clears the param). The open→peek→"Open full page" flow is exercised end-to-end by the Story E2E (2.5.6), which also sweeps the open modal for shell-a11y (focus trap, labelled dialog, return focus).
Context refs
design/work-items/quick-view.mock.html+design-notes.md(2.5.18) — the layout authority- 2.5.3
app/(authed)/issues/page.tsx(readssearchParams; render the modal here) + the shared row cells /issueRows+IssueTreeTable(where the trigger lands) + 2.5.8's flat List rows - 2.4
getIssueDetail(identifier, ctx)/IssueDetailDto+app/(authed)/issues/[key]/page.tsx(the detail destination) + its field components to compose the condensed view components/ui/Modal(the 2.3CreateIssueModalopen/close+URL precedent) · 2.5.4 / 2.5.8 URL-param pattern ·motir-core/CLAUDE.md— 4-layer ·--el-*tokens