Skip to content

fix(scrollbars): unify transient overlay behavior - #1286

Merged
Harry19081 merged 3 commits into
developfrom
dev/unified-scrollbars
Sep 5, 2026
Merged

fix(scrollbars): unify transient overlay behavior#1286
Harry19081 merged 3 commits into
developfrom
dev/unified-scrollbars

Conversation

@sudomaggie

Copy link
Copy Markdown
Collaborator

Problem

Visible scrollbar behavior was implemented independently across native overflow regions, dropdowns, CodeMirror/custom editor tracks, xterm, and Canvas documents. Those paths used different colors and widths, some changed size or color on hover, and some reserved layout space. The unlayered global selector could also outrank the existing scrollbar-hide utility, exposing horizontal bars in chat panes and compact title/tab rows that are intentionally scrollbar-free. Code blocks additionally owned a timer and React state per instance just to detect scrolling.

Solution

Introduce one semantic thumb-color token and shared 6px thumb / 12px hit-area / 3px edge-inset geometry. A single app-wide passive capture listener reveals only the currently scrolling surface and clears it after 900ms of inactivity, pauses while the document is hidden, and disposes on HMR. Native scrollers, dropdowns, CodeMirror, custom editor tracks, xterm, and isolated Canvas documents now follow that contract without hover resizing; xterm and custom tracks remain absolute overlays, and the global native rule uses scrollbar-gutter: auto. Explicit scrollbar-hide policy is restored for chat history, compact tab/title/filter rows, and other audited no-scrollbar surfaces.

Potential risks

  • Styled native scrollbar overlay behavior still follows the host WebKit/Chromium platform; the shared CSS avoids a stable gutter, but actual compositing can vary outside the supported desktop shells.
  • xterm overlay sizing relies on the current xterm/FitAddon truthy-width fallback behavior. A source-contract regression test covers that configuration, but a future xterm upgrade should re-check column measurement and dragging.
  • Sandboxed Canvas documents need a small self-contained scroll listener because they cannot inherit the host listener. Each iframe owns one passive listener and at most one timeout; destroying the iframe releases both.
  • Live light/dark-theme and constrained-viewport screenshots were not captured because this repository requires explicit opt-in for desktop UI control and it was not enabled for this task. Automated style contracts, lifecycle tests, Sass compilation, and the production build cover the implementation paths, but visual QA remains an unverified path.

Verification

  • pnpm exec vitest run --config config/vitest.config.ts src/util/ui/__tests__/transientScrollbars.test.ts src/util/ui/__tests__/scrollbarStyleContract.test.ts src/engines/ChatPanel/blocks/CanvasInlineCard/canvasBuilder.test.ts src/engines/ChatPanel/blocks/CanvasInlineCard/reactArtifactDocument.test.ts src/engines/ChatPanel/blocks/CanvasInlineCard/staticHtmlCanvas.test.ts src/components/Dropdown/tokens.test.ts src/components/SettingsTable/SettingsTablePagination.test.ts src/components/SettingsTable/stickyContract.test.ts src/engines/TerminalCore/components/TerminalInteractive/__tests__/terminalSizing.test.ts — 9 files and 76 tests passed.
  • pnpm run typecheck — passed.
  • pnpm run lint:fast — passed with zero warnings.
  • pnpm run check:test-placement — passed across 495 directories.
  • pnpm exec sass src/index.scss /tmp/orgii-unified-scrollbars.css --load-path=node_modules --no-source-map — compiled successfully; only the repository's existing Sass @import deprecation warnings were emitted.
  • pnpm run build — production webpack build passed.
  • pnpm exec prettier --check <29 changed files> and git diff --check — passed.
  • Final staged diff scan found no credentials, personal paths, generated build artifacts, caches, debug logs, or unrelated formatting changes.
  • Live visual QA/screenshots — not run because desktop UI control was not explicitly authorized.

UI audit

  • Scrollbar consistency sweep: 1 fix, 5 keep with reason, 1 abstract.
  • CustomScrollbar: 3 pre-existing accessibility/theme fix candidates, 3 keep with reason, 0 abstract. The candidates are documented but intentionally deferred to keep this PR limited to scrollbar appearance, visibility, and lifecycle.

Performance guard

Area Verdict Evidence Change or reason kept Verification
Background work fix Previous code blocks and custom tracks owned per-instance timers/listeners One app-wide passive listener, one coalesced timeout, hidden-document clearing; drag listeners exist only while dragging Visibility, coalescing, singleton, hidden-state, and disposal unit tests
Memory fix Activity state retains one element and one timeout Bounded singleton state with explicit clear/dispose Timer-count and latest-owner tests
Scope/isolation keep Shadow roots and sandboxed documents cannot inherit the host observer Shadow surfaces bridge to the host controller; each iframe owns one bounded observer Shadow cleanup and Canvas document injection tests
Rendering/hot path fix Code blocks used React state on high-frequency scroll events Direct attribute changes and existing rAF geometry updates avoid scroll-driven rerenders Unit tests plus production build

Performance verdict: pass.

sudomaggie and others added 3 commits September 5, 2026 14:04
Use one tokenized 6px thumb in a 12px overlay hit area across native scrollers, dropdowns, CodeMirror, xterm, custom editors, and isolated Canvas documents. Preserve explicit no-scrollbar chat and compact chrome surfaces, and replace per-code-block visibility timers with a bounded app-wide scroll activity controller.

Verified with targeted Vitest coverage, typecheck, fast lint, test-placement checks, Sass compilation, and a production webpack build.

Pre-commit hook ran. Total eslint: 0, total circular: 0
@Harry19081
Harry19081 merged commit b05bd01 into develop Sep 5, 2026
6 checks passed
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.

2 participants