# Route-backed Task Detail Modal Design

**Date:** 2026-08-09
**Status:** Approved for implementation
**Owner:** agentteam-codex4
**Review:** agentteam-glm4

## Goal

Normal in-app Task navigation should preserve the caller's working context
without creating a second Task detail implementation.

On viewports at least 1024 CSS pixels wide, a normal in-app Task navigation
opens the canonical Task detail document in a large centered modal. Direct
links, page reloads, new tabs, explicit expansion, and viewports below 1024
pixels render the canonical page route.

The Task URL remains:

```text
/channels/:channelId/tasks/:taskNumber
```

The modal and page are two presentations of the same route, data, mutations,
cache, unread behavior, and Task detail UI.

## Product contract

### Entry behavior

The following desktop in-app entries open the modal:

- workspace All Tasks list and board cards;
- channel Task list and board cards;
- Inbox Task rows;
- unified search Task results;
- channel, DM, and reply-thread Task references;
- Agent Task lists;
- Workflow Run Task links;
- Task-to-Task navigation from the open Task document.

The following entries render the full page:

- copied or shared canonical links;
- address-bar navigation and cold application boot;
- page reload;
- modified click or new-tab navigation;
- explicit Expand from the modal;
- any viewport below 1024 CSS pixels.

The application must not infer modal presentation from the URL alone.

### Back and close behavior

- Browser Back from the first modal Task returns to the source route.
- Browser Back after a parent/child drill-down returns to the previous Task
  while keeping one modal container.
- Closing with Escape or the explicit close action returns directly to the
  original source route.
- The modal does not close when the scrim is clicked.
- Expand replaces the current modal history entry with the full-page
  presentation of the same canonical URL and retires that modal chain, so
  browser Back cannot reopen an earlier drilled Task as a modal.
- The canonical page header Back action always replaces the route with
  `/tasks/views/all-tasks`.
- Browser Back on a canonical page keeps normal browser history behavior.

### URL invariant

The address bar must always identify the Task currently rendered in the modal.
Parent/child drill-down pushes the next canonical Task URL. Sequential
previous/next browsing may replace the current Task URL.

Copy Link always copies a workspace-scoped canonical Task URL. A copied URL
never encodes modal presentation or a source route.

## Architecture

### Route-backed overlay state

Normal Task navigation still pushes the canonical Task URL. The Router
location state additionally carries a validated transient presentation record:

```ts
type TaskDetailOverlayState = {
  version: 1
  runtimeId: string
  entryId: string
  backgroundLocation: {
    pathname: string
    search: string
    hash: string
    state: unknown
    key: string
  }
}
```

`runtimeId` is generated once for the current JavaScript application runtime.
A reload may retain `history.state`, so state is not considered restorable
unless its `runtimeId` matches the current runtime. This guarantees that a
cold boot or reload renders the full page.

The overlay record is accepted only when:

- the current route is a valid canonical Task route;
- the accompanying `taskDetailOrigin` is valid;
- `entryId` matches the origin;
- the background belongs to the current workspace;
- the background route is not another Task detail;
- the normalized background route matches the origin's `returnTo`;
- the overlay `entryId` has not been retired by Expand;
- the viewport currently satisfies `(min-width: 1024px)`.

Invalid or stale overlay state fails closed to the canonical page.

### Central navigation enhancement

`WorkspaceRouteProvider` already intercepts workspace-local Router navigation.
The Workspace shell enhances Task targets at this single boundary:

1. Existing typed Task navigation state is preserved.
2. Raw in-app canonical Task links receive a generic validated Task origin.
3. A qualifying source-to-Task navigation receives transient overlay state.
4. Task-to-Task navigation propagates the current valid overlay state.
5. Non-Task navigation is unchanged.
6. Mobile/narrow navigation receives no overlay state.

Reply-thread Task navigation must not close the thread before navigating. The
thread remains mounted as the modal background and is restored unchanged when
the modal closes.

### Background rendering

While the overlay is active:

- the Workspace shell derives its presentation route from
  `backgroundLocation`;
