Skip to content

feat(ui): clamp long text items with scroll and edge fades - #31

Merged
fang2hou merged 2 commits into
mainfrom
feat/clamp-text-scroll
Oct 14, 2026
Merged

fang2hou merged 2 commits into
mainfrom
feat/clamp-text-scroll

Conversation

@fang2hou

Copy link
Copy Markdown
Owner

What

Plain-text items (the voice-input path) can grow a card without bound. The text body now renders inside a scroll container capped at 16rem:

  • scrollbar-none (existing utility) keeps the scrollbar invisible; touch and wheel scrolling work natively.
  • Scroll-aware edge fades: a ~1.5rem white gradient (opaque → 60% @55% → transparent) at each edge, rendered only while content extends past that edge (scrollTop > 0 for the top, scrollTop + clientHeight < scrollHeight for the bottom). Fades are siblings of the scroller so they never scroll away, and are pointer-events-none + aria-hidden.
  • A ResizeObserver on the scroller and its content re-measures on reflow, so fade state stays correct through late webfont swaps, rotation/window resize, and edit-save text swaps. 1px slack absorbs fractional heights at pinch zoom.
  • Short items render pixel-identical to before: no overflow means no fades and no extra chrome.

Verification

  • mise run check green (oxfmt, oxlint, svelte-check, tsc).
  • pnpm vitest run 40/40.
  • Live DOM probe against local dev: long item clamps at 256px (scrollHeight 728) with bottom fade only; mid-scroll shows both fades; scrolled to end shows top only; short item shows zero fades and no layout change.

Plain-text items (typically voice-transcribed) grew the card without
bound. Wrap the plain-text <p> in a max-h-64 overflow-y-auto container
reusing the existing scrollbar-none utility; short content renders
pixel-identical (mt-2 moves to the wrapper, no padding added).

Top/bottom 1.5rem gradient fades (white -> 60% white at 55% -> transparent)
indicate clipped content and track scroll position in both directions:
top fade only when scrollTop > 0, bottom fade only when there is hidden
content below, measured on scroll and re-measured on mount and whenever
item.text changes (covers content replaced after an edit-save).

Tradeoffs:
- Fades are siblings of the scroller inside a relative wrapper, not
  children of it: absolutely positioned children of a scroll container
  scroll away with the content, so they could never stay pinned to the
  visible edges.
- Fade overlays use pure white while the card hover background is
  #fdfcfa (~1% luminance difference, imperceptible); this keeps the
  gradient a static utility class instead of a JS-synced background.
- The bottom-edge check keeps 1px slack so fractional scrollHeight at
  pinch-zoom or non-integer zoom levels cannot pin the bottom fade on.
- scrollTop is external DOM state, not derivable from runes, so the
  imperative measure-and-write in an $effect is deliberate; a
  ResizeObserver for font-swap-driven reflow was left out as none of
  the supported cases (initial, scroll, text swap) need it.

Scope: plain-text branch only; the code block branch, FileBrowser, and
collection headers are untouched.
Review follow-up to 59b2cad: the previous $effect only re-measured on
mount, scroll, and item.text changes. Width-driven reflow — window
resize, phone rotation, late webfont swap — changes overflow state
without firing any of those, leaving a stale bottom fade over the last
line (or a missing fade when text starts overflowing).

Observe the scroller and its content with a platform ResizeObserver,
measure from the observer callback, disconnect in the effect cleanup.
This subsumes the item.text dependency: text swaps that change the
content's size fire the observer, swaps that do not leave the overflow
state identical. The sync initial measure stays so the first paint
already has correct fades (ResizeObserver's first callback is async).

This retracts the 'no ResizeObserver' tradeoff recorded in the previous
commit body; everything else there is unchanged.
@fang2hou
fang2hou merged commit 3cc0e50 into main Oct 14, 2026
3 checks passed
@fang2hou
fang2hou deleted the feat/clamp-text-scroll branch October 14, 2026 05:47
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.

1 participant