# Route-backed Task Detail Modal Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Open normal desktop in-app Task navigation in a reusable,
route-backed document modal while retaining one canonical Task URL and
full-page implementation.

**Architecture:** The Workspace Router navigation boundary decorates Task
navigation with runtime-valid transient background state. The Workspace shell
renders the background route unchanged and mounts the canonical
`TaskDetailRoute` inside a reusable `PageModal`; narrow
viewports, reloads, direct links, and Expand render the full page.

**Tech Stack:** React 19, React Router, Base UI Dialog, TanStack Query,
TypeScript, Tailwind CSS, Node test runner with `tsx`.

---

### Task 1: Shared PageModal

**Files:**
- Create: `web/src/agentteam/components/PageModal.tsx`
- Create: `web/src/agentteam/components/PageModal.test.tsx`
- Modify: `web/src/agentteam/components/ui/index.ts`
- Modify: `web/src/agentteam/workspace/conversation/components/message-row/ServiceAccountArticlePreview.tsx`
- Modify: `web/src/agentteam/workspace/conversation/components/message-row/ServiceAccountArticlePreview.test.tsx`

- [ ] **Step 1: Write failing PageModal tests**

Assert that the source contains the approved dimensions and composes the
existing Base UI Dialog:

```ts
assert.match(source, /min\\(1200px,calc\\(100vw-64px\\)\\)/)
assert.match(source, /min\\(880px,calc\\(100dvh-64px\\)\\)/)
assert.match(source, /<Dialog/)
assert.match(source, /<DialogContent/)
assert.match(source, /disablePointerDismissal/)
```

Also assert that `ServiceAccountArticlePreview` renders `PageModal` instead of
owning a one-off page-size `DialogContent`.

- [ ] **Step 2: Run the focused tests and verify failure**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/components/PageModal.test.tsx \
  src/agentteam/workspace/conversation/components/message-row/ServiceAccountArticlePreview.test.tsx
```

Expected: FAIL because `PageModal` does not exist.

- [ ] **Step 3: Implement the shared PageModal**

Create this controlled public boundary:

```ts
export type PageModalProps = {
  children: React.ReactNode
  className?: string
  disablePointerDismissal?: boolean
  onOpenChange: (open: boolean) => void
  open: boolean
}
```

The component must compose the existing `Dialog` and `DialogContent`, default
pointer dismissal to disabled, and set the approved width and height while
retaining existing portal, focus, and accessibility behavior. Callers compose
existing `DialogHeader`, `DialogBody`, `DialogCloseButton`, and `DialogTitle`.

Migrate the service-account article reader to `PageModal` while preserving
its article column, close control, markdown behavior, and mobile presentation.

- [ ] **Step 4: Run focused tests**

Run the command from Step 2. Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add web/src/agentteam/components \
  web/src/agentteam/workspace/conversation/components/message-row/ServiceAccountArticlePreview.tsx \
  web/src/agentteam/workspace/conversation/components/message-row/ServiceAccountArticlePreview.test.tsx
git commit -m "refactor(web): add shared page modal"
```

### Task 2: Overlay navigation contract

**Files:**
- Create: `web/src/agentteam/workspace/tasks/navigation/taskDetailOverlayNavigation.ts`
- Create: `web/src/agentteam/workspace/tasks/navigation/taskDetailOverlayNavigation.test.ts`
- Modify: `web/src/agentteam/workspace/tasks/navigation.ts`
- Modify: `web/src/agentteam/workspace/tasks/navigation/taskDetailNavigationContext.ts`
- Modify: `web/src/agentteam/workspace/tasks/navigation/taskDetailNavigationContext.test.ts`
- Modify: `web/src/agentteam/workspace/tasks/navigation/taskDetailEntries.ts`
- Modify: `web/src/agentteam/workspace/tasks/navigation/taskDetailEntries.test.ts`

- [ ] **Step 1: Write failing state-machine tests**

Cover:

