Files
kandev/docs/plans/threads-layouts/task-02-render-grid.md
T

8.7 KiB

id, title, status, wave, depends_on, plan, requirements, acceptance_criteria, system_design
id title status wave depends_on plan requirements acceptance_criteria system_design
02-render-grid Render the conversation grid done 2
01-persist-presentation
plan.md
REQ-UI-THREADS-DECK-004
AC-UI-THREADS-DECK-004.1
AC-UI-THREADS-DECK-004.2
AC-UI-THREADS-DECK-004.3
AC-UI-THREADS-DECK-004.4
AC-UI-THREADS-DECK-004.5
AC-UI-THREADS-DECK-004.6
AC-UI-THREADS-DECK-004.7
AC-UI-THREADS-DECK-004.8
../../specs/ui/system-design/threads-conversation-deck.md

Task 02: Render the conversation grid

Summary

Render the saved layout using stable task tiles, with two live desktop/tablet rows and the existing one-chat phone composition. Preserve selection, draft identity, scrolling, and viewport-owned session delivery.

In scope

  • Pass effective layout from the page and add the proposed pure thread-layout.ts helper plus board content-size measurement.
  • Preserve direct task children and keys; add Grid tracks, odd/single-task handling, height fallback, lower-row navigation, and layout-aware recovery.
  • Refresh activation on layout changes, reject stale observer callbacks, and prove both visible rows without increasing offscreen membership/streams.
  • Localize the fallback explanation and preserve existing task actions.

Out of scope

Composer disclosure and the Display editor/shortcut, owned by Tasks 03 and 04. Do not change platform traffic contracts, task ordering, or admission limits.

Acceptance

  1. Columns/Grid match UI-01/UI-02, including odd/single/empty cases and the 300px row-height fallback; saved preferences survive responsive changes.
  2. Switching layouts or removing a lower-row task retains the deterministic reader/session/draft and existing task navigation behavior.
  3. Both visible rows load selected conversations; 30-shell tests stay bounded, offscreen chats release, and phone transitions retain one active detail.

ASCII UI preview

UI-02: Grid at a sufficient desktop height, excerpt from full previews.

+--------------+--------------+--------------+
| A     [Open] | C     [Open] | E     [Open] |
| transcript  | transcript   | transcript   |
+--------------+--------------+--------------+
| B     [Open] | D     [Open] | F     [Open] |
| transcript   | transcript   | transcript   |
+--------------+--------------+--------------+

Task 02 retains normal composers; Task 03 supplies the collapsed footer shown in the combined preview. The task order is A, B, C, D, E, F. UI-05 phone excerpt:

[Threads / View] [2/6] [Menu]
[Task B v]            [Open]
[one live conversation     ]
[normal composer           ]

The phone uses swipe/picker navigation. These structures cover all assigned layout criteria. The board scrolls horizontally; each transcript owns its vertical content scroll. Grid tracks must not stretch when content grows.

Verification

From the repository root, after Task 01 bootstrap. Add behavior-specific RED unit/browser assertions before implementation. New files below are planned.

(cd apps/web && pnpm exec vitest run components/threads/thread-layout.test.ts components/threads/threads-board.test.tsx components/threads/thread-column-activation.test.tsx components/threads/use-thread-selection-recovery.test.tsx components/threads/use-mobile-thread-position.test.tsx lib/threads/thread-selection-fallback.test.ts app/threads/threads-page-client.test.tsx)
(cd apps/web && pnpm run typecheck)
(cd apps/web && pnpm exec eslint components/threads/threads-board.tsx components/threads/thread-column.tsx components/threads/thread-layout.ts components/threads/use-thread-column-activation.ts components/threads/use-thread-selection-recovery.ts app/threads/threads-page-client.tsx --max-warnings 0)
(cd apps/web && pnpm run i18n:check)
(cd apps/web && pnpm run i18n:ratchet)
(cd apps/web && pnpm e2e:run --project chromium tests/task/threads-layouts.spec.ts tests/task/threads-view.spec.ts tests/task/threads-task-actions.spec.ts -- --retries=0)
(cd apps/web && pnpm e2e:run --project mobile-chrome tests/task/mobile-threads-view.spec.ts tests/task/mobile-threads-swipe.spec.ts -- --retries=0)
git diff --check

