Skip to content

feat: improve horizontal overflow cues and off-screen task attention#52

Open
ASRagab wants to merge 7 commits intojohannesjo:mainfrom
ASRagab:task/feat-sidescrolling-2b86eb
Open

feat: improve horizontal overflow cues and off-screen task attention#52
ASRagab wants to merge 7 commits intojohannesjo:mainfrom
ASRagab:task/feat-sidescrolling-2b86eb

Conversation

@ASRagab
Copy link
Copy Markdown
Contributor

@ASRagab ASRagab commented Apr 3, 2026

Summary

This PR improves the usability of Parallel Code when many task slices are open side-by-side.

The key change in framing was that horizontal scrolling already existed; the real product gaps were:

  1. overflow discoverability — it was easy to miss that more slices existed off-screen
  2. off-screen observability — hidden agent/task slices could need attention without a strong signal in the sidebar

This PR addresses those gaps and also increases terminal scrollback for agent review/debugging.

What changed

1) Horizontal overflow affordance

  • adds passive left/right edge cues to the main tiling strip in src/components/TilingLayout.tsx
  • tuned the visual treatment in src/styles.css to use a narrow accent/caret-led cue rather than a heavy overlay
  • preserves the existing horizontal scrolling model instead of introducing custom scroll controls or a layout rewrite

2) Runtime-only viewport visibility for task slices

  • adds runtime-only task visibility state:
    • visible
    • offscreen-left
    • offscreen-right
  • publishes that state from the actual horizontal viewport in TilingLayout
  • intentionally does not persist viewport visibility

3) Additive task attention model

  • adds a separate attention model in src/store/taskStatus.ts without replacing the existing dot status model:
    • idle
    • active
    • needs_input
    • error
    • ready
  • keeps getTaskDotStatus() intact for backward compatibility

4) Better off-screen/background attention detection

  • extends throttled output analysis so background task agents can still surface meaningful attention state
  • fixes an important edge case where throttled background [Y/n] prompts could otherwise lose needs_input state

5) UI surfacing for off-screen attention

  • updates Sidebar and StatusDot so off-screen tasks can surface stronger attention cues
  • keeps the UI relatively quiet:
    • active off-screen tasks get visual emphasis
    • explicit text badges are reserved for stronger states like input/error
  • updates edge affordances so they can reflect off-screen attention directionally

6) Attention-aware desktop notifications

  • moves notification logic from coarse busy/waiting transitions toward richer attention transitions:
    • ready
    • needs_input
    • error
  • keeps debounce/deduping behavior

7) Longer terminal scrollback

  • increases xterm scrollback to 10_000 lines in both:
    • src/components/TerminalView.tsx
    • src/remote/AgentDetail.tsx
  • centralizes the shared value in src/lib/terminalConstants.ts

2026-04-03 00 49 36

Why this approach

  • does not rewrite the layout system
  • does not rewrite focus/navigation behavior
  • keeps viewport state runtime-only
  • prefers additive observability over broad architectural change
  • treats task-agent attention as the first pass, rather than trying to model every shell-specific state immediately

Notes on implementation details

  • viewport measurement uses actual DOM rects against the scroll container rather than brittle offset math
  • recompute triggers were tightened so reorder/structure changes do not leave stale off-screen state behind
  • the overflow cue went through a few iterations to land on a narrower, more natural accent/caret-led affordance that works better across themes

Validation

  • npm run typecheck
  • npm run lint
  • npx vitest run src/store/taskStatus.test.ts

All passed locally.

Files changed

  • src/components/Sidebar.tsx
  • src/components/StatusDot.tsx
  • src/components/TerminalView.tsx
  • src/components/TilingLayout.tsx
  • src/lib/terminalConstants.ts
  • src/remote/AgentDetail.tsx
  • src/store/core.ts
  • src/store/desktopNotifications.ts
  • src/store/store.ts
  • src/store/taskStatus.test.ts
  • src/store/taskStatus.ts
  • src/store/types.ts
  • src/store/ui.ts
  • src/styles.css

Commit structure

  • feat: add horizontal overflow affordance cues
  • feat: surface off-screen task attention
  • feat: increase terminal scrollback
  • chore: simplify sidebar attention cleanup

@johannesjo
Copy link
Copy Markdown
Owner

Hey hey! Thank you very much! I'm on a short break since we just got a baby, but I'll review this ASAP once I'm back

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves usability when many task slices are open side-by-side by adding horizontal overflow cues, tracking runtime-only per-task viewport visibility, and introducing a richer “attention” model (active/needs input/error/ready) that can surface in the sidebar, edge cues, and desktop notifications.

Changes:

  • Added left/right overflow affordances to the tiling strip and runtime-only per-task viewport visibility tracking.
  • Introduced additive task “attention” state derived from agent output/question detection, and surfaced it in the sidebar/status dot + desktop notifications.
  • Increased xterm scrollback (centralized constant) for better agent review/debugging.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/styles.css Adds styling for sidebar offscreen attention badges and tiling overflow/attention edge cues.
