Skip to content

Improve Task Strip edge clicks and panel switching behavior#183

Merged
johannesjo merged 8 commits into
johannesjo:mainfrom
FourWindff:focus/task-strip-scroll-alignment
Jun 29, 2026
Merged

Improve Task Strip edge clicks and panel switching behavior#183
johannesjo merged 8 commits into
johannesjo:mainfrom
FourWindff:focus/task-strip-scroll-alignment

Conversation

@FourWindff

Copy link
Copy Markdown
Contributor

Description

This PR improves the interaction experience for the Task Strip / Task Panel in three areas:

  1. Clicking the leftmost or rightmost task no longer reveals the edge action button
  2. Clicking a middle Task Panel can now more reliably select nearby task panels
  3. Added animation for task panel switching and scroll alignment to make the interaction feel smoother

Impact

  • Task item click behavior in the Task Strip
  • Active Task Panel scroll alignment logic
  • Visual feedback during panel switching

Verification

  • Clicking the leftmost or rightmost task does not show the edge action button
  • Clicking near a middle Task Panel correctly switches to the intended nearby task
  • Scroll alignment and switching animations behave as expected

FourWindff and others added 5 commits June 18, 2026 19:17
- Replace manual parentElement walk with el.closest('[data-tiling-strip]').
- Drop redundant taskOrder parameter from scrollTaskToEdge; read store.taskOrder internally.
- Update call sites and tests accordingly.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add optional behavior parameter to scroll helpers so callers can request

smooth scrolling. Use smooth behavior when focusing a panel and when

TilingLayout reacts to active task changes.
Replace scrollTaskToEdge and scrollTaskWithClickablePreview with a single

computeTaskStripScrollLeft helper that handles both edge snapping and

clickable-preview scrolling. Removes now-unused exports from store.ts.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Avoid smooth scrolling when aligning the active task on first render.

The first scroll uses 'instant' behavior; subsequent navigations keep 'smooth'.
@johannesjo

Copy link
Copy Markdown
Owner

Reviewed the diff + tests. The implementation is correct (I traced each test's arithmetic against computeTaskStripScrollLeft and they all hold), well-commented, and nicely covered. One thing worth discussing before merge, plus a couple of polish notes.

Worth discussing: could the platform do this for us?

The core behavior — keep a 64px clickable preview of neighbors and snap first/last tasks flush to the edge so the chevron affordance disappears — looks reproducible with native scrolling:

.tiling-layout-strip { scroll-padding-inline: 64px; }
el.scrollIntoView({ inline: 'nearest', behavior });

Tracing the cases:

  • Middle, fully visiblenearest no-ops (= your return null)
  • Middle, clipped → scrolls so the edge lands at the 64px padding boundary (= both branches)
  • First task → left edge aligns to padding, target clamps to 0 → left affordance hidden (= return 0)
  • Last task → right edge aligns to padding, clamps to max → right affordance hidden (= scrollWidth - clientWidth)

The natural scroll clamping reproduces the edge-snap requirement for free, which would remove most of the ~90 lines of math + ~150 lines of tests.

Two fair reasons you might keep the explicit version:

  • scrollTo touches only the strip; scrollIntoView walks all scrollable ancestors.
  • Your explicit tie-break for a task wider than clientWidth - 128px is more predictable than native nearest.

Not a blocker — just worth a deliberate "we chose explicit scrollTo because X" rather than defaulting to manual math.

Minor

  • focused-panel.ts (first/last branches) — these return 0 / scrollWidth - clientWidth without the Math.min(max, Math.max(0, …)) clamp the middle branch uses. When the strip doesn't overflow, the last-task branch returns a negative target. The browser clamps it so it's harmless, but it's inconsistent with the middle path — consider clamping or short-circuiting when scrollWidth <= clientWidth.
  • TASK_CLICKABLE_PREVIEW_PX = 64 — not tied to the 26px .tiling-layout-scroll-affordance width or a shared token, so it can silently drift if the affordance changes. A one-line comment relating the two would help.
  • (note, not a regression) the store layer (focused-panel.ts) reaches into the DOM here. Already true of the pre-existing scrollTaskIntoView, just expanded — flagging for awareness.

Nice touches

The data-tiling-strip marker to avoid grabbing nested scrollers, the instant-first/smooth-after closure, and the precise geometry tests are all well done.

FourWindff and others added 3 commits June 29, 2026 12:30
- Replace manual edge/preview scroll math with native scrollIntoView and
  CSS scroll-padding-inline for preview margins.
- Remove createInitialTaskScrollBehavior closure factory; use a plain
  component-local boolean in TilingLayout instead.
- Update tests and remove the now-unused re-exports.

Co-Authored-By: Claude <[email protected]>
…TODO

- Explain why TASK_CLICKABLE_PREVIEW_PX (64px) is intentionally larger
  than the 26px visual scroll affordance.
- Add a TODO noting that scrollTaskIntoView should move from the store
  layer to the view layer (TilingLayout.tsx) in the future.

Co-Authored-By: Claude <[email protected]>
…lper

- Snap the last task panel flush to the right edge of the strip to avoid
  a sliver of overflow from scroll-padding-inline.
- Extract maxScrollLeft helper and flatten the nullable scroller fallback
  in scrollTaskElementIntoView.

Co-Authored-By: Claude <[email protected]>
@FourWindff

Copy link
Copy Markdown
Contributor Author

Using the native approach is fine, but I ended up combining native scrollIntoView with a small amount of manual scrolling, keeping the benefits of the manual calculation. As you noted, the second reason to keep the manual version — your explicit tie-break for a task wider than clientWidth - 128px is more predictable than native nearest — is handled by the manual path. I also kept the manual handling for snapping the last panel flush to the right edge. These two edge cases add a small amount of extra calculation, but the overall code volume is much smaller than the previous fully-manual implementation.

Regarding the concern that scrollIntoView traverses all scrollable ancestors: in practice the task panel only has one scrollable parent, .tiling-layout-strip (data-tiling-strip). I added block: 'nearest', so scrollIntoView won't move any outer container vertically; horizontally, since only .tiling-layout-strip is scrollable, inline: 'nearest' naturally aligns the task inside that single strip and won't walk up into non-scrollable ancestors.

For the three minor points:

  1. The first issue you mentioned is resolved now that we use the native implementation.
  2. Added sync comments at src/styles.css:995 and src/store/focused-panel.ts:3 noting that scroll-padding-inline: 64px must stay in sync with TASK_CLICKABLE_PREVIEW_PX.
  3. Added a TODO at src/store/focused-panel.ts:150 flagging that this DOM side effect should eventually move out of the store layer and into the view layer (TilingLayout.tsx).

One extra note on the trade-off: normal tasks still use native scrollIntoView, relying on scroll-padding-inline to preserve the preview margins. Only very wide tasks and the last task take the manual scrollTo path — the former avoids the unpredictable edge jumping of inline: 'nearest', and the latter ensures the right edge snaps flush. This keeps the manual code limited to edge cases while letting the browser handle the common case.

@johannesjo

Copy link
Copy Markdown
Owner

Thank you very much! <3

@johannesjo johannesjo merged commit 7b99e9c into johannesjo:main Jun 29, 2026
2 checks passed
@FourWindff FourWindff deleted the focus/task-strip-scroll-alignment branch June 29, 2026 14:14
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