Mobile editing: app-shell layout with reliable caret visibility (WIP)#352
Draft
johannesmutter wants to merge 5 commits into
Draft
Mobile editing: app-shell layout with reliable caret visibility (WIP)#352johannesmutter wants to merge 5 commits into
johannesmutter wants to merge 5 commits into
Conversation
- App shell while editing: locked document, nested scroller, toolbar as grid layer pinned above the keyboard (stable, no JS position tracking) - Tap caret preempt at the selectionchange commit: lands the caret centered in the predicted visible band; a short scroll guard re-asserts against iOS reveals computed from transient viewport geometry - Remove interactive-widget=resizes-content: iOS 26 half-engages it and resizes the layout viewport mid-gesture; layout-resizing platforms are detected at runtime instead and left to the OS - inputmode defaults to text when no selection exists, so taps during hydration still summon the keyboard - .svedit is the containing block for markers and overlays, so they scroll on the compositor with the content instead of lagging a frame behind in nested scrollers - Cursor reveals are scroller-aware and honor scroll-padding; touch node selections fade ::selection to a faint neutral where supported Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…geometry, embed hardening - Edit mode is server-rendered markup (data-editing on the shell, consumed via :has()) instead of a JS-toggled class, so the shell layout applies at first paint — kills the load flashes (toolbar bottom-right, full-bleed page visibly cropping once hydration landed). The scroll handoff splits into an $effect.pre capture before the DOM flips and a post-effect apply. - Svedit owns keyboard geometry: its single visualViewport listener learns the keyboard size (pinch-zoom guarded, rotation resets), detects layout-resizing platforms via a persistence latch with the resizes-content signature, and publishes --svedit-keyboard-inset for pure-CSS consumers. Predictive updates at text-selection commit and blur let the toolbar glide with the keyboard (iOS spring-like easing) instead of hanging mid-screen until the OS reports late. - Embed hardening: the tap preempt only engages when the document itself is unscrollable (app-shell signature) and never in iframes without a learned inset; the band clamps to the container box; the scroll-container walk is vertical-axis-aware; the stomp guard yields to user input; percentage scroll-padding resolves against the scrollport; .svedit isolates marker z-indices; a limit-clamped container hands residual reveal delta to the window. - Edit-mode chrome zones are painted by a fixed backdrop (escapes the overflow clip without creating scrollability). Percentage height chain replaces 100dvh (Safari 26 dvh mis-sizing). viewport-fit=cover tried and reverted: device-verified that it does not extend the in-browser layout viewport on iOS 26. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hook - Remove the fixed-overdraw experiment: device-verified that Safari composites only root-document overflow behind its floating chrome — fixed boxes beyond the layout viewport (backdrop or overdrawing shell) never paint there. A locked document can only tint the zones via the page background; real content behind the bars while editing requires standalone/PWA display. Findings documented in app.html and reset.css where the next attempt would start. - Edit-mode chrome zones tint via --app-chrome-zone-fill (defaults to the app canvas fill). - Reveal-bounds padding stays anchored to the visible edges (one rule, errs toward visibility); scroll-padding reads factored into named pad_* values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
michael
marked this pull request as ready for review
July 22, 2026 10:27
michael
marked this pull request as draft
July 22, 2026 10:28
Owner
|
Thanks for the exploration! Tried it out and some things I noticed:
Here's a screen cap of that selection flickering issue: ScreenRecording_07-22-2026.13-52-46_1.MP4Some questions for understanding:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft — up for review and experimentation (see #349 discussion). Based on #350: its commit is included here and drops out on rebase once #350 lands.
The product bet
On phones, formatting tools (bold, undo/redo, save) must be reachable with the thumb — so the toolbar docks at the bottom, above the software keyboard. That is the expensive choice on iOS Safari, and this PR is the price, paid deliberately and paid once: the reference editors avoid the problem rather than solve it (wordgard uses a sticky top menubar that a keyboard can never occlude; tldraw hides its toolbar entirely while editing on mobile; ProseMirror/CodeMirror ship no keyboard-docked chrome at all). Svedit keeps the bottom toolbar and makes it work.
What this does
While
editableis on, the demo becomes an app shell; read mode stays a normal scrolling web page (browser chrome minimizes, content shows behind the translucent bars — important for what visitors of a Svedit-built site experience).data-editingon the shell, consumed via:has()), so shell layout applies at first paint — no hydration flash. The only mode JS is the scroll handoff between document and shell scroller ($effect.precapture, post-effect apply) and a 3-line window pin.--svedit-keyboard-inset— position is layout, not JS tracking, so it is pixel-stable at any scroll speed. Its height is measured (ResizeObserver→--toolbar-height) and reserved viascroll-padding-bottom, so it can grow to multiple rows.visualViewportlistener: it learns the keyboard size, publishes the inset for pure-CSS consumers, and updates it predictively at text-selection commit and blur so the toolbar glides with the keyboard (iOS-spring-like transition) instead of hanging on the OS's late geometry reports.selectionchangethat commits the caret — one owned scroll; visible carets never move. An 800ms scroll guard re-asserts the position against iOS's mid-presentation stomps (observed up to ~2900px) and yields instantly to user input..sveditis the containing block (plusisolation) for anchor-positioned markers/overlays, so they scroll on the compositor with the content instead of lagging a frame behind in nested scrollers.inputmodedefaults totextwhen no selection exists, so taps during hydration still summon the keyboard.All iOS-specific behavior is gated on an
is_ioscheck (like every editor library does for these quirks); Android and desktop never see the workarounds. The band math has a single definition (__get_reveal_bounds), shared by the tap preempt and all model-driven reveals, clamped by the live visual viewport and honoring the scroller's declaredscroll-padding.Device-verified platform findings (iOS 26)
These cost real debugging time and are documented in code comments where relevant:
interactive-widget=resizes-contentis no longer ignored by iOS — it half-engages, resizing the layout viewport mid-gesture on refocus taps (blurring the canvas, closing the keyboard). Removed; must not come back.env(keyboard-inset-height)) — onevisualViewportlistener is the floor for a keyboard-docked toolbar.clickby re-hit-testing); delayed/debounced scrolls lose to iOS's own reveal or kill the keyboard. Theselectionchangecommit is the one safe moment to scroll.innerHeight) that snaps back — hence the scroll guard.viewport-fit=coverdoes not extend the in-browser layout viewport, and fixed boxes overdrawing it are not painted there. A locked shell can therefore only tint those zones (--app-chrome-zone-fill); true content behind the bars while editing needs standalone/PWA display.Open items
is_iosgating should make it inert there; unverified on hardware).--app-chrome-zone-fill) currently resolves to white — color choice open, as is the standalone/PWA question for true edge-to-edge editing.🤖 Generated with Claude Code