- `WorkspaceShellRoutes`, sidebar presentation, Inbox frame, and
  `ThreadAwareSurfaceLayout` continue rendering the source location;
- the source remains mounted so board filters, list scroll, drafts, thread
  state, sockets, and query observers remain intact;
- the source shell is `inert` and `aria-hidden` while the modal is open.

The modal renders `TaskDetailRoute` directly with route parameters parsed from
the current canonical URL.

### Shared `PageModal`

A dedicated `PageModal` is built on the existing Base UI-backed `Dialog`
primitive family. The name describes its page-like structure and scale without
embedding Workspace, Task, article, or document semantics. It owns only:

- responsive outer dimensions;
- a bounded, overflow-hidden page-level container.

`PageModal` itself composes existing `Dialog` and `DialogContent`. Callers
compose `DialogHeader`, `DialogBody`, `DialogCloseButton`, and `DialogTitle`
for their own chrome. Neither layer reimplements portal behavior, focus
trapping/restoration, Escape, close semantics, or accessibility.

The first consumers are:

- route-backed Task detail;
- the existing service-account article reader.

Task routes, Task data, source restoration, and Task navigation remain outside
`PageModal`. Service-account authoring and other large dialogs may adopt it
later without expanding this Task change.

## Responsive layout

`PageModal` uses content-driven clamps rather than a fixed viewport percentage:

```css
width: min(1200px, calc(100vw - 64px));
height: min(880px, calc(100dvh - 64px));
```

Additional rules:

- Task modal presentation is enabled only at 1024px or wider.
- Below 1024px the Task uses the canonical page, not a full-screen dialog.
- Crossing the 1024px boundary while a Task modal is open intentionally
  remounts the Task route in page or modal presentation. Persisted/query state
  remains canonical, but transient editor text and Task-local scroll are not
  guaranteed to survive this uncommon live-resize transition.
- The dialog keeps about 12px corner radius and a 45-50% scrim.
- The Task header remains fixed.
- `TaskDetailPage` owns the only vertical document scroll region.
- Prose paragraphs should keep a readable line length near 68 characters,
  while tables, code, attachments, editors, activity cards, and the existing
  60rem Task content container may use their required width.

## Task document chrome

The full-page Task keeps its existing Back action and receives no modal-only
controls.

The modal presentation adds:

- Expand to full page;
- an explicit Dialog Close control;
- an accessible dialog title;
- no mobile sidebar trigger.

The modal uses the canonical Task detail header and body rather than wrapping
the Task in a second summary UI.

## Read, focus, and nested surfaces

- Opening the real Task detail may retain the existing auto-mark-read
  behavior; hover and focus alone do not navigate, prefetch, or mark read.
- Base UI Dialog owns the focus trap.
- Closing restores focus to the original opener when it remains mounted;
  otherwise the source surface receives focus.
- Nested dropdowns, collaborator panels, attachment previews, and dialogs
  handle Escape before the outer Task dialog.
- Task close controls remain reachable to touch screen readers.

## Canonical and compatibility routes

New Inbox Task navigation targets the canonical channel-owned Task URL.
The existing `/inbox/tasks/:channelId/:taskNumber` path is compatibility input
only and redirects to the canonical Task route.

`specs/task/board.md` is updated so the canonical detail and modal/page
presentation contract has one source of truth.

## Verification

The implementation must cover:

- overlay state construction, propagation, validation, and stripping;
- stale runtime state after reload;
- background workspace and origin mismatch rejection;
- canonical URL rewriting for Inbox aliases;
- 1023px page versus 1024px modal behavior;
- Task-to-Task URL synchronization;
- Expand replacement, retired modal history, and close history depth;
- full-page header Back to All Tasks;
- reply-thread preservation;
- shared document dialog sizing and pointer-dismissal policy;
- service-account article reader reuse;
- source inertness, fixed chrome, and bounded Task scrolling;
- TypeScript typecheck, lint, production build, focused tests, and the full web
  unit suite.