src/store/ui.ts Adds getters/setters for runtime-only task viewport visibility state in the store.
src/store/types.ts Introduces TaskViewportVisibility type and stores it in AppStore.
src/store/taskStatus.ts Adds TaskAttentionState, background-output analysis scheduling, and derived attention helpers.
src/store/taskStatus.test.ts Adds coverage for new attention state behavior and background prompt edge cases.
src/store/store.ts Re-exports attention and viewport visibility APIs/types from the store barrel.
src/store/desktopNotifications.ts Switches desktop notifications to attention-based transitions (ready/needs_input/error).
src/store/core.ts Initializes taskViewportVisibility and narrows cleanupPanelEntries input type.
src/remote/AgentDetail.tsx Uses centralized scrollback constant for remote agent terminal.
src/lib/terminalConstants.ts Adds shared TERMINAL_SCROLLBACK_LINES = 10_000 constant.
src/components/TilingLayout.tsx Implements overflow cues, viewport visibility publishing, and offscreen attention direction detection.
src/components/TerminalView.tsx Uses centralized scrollback constant for main terminal.
src/components/StatusDot.tsx Extends dot rendering to reflect attention state (color + optional glow/pulse).
src/components/Sidebar.tsx Surfaces offscreen attention (badge + row emphasis) and passes attention into StatusDot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Coalesce scroll handler with requestAnimationFrame to avoid
  synchronous layout reads (getBoundingClientRect) on every scroll event
- Drop subtree:true from MutationObserver so internal panel renders
  don't trigger expensive disconnect/reconnect cycles (panel add/remove
  is already covered by the taskOrder createEffect + ResizeObserver)
- Compute getTaskAttentionState once in getOffscreenAttentionInfo
  instead of calling both taskNeedsAttention and getTaskAttentionState
  (taskNeedsAttention internally calls getTaskAttentionState, so the
  attention state was derived twice per invocation)
@ASRagab
Copy link
Copy Markdown
Contributor Author

ASRagab commented Apr 9, 2026

Addressed the 3 review comments from Copilot in 022a7af:

1. MutationObserver subtree: truechildList: true (TilingLayout.tsx)
Dropped subtree: true so internal panel renders (text updates, child re-renders) no longer trigger the expensive observeStrip() cycle (disconnect + reconnect ResizeObserver + forced layout read). Panel add/remove/reorder is already covered by the createEffect on store.taskOrder and the ResizeObserver on the content element.

2. Scroll handler rAF coalescing (TilingLayout.tsx)
Wrapped handleScroll in a requestAnimationFrame guard so updateViewportState() (which calls getBoundingClientRect() on the container + each task element) runs at most once per frame instead of synchronously on every scroll event.

3. Deduplicate attention state computation (Sidebar.tsx)
getOffscreenAttentionInfo was calling taskNeedsAttention(taskId) (which internally calls getTaskAttentionState) and then calling getTaskAttentionState(taskId) again. Now computes attention state once and checks idle/ready inline — same logic, half the reactive signal reads.

All changes verified: tsc --noEmit clean, eslint clean, vitest run src/store/taskStatus.test.ts 35/35 pass.

@johannesjo
Copy link
Copy Markdown
Owner

Thank you very much! <3

@johannesjo
Copy link
Copy Markdown
Owner

Good work overall — the three Copilot comments (MutationObserver subtree, scroll rAF coalescing, attention deduplication) were addressed cleanly. One new issue I noticed:

Desktop notification re-firing when state persists (desktopNotifications.ts)

The needs_input and error branches have no guard against the previous state:

} else if (current === 'needs_input') {
  scheduleBatch('needs_input', taskId);
} else if (current === 'error') {
  scheduleBatch('error', taskId);
}

The original waiting branch required prev === 'busy' (i.e. it fired only on transition into that state). The new branches fire on every reactive re-run of the createEffect while the task is in needs_input or error. Since createEffect re-executes whenever any getTaskAttentionState() call changes its reactive dependencies (including activeAgents(), store.tasks, or store.taskGitStatus for any task in the list), a task that stays blocked on [Y/n] input can send repeated desktop notifications each time an unrelated task moves.

Suggested fix — mirror the ready guard pattern:

} else if (current === 'needs_input' && prev !== 'needs_input') {
  scheduleBatch('needs_input', taskId);
} else if (current === 'error' && prev !== 'error') {
  scheduleBatch('error', taskId);
}

Everything else looks solid: the rAF guard on scroll is correctly implemented and cleaned up (removeEventListener prevents new events; the existing if (!containerRef) guard in updateViewportState prevents crashes from any rAF that fires after unmount), childList: true without subtree: true is correct for detecting panel add/remove at the strip level, and the syncTaskViewportVisibility shallow-equality check avoids unnecessary store writes. The getOffscreenAttentionInfo deduplication is clean. Test coverage for the throttled background prompt case is appreciated.

Copy link
Copy Markdown
Owner

@johannesjo johannesjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three original Copilot review comments were all correctly addressed. One remaining issue before merge:

Notification spam — the createEffect in desktopNotifications.ts that fires needs_input/error notifications is missing a transition guard. It fires on every reactive re-run while a task is in that state, not just on state entry. With multiple tasks open, any unrelated attention-state change can trigger duplicate notifications. Fix: track previous state per task and only notify on prev !== currentState transitions.

Otherwise this PR is solid. Details in the review comment.

ASRagab and others added 2 commits April 14, 2026 00:30
Mirror the ready-branch transition guard so needs_input/error only fire on state entry, not on every reactive re-run while the task stays in that state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/components/StatusDot.tsx
#	src/store/taskStatus.ts
@ASRagab
Copy link
Copy Markdown
Contributor Author

ASRagab commented Apr 14, 2026

@johannesjo last comment addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants