MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-130

2.5.19 Work-item quick-view modal in `/issues` (URL-driven peek + "Open full page")

Done
Description

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.html from getIssueDetail (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 via Pill tones.
  • A stale / cross-workspace / deleted peek key renders the not-found/no-access state (no leak, no crash) — inherited from getIssueDetail'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 (reads searchParams; 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.3 CreateIssueModal open/close+URL precedent) · 2.5.4 / 2.5.8 URL-param pattern · motir-core/CLAUDE.md — 4-layer · --el-* tokens
Status
Done
Type
Sub-task