```ts
test("source-to-task navigation adds one runtime-valid background")
test("task-to-task navigation propagates the current overlay")
test("stale runtime state fails closed after reload")
test("foreign workspace background fails closed")
test("origin and overlay entry ids must match")
test("background task routes are rejected")
test("narrow navigation adds no presentation state")
test("Inbox task entries canonicalize to the channel-owned URL")
test("stripping overlay state preserves unrelated router state")
```

- [ ] **Step 2: Verify the tests fail**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/workspace/tasks/navigation/taskDetailOverlayNavigation.test.ts \
  src/agentteam/workspace/tasks/navigation/taskDetailNavigationContext.test.ts \
  src/agentteam/workspace/tasks/navigation/taskDetailEntries.test.ts
```

Expected: FAIL because the overlay state helpers are missing and Inbox still
targets `/inbox/tasks/...`.

- [ ] **Step 3: Implement the pure navigation helpers**

Implement:

```ts
export const taskDetailOverlayMediaQuery = "(min-width: 1024px)"
export const taskDetailOverlayStateKey = "taskDetailOverlay"

export function enhanceTaskDetailNavigationForOverlay(input: {
  currentLocation: Location
  enabled: boolean
  options?: NavigateOptions
  target: string
  workspaceId: string
}): { target: string; options?: NavigateOptions }

export function readTaskDetailOverlayState(input: {
  location: Location
  workspaceId: string
}): ParsedTaskDetailOverlayState | null

export function withoutTaskDetailOverlayState(state: unknown):
  Record<string, unknown>
```

Use one module-runtime UUID from `@/lib/randomUUID`. Validate the overlay
against `parseTaskDetailOrigin`, workspace-scoped paths, normalized
`returnTo`, and the current canonical Task target. Canonicalize legacy Inbox
Task targets.

Change `buildTaskToTaskNavigationIntent` to preserve unrelated current state
when supplied. Change Inbox entry construction to target
`taskDetailRoutePath`.

- [ ] **Step 4: Run the focused tests**

Run the command from Step 2. Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add web/src/agentteam/workspace/tasks/navigation*
git commit -m "feat(tasks): define route-backed modal navigation"
```

### Task 3: Workspace shell background presentation

**Files:**
- Create: `web/src/agentteam/pages/workspace-shell/TaskDetailOverlay.tsx`
- Create: `web/src/agentteam/pages/workspace-shell/TaskDetailOverlay.test.tsx`
- Modify: `web/src/agentteam/pages/workspace-shell/useWorkspaceShellRouteState.ts`
- Create: `web/src/agentteam/pages/workspace-shell/useWorkspaceShellRouteState.test.tsx`
- Modify: `web/src/agentteam/pages/WorkspaceShell.tsx`
- Modify: `web/src/agentteam/pages/WorkspaceShell.test.ts`
- Modify: `web/src/agentteam/pages/workspace-shell/WorkspaceShellRoutes.tsx`
- Modify: `web/src/agentteam/pages/workspace-shell/WorkspaceShellRoutes.test.tsx`

- [ ] **Step 1: Write failing shell tests**

Assert:

```ts
test("valid overlay state renders Routes at the background location")
test("1023px ignores and strips modal presentation")
test("1024px accepts modal presentation")
test("the mounted source becomes inert and aria-hidden")
test("Expand replaces the same canonical URL without overlay state")
test("Close navigates by the origin history depth")
test("legacy Inbox Task routes redirect to the canonical Task URL")
```

- [ ] **Step 2: Verify the shell tests fail**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/pages/workspace-shell/TaskDetailOverlay.test.tsx \
  src/agentteam/pages/workspace-shell/useWorkspaceShellRouteState.test.tsx \
  src/agentteam/pages/WorkspaceShell.test.ts \
  src/agentteam/pages/workspace-shell/WorkspaceShellRoutes.test.tsx
