Skip to content

[Story 3.2d] Webview-side task-scoping guard + per-task seq #371

@edelauna

Description

@edelauna

Part of #358 (Epic 3: TaskScheduler + Subtask Fan-out).

Depends on: Story 3.2c (Extension-side task-scoping guard — must land first).

Context

Story 3.2c gates posting at the extension host — non-focused tasks don't post. This story adds defense-in-depth on the webview side: the frontend actively rejects messages tagged with a non-current taskId, and migrates clineMessagesSeq from a single global counter to a per-task Map<string, number>.

Without the seq migration a focus switch mid-flight causes a correctness bug: the newly-focused task's first state push carries its own lower seq value, which the webview rejects because the old task's higher global counter is still in place — silently discarding the first update after every task switch.

Also adds a "N tasks running" visual indicator in ChatView so users know background tasks are active.

Developer Notes

In src/core/webview/ClineProvider.ts:

  • Migrate clineMessagesSeq from number to Map<string, number> keyed by taskId. Each task increments its own seq counter. On focus switch, the new task's seq starts from its own accumulated value, not from the global counter.
  • Emit clineMessagesTaskId: string alongside clineMessagesSeq in state messages, populated from taskRegistry.current?.taskId.

In webview-ui/src/context/ExtensionStateContext.tsx:

  • In handleMessage for messageUpdated: reject if message.taskId !== currentState.currentTaskId. This prevents a background task's message from corrupting the displayed task's chat.
  • In mergeExtensionState: reject clineMessages if clineMessagesTaskId !== currentTaskId (cross-task state push from a task that lost focus mid-flight).
  • Update seq comparison to use per-task seq: only compare seq values when clineMessagesTaskId matches.

In webview-ui/src/components/chat/ChatView.tsx (or a small new component):

  • If runningTaskIds.length > 1: show a small indicator (e.g., "2 tasks running" pill/badge near the TaskHeader). Clicking it navigates to task history where SubtaskRow already handles navigation.
  • No other UI changes needed — existing subtask tree and "Back to parent task" button handle task switching.

Files: src/core/webview/ClineProvider.ts, webview-ui/src/context/ExtensionStateContext.tsx, webview-ui/src/components/chat/ChatView.tsx

Tests (webview-ui/src/__tests__/ExtensionStateContext.spec.ts or similar):

  • messageUpdated with taskId !== currentTaskId → message is rejected, clineMessages unchanged.
  • messageUpdated with taskId === currentTaskId → message is applied normally.
  • State message with clineMessagesTaskId !== currentTaskIdclineMessages not updated, other state fields still merged.
  • Per-task seq: state with matching taskId and higher seq → applied. State with matching taskId and lower seq → rejected.

Acceptance Criteria

  • Webview rejects messageUpdated from non-current tasks — no message corruption when two tasks run concurrently.
  • Per-task clineMessagesSeq prevents cross-task stale overwrites after a focus switch.
  • "N tasks running" indicator is visible in ChatView when runningTaskIds.length > 1.
  • All existing single-task tests pass unchanged — per-task seq is transparent when only one task runs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions