fix: recognize visible Dockview chat panels (#3481)

This commit is contained in:
Carlos Florêncio
2026-09-07 22:17:02 +01:00
committed by GitHub
parent ffa42a8cfd
commit 4ea3ccf336
7 changed files with 210 additions and 27 deletions
@@ -41,6 +41,14 @@ A focused throwaway hook test reproduced the race: session A's delayed response
produced no `updateSessionReadCursor("session-1", "m2")` call after session B
dispatched. The temporary test was removed after recording the red evidence.
The follow-up trace exposed a separate visibility defect. Dockview restored the
incoming Chat tab as selected in the center group while the side-by-side
Changes group retained global focus. `usePanelActive` read Dockview's
`isActive`, which combines group-local tab visibility with global group focus,
so the visibly rendered transcript reported `isVisible=false` and skipped
initial placement. Completed agents produced no later message or work-state
write to mask the skipped placement.
## Scope
### In scope
@@ -119,6 +127,7 @@ changes.
## Work orders
- [x] [Task 01: Preserve read cursor across task switches](task-01-preserve-read-cursor-across-task-switches.md)
- [x] [Task 02: Recognize visible Dockview chat panels](task-02-recognize-visible-dockview-chat-panels.md)
## Verification results
@@ -132,6 +141,15 @@ changes.
- Mobile Chrome completed-task switch E2E passed (1 test).
- Specification linter tests passed (30 tests); all specification files passed.
- `git diff --check` passed.
- Follow-up TDD regression: the visibility hook returned false when Dockview
reported `isActive=false` and `isVisible=true`; it passed after switching to
the group-local visibility contract.
- Follow-up focused Vitest passed: 1 file, 6 tests.
- Follow-up TypeScript typecheck and scoped ESLint passed.
- Follow-up Chromium task-switch E2E passed with Changes retaining global
Dockview focus while the visible Chat transcript returned to the bottom.
- Follow-up Mobile Chrome completed-task switch E2E passed.
- Follow-up specification linter tests and full specification lint passed.
## Risks
@@ -0,0 +1,97 @@
---
id: "02-recognize-visible-dockview-chat-panels"
title: "Recognize visible Dockview chat panels"
status: done
wave: 2
depends_on:
- "01-preserve-read-cursor-across-task-switches"
plan: "plan.md"
requirements:
- REQ-UI-TRANSCRIPT-AUTO-SCROLL-001
acceptance_criteria:
- AC-UI-TRANSCRIPT-AUTO-SCROLL-001.11
- AC-UI-TRANSCRIPT-AUTO-SCROLL-001.14
- AC-UI-TRANSCRIPT-AUTO-SCROLL-001.15
system_design:
- ../../specs/ui/system-design/transcript-auto-scroll.md
---
# Task 02: Recognize Visible Dockview Chat Panels
## Summary
Treat the selected Chat tab as visible independently of which side-by-side
Dockview group owns global focus. Prove that a completed task restores its
transcript placement without relying on a later agent update.
## In scope
- Subscribe to Dockview panel visibility rather than global active state.
- Cover the focused-sibling-group contract in the visibility hook.
- Extend the desktop completed-task switch regression with Changes-group focus.
## Out of scope
- Dockview layout persistence or active-group restoration.
- Transcript auto-scroll preference semantics or stored offsets.
- Mobile composition, which does not mount Dockview.
## Acceptance
- A selected Chat tab reports visible when another Dockview group owns focus.
- Hiding Chat behind another tab in its own group reports not visible.
- Returning to an idle completed task places the transcript at the bottom while
Changes remains the globally active panel.
## Verification
```bash
(cd apps && pnpm --filter @kandev/web test -- --run hooks/use-panel-active.test.ts)
(cd apps/web && pnpm exec eslint hooks/use-panel-active.ts hooks/use-panel-active.test.ts e2e/tests/chat/unread-divider.spec.ts)
(cd apps/web && pnpm run typecheck)
(cd apps/web && pnpm e2e:run --host --project chromium tests/chat/unread-divider.spec.ts -- --grep "completed task switch" --retries=0)
(cd apps/web && pnpm e2e:run --host --no-build --project mobile-chrome tests/chat/mobile-unread-divider.spec.ts -- --grep "completed task switch" --retries=0)
python3 scripts/lint-spec-files.test.py
python3 scripts/lint-spec-files.py --all
git diff --check
```
## Files likely touched
- `apps/web/hooks/use-panel-active.ts`
- `apps/web/hooks/use-panel-active.test.ts`
- `apps/web/e2e/tests/chat/unread-divider.spec.ts`
- `docs/specs/ui/requirements/transcript-auto-scroll.md`
- `docs/specs/ui/system-design/transcript-auto-scroll.md`
## Dependencies
Task 01 established the environment-switch placement lifecycle and its
completed-task regression.
## Risks
- Using global Dockview focus recreates the defect when another visible group
remains active after layout restoration.
- Treating every mounted portal as visible could mark hidden session tabs read.
## Parallelism
`sequential`
## Inputs
- `REQ-UI-TRANSCRIPT-AUTO-SCROLL-001`, especially acceptance criteria `.11`,
`.14`, and `.15`.
- UI transcript auto-scroll system design.
- Dockview panel visibility and active-state contracts.
## Results
- Replaced Dockview's global active-state subscription with the group-local
visibility property and event.
- Added unit coverage for a visible Chat panel whose sibling group owns focus.
- Extended the desktop completed-task switch regression to retain Changes as
the active panel and prove the visible transcript returns to the bottom.
- Focused Vitest, scoped ESLint, TypeScript typecheck, desktop Chromium E2E,
Mobile Chrome parity E2E, specification lint, and diff checks passed.
@@ -2,7 +2,7 @@
status: active
system: ui
created: 2026-07-30
updated: 2026-09-04
updated: 2026-09-07
owners:
- cfl
---
@@ -72,6 +72,11 @@ hand-off it is designed to protect.
newest cached message and a disabled transcript shows its saved reader
position as soon as the panel is measurable, without first exposing the
browser's default top position.
- **AC-UI-TRANSCRIPT-AUTO-SCROLL-001.15:** **GIVEN** a desktop transcript is the
selected tab in its Dockview group while another visible group owns global
focus, **WHEN** a task switch restores that layout, **THEN** the transcript
is treated as visible and completes its enabled or disabled initial
placement.
## Migrated source detail
@@ -127,6 +132,9 @@ the asynchronous hand-off it is designed to protect.
**WHEN** the user switches between them and returns, **THEN** the incoming
enabled transcript shows the newest cached message immediately and remains
at the newest message after its history refresh.
- **GIVEN** the Changes panel owns Dockview focus while Chat remains the
selected center tab, **WHEN** the user returns to that task, **THEN** Chat
completes initial transcript placement without requiring an agent update.
- **GIVEN** the incoming transcript has auto-scroll disabled, **WHEN** that
environment-switch placement runs, **THEN** it restores the incoming
session's saved position rather than the outgoing transcript's position.
@@ -4,7 +4,7 @@ system: ui
requirements:
- REQ-UI-TRANSCRIPT-AUTO-SCROLL-001
created: 2026-08-27
updated: 2026-09-04
updated: 2026-09-07
owners:
- kandev
---
@@ -50,8 +50,15 @@ if that path reads it. The scroll setter records the WebKit-safe offset.
Desktop Dockview renders panel content through persistent portals. An inactive
session transcript can therefore be mounted and receive messages while its
scroll container has zero or detached geometry. `usePanelActive` supplies the
authoritative active-tab signal to `TaskChatPanel`, which passes it to the
native transcript scroll coordinator as `isVisible`.
authoritative group-local visibility signal to `TaskChatPanel`, which passes it
to the native transcript scroll coordinator as `isVisible`.
Dockview's panel `isVisible` property means the panel is the selected tab in
its own group. Its `isActive` property additionally requires that group to own
global Dockview focus. The transcript uses `isVisible` and
`onDidVisibilityChange`; clicking a side-by-side Files or Changes group cannot
make a still-rendered Chat panel ineligible for initial placement or read
tracking.
Geometry-based initialization and divider placement do not consume their
one-time completion state while `isVisible` is false. When a transcript first
@@ -194,10 +201,11 @@ pinning, and disabled position restoration.
## Failure modes
- If the container is not mounted, the helper performs no work.
- If a transcript is mounted but inactive, one-time placement remains pending
until the panel becomes active and measurable.
- If a panel becomes inactive again before the post-restore frames complete,
the scheduled placement is cancelled.
- If a transcript is mounted but hidden behind another tab in its group,
one-time placement remains pending until the panel becomes visible and
measurable.
- If a panel becomes hidden again before the post-restore frames complete, the
scheduled placement is cancelled.
- Chromium, Gecko, and WebKit clamp the signed 32-bit maximum to the native
maximum scroll position.
- An offset outside WebKit's safe range can resolve to zero and move the