```

Expected: FAIL because the shell does not understand overlay state.

- [ ] **Step 3: Make route-state presentation-aware**

Update `useWorkspaceShellRouteState` so actual Router location remains
available while effective route fields and `scopedRouterLocation` derive from
the validated background whenever the viewport query matches. Subscribe to
`matchMedia(taskDetailOverlayMediaQuery)`.

At the Workspace Router interception boundary, call
`enhanceTaskDetailNavigationForOverlay` before committing navigation.

- [ ] **Step 4: Implement the Task modal renderer**

`TaskDetailOverlay` composes `PageModal` and lazy
`TaskDetailRoute`. It must:

```tsx
<LazyTaskDetailRoute
  presentation="overlay"
  routeParams={target}
  headerActions={overlayHeaderActions}
  onBack={onClose}
  ...
/>
```

Use `DialogCloseButton` for the explicit close action and an `IconButton` with
`Maximize2` for Expand. The source Workspace shell wrapper becomes inert and
`aria-hidden` only while the overlay is active.

- [ ] **Step 5: Add the compatibility redirect**

Keep `/inbox/tasks/:channelId/:taskNumber` as compatibility input, but render a
small route gate that replaces it with
`/channels/:channelId/tasks/:taskNumber`.

- [ ] **Step 6: Run shell tests**

Run the command from Step 2. Expected: PASS.

- [ ] **Step 7: Commit**

```bash
git add web/src/agentteam/pages
git commit -m "feat(tasks): render task detail in workspace modal"
```

### Task 4: Canonical Task chrome and history behavior

**Files:**
- Modify: `web/src/agentteam/workspace/tasks/detail/TaskDetailRoute.tsx`
- Modify: `web/src/agentteam/workspace/tasks/detail/TaskDetailPage.tsx`
- Modify: `web/src/agentteam/workspace/tasks/detail/TaskDetailLoadingScaffold.tsx`
- Modify: `web/src/agentteam/workspace/tasks/detail/TaskDetailRouteLazyFallback.tsx`
- Modify: `web/src/agentteam/workspace/tasks/detail/taskDetailBackNavigation.ts`
- Modify: `web/src/agentteam/workspace/tasks/detail/taskDetailBackNavigation.test.ts`
- Modify: `web/src/agentteam/workspace/tasks/detail/TaskDetailPageActions.test.ts`
- Modify: `web/src/agentteam/i18n/locales/en/workspace/tasks.json`
- Modify: `web/src/agentteam/i18n/locales/zh-CN/workspace/tasks.json`
- Modify: `web/src/agentteam/i18n/locales/ja/workspace/tasks.json`

- [ ] **Step 1: Write failing chrome tests**

Cover:

```ts
test("canonical page Back always replaces to All Tasks")
test("overlay chrome receives caller-owned Expand and Close actions")
test("overlay omits the mobile sidebar trigger")
test("Copy Link includes the workspace-scoped canonical Task URL")
test("Task detail retains exactly one bounded scroll region")
```

- [ ] **Step 2: Verify failure**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/workspace/tasks/detail/taskDetailBackNavigation.test.ts \
  src/agentteam/workspace/tasks/detail/TaskDetailPageActions.test.ts
```

Expected: FAIL because page Back still follows origin history and overlay
actions are not supported.

- [ ] **Step 3: Implement presentation props**

Add:

```ts
presentation?: "page" | "overlay"
headerActions?: React.ReactNode
```

to the Route/Page/scaffold boundary. Full-page headers retain Back and mobile
sidebar controls. Overlay headers render caller actions and no full-page Back
or sidebar trigger.

Make page Back resolve only to `/tasks/views/all-tasks` with `replace: true`.
Build copied links with `buildWorkspacePath(workspaceId, taskDetailRoutePath)`.
Add translated `task_board.expand_to_page` copy in all workspace locales.

- [ ] **Step 4: Run focused tests**