Use the managed runner's builds and memory limits. Confirm discovery and capture rendered UI-01/UI-02 at wide desktop, short height, 767/768px, and a coarse-pointer tablet. API-seeded preferences are fixture setup until Task 04 exposes controls; assertions must read and interact with the rendered board.

Files likely touched

  • apps/web/app/threads/threads-page-client.tsx
  • apps/web/components/threads/threads-board.tsx, thread-column.tsx, use-thread-column-activation.ts, and use-thread-selection-recovery.ts.
  • New apps/web/components/threads/thread-layout.ts and its test.
  • New use-thread-selection-recovery.test.tsx beside the hook; existing board/activation/page tests named above.
  • New apps/web/e2e/tests/task/threads-layouts.spec.ts; reused presentation setup may live in new threads-presentation-helpers.ts.
  • apps/web/src/locales/*/threads.json for the height fallback.
  • Existing desktop/task-action/mobile specs only when fixture expectations need the explicit default layout; retain their scenarios.
  • apps/web/components/threads/AGENTS.md for layout and reflow ownership.

Dependencies

Task 01. Layout consumes its normalized effective presentation.

Risks

Pair wrappers or index-based React keys remount editors. An observer can deliver old-layout geometry after resize. Both rows share horizontal offsets, so recovery must retain task identity instead of inferring a unique task from an x-coordinate. Full editors need bounded row sizing.

Parallelism

sequential

Inputs

  • Layout requirements
  • Layout design
  • Existing board, viewport activation, stable order, primary-session E2E, thread task actions, and mobile swipe test patterns.

Results

PR #3626 follow-up: observer rebuilds measure current visibility, height fallback text no longer changes board allocation, wheel interaction records the reader anchor, and deep-link scrolling honors reduced motion. Final commands and results are in the review remediation record. The original implementation evidence below remains historical.

Done, 2026-09-11.

  • Behavioral RED: layout helper returned Columns for Grid, stale observer delivery changed active details, and browser geometry remained single-row. The real-page test additionally caught the native boot field omission recorded in Task 01, resize-generated scroll/live snapshots replacing the reader before responsive commit, and layout/width reflow interrupting deep-link scroll.
  • GREEN: the seven required Vitest files pass 62 tests. Task shells retain identity, both rows activate, 30-shell preload/stream windows stay bounded, lower-row horizontal anchors survive reflow, and user wheel input retires the initial deep-link mark. Typecheck and the exact ESLint command pass.
  • pnpm run i18n:check, pnpm run i18n:ratchet, and git diff --check pass. Traditional Chinese and pseudo catalogs were generated; unrelated generator changes were removed. No new dependencies or non-test runtime instances were added. Updated the scoped engineering guide's grid and reflow ownership.
  • First rebuilt desktop run: 19 passed, one deep-link failure. Corrected the reflow handoff. Final desktop command below passes all 20 cases (3.7 minutes) against the final same-membership snapshot guard, with retries disabled. The guard's 62 unit cases, typecheck, lint, and localization ratchet pass too.
  • Final mobile command: 10 tests passed with retries disabled (1.6 minutes), after pnpm run build:e2e. Both phone-to-tablet selection and an immediate resize after a deep link are covered. Inspected phone and coarse-tablet screenshots: one phone chat, two tablet rows, retained lower-row selection, and 44px Open task touch targets. This is emulator evidence, not a physical keyboard/device check.

Final browser commands ran sequentially from apps/web against the final production change, with one managed worker and no retries. The backend and plugin fixtures were built by the earlier managed run; the final change was frontend-only.

pnpm run build:e2e
pnpm e2e:run --no-build --project chromium tests/task/threads-layouts.spec.ts tests/task/threads-view.spec.ts tests/task/threads-task-actions.spec.ts -- --retries=0
pnpm e2e:run --no-build --project mobile-chrome tests/task/mobile-threads-view.spec.ts tests/task/mobile-threads-swipe.spec.ts -- --retries=0