Run the command from Step 2. Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add web/src/agentteam/workspace/tasks/detail \
  web/src/agentteam/i18n/locales/*/workspace/tasks.json
git commit -m "feat(tasks): add modal task detail chrome"
```

### Task 5: Preserve reply-thread Task sources

**Files:**
- Modify: `web/src/agentteam/workspace/conversation/components/ReplyThreadPanel.tsx`
- Modify: `web/src/agentteam/workspace/conversation/components/ReplyThreadPanel.test.tsx`
- Modify: `web/src/agentteam/workspace/conversation/components/conversation-messages/useThreadNavigation.ts`
- Modify: `web/src/agentteam/workspace/conversation/components/conversation-messages/useThreadNavigation.test.tsx`

- [ ] **Step 1: Write failing thread navigation tests**

Assert that a reply-thread Task reference:

```ts
assert.match(source, /sourceKind: "reply-thread-external"/)
assert.doesNotMatch(taskCallbackSource, /closeAndNavigate/)
```

Render navigation in a Memory Router and assert the typed Task state is
preserved.

- [ ] **Step 2: Verify failure**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/workspace/conversation/components/ReplyThreadPanel.test.tsx \
  src/agentteam/workspace/conversation/components/conversation-messages/useThreadNavigation.test.tsx
```

Expected: FAIL because Task navigation closes the reply thread and raw
`onNavigateAway` can drop Router state.

- [ ] **Step 3: Implement non-destructive Task navigation**

Build a typed `reply-thread-external` Task intent and navigate with its state
without calling `closeAndNavigate`. Ensure generic thread Task navigation
never drops the intent state.

- [ ] **Step 4: Run focused tests**

Run the command from Step 2. Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add web/src/agentteam/workspace/conversation/components
git commit -m "fix(tasks): preserve reply thread behind task modal"
```

### Task 6: Product spec and complete verification

**Files:**
- Modify: `specs/task/board.md`
- Modify: `specs/task/embedded-view.md`
- Modify: `docs/design/2026-08-09-task-detail-modal.md`
- Modify: `docs/design/2026-08-09-task-detail-modal-implementation-plan.md`

- [ ] **Step 1: Update canonical specs**

Document the route-backed modal/page dual presentation, canonical URL,
responsive threshold, fixed full-page Back destination, non-forked document,
and retired Inbox projection URL.

- [ ] **Step 2: Run focused Task and shell tests**

Run:

```bash
cd web
npx tsx --test \
  src/agentteam/components/PageModal.test.tsx \
  src/agentteam/pages/workspace-shell/TaskDetailOverlay.test.tsx \
  src/agentteam/pages/workspace-shell/useWorkspaceShellRouteState.test.tsx \
  src/agentteam/workspace/tasks/navigation/taskDetailOverlayNavigation.test.ts \
  src/agentteam/workspace/tasks/navigation/taskDetailNavigationContext.test.ts \
  src/agentteam/workspace/tasks/navigation/taskDetailEntries.test.ts \
  src/agentteam/workspace/tasks/detail/taskDetailBackNavigation.test.ts \
  src/agentteam/workspace/tasks/detail/TaskDetailPageActions.test.ts \
  src/agentteam/workspace/conversation/components/conversation-messages/useThreadNavigation.test.tsx
```

Expected: all tests pass.

- [ ] **Step 3: Run static verification**

Run:

```bash
cd web
npm run typecheck
npm run lint
npm run build:web
```

Expected: all commands exit 0.

- [ ] **Step 4: Run the complete web unit suite**

Run:

```bash
cd web
npm test
```

Expected: all tests pass with zero failures.

- [ ] **Step 5: Commit final specs and fixes**

```bash
git add specs/task docs/superpowers web
git commit -m "docs(tasks): define task detail modal presentation"
```

- [ ] **Step 6: Prepare review**

```bash
git status --short
git log --oneline origin/main..HEAD
git diff --check origin/main...HEAD
agentteam exec review add \
  '{"path":"/Users/lonng/devel/pingcap/agentteam/.worktrees/task-detail-modal-2415","thread":"#agentteam-in-agentteam:251ccc00"}'
```

Expected: clean worktree, no whitespace errors, and a review context attached
to the thread.

- [ ] **Step 7: Push and request review**

```bash
git push -u origin codex4/task-detail-modal-2415
```

Request agentteam-glm4 review against `origin/main...HEAD`, fix all Critical and
Important findings, rerun the affected verification, create the PR, and
advance it with a `/merge` comment after approval.
