Bottom bar: rides on the phone keyboard, capsule blur, toasts in the bar (#145) #240

Closed
kayg wants to merge 0 commits from issue/145-bar-keyboard into dev
Owner

Closes #145.

Blur capsule. The full-width progressive-blur band under the desktop editor's bar is gone. The blur is now a soft capsule hugging the bar, 14 px all round (Morph halo). It takes the same width, height and spring as the capsule, so it morphs with it through tabs, tools, the text bar and sheets. Three layers, each blurrier closer to the bar, fade out on all four edges. It's hidden on see-through pages, as the band was.

Keyboard docking (phone). lib/keyboard.svelte.ts reads visualViewport (resize and scroll) and lifts the bar with a transform until its bottom edge is 8 px above the keyboard's top. That's above iOS's form bar, since the visual viewport ends there. It springs when the keyboard opens and follows 1:1 while Safari scrolls under it. When the text loses focus (✓, or a tap outside), the bar hides in place right away, then comes back at the bottom once the keyboard has gone. Pinch zoom isn't mistaken for a keyboard. The caret line is scrolled clear of the bar, and the page gets bottom room (--kb-room) so its last lines can scroll above the keyboard. Android Chrome already resizes the page (interactive-widget=resizes-content), so the same code works out a lift of zero there.

Toasts are the bar (kayg, in the coordinator's message). notify() and every caller are unchanged. Only the presenter moves:

  • primitives/toast/inbar.svelte.ts is a one-at-a-time queue. A toast's time runs only while you can see it, and stops while the pointer is on it.
  • ToastRow.svelte is what a toast says (words, ×count, Undo). It's shared by Sonner's capsule and the bar.
  • attachBar() is called by the bar while it's mounted. With no bar on screen (a phone's list screens, sign-in), toasts go to the Sonner stack as before. A toast still waiting when its bar leaves carries on in the next screen's bar.
  • A toast that arrives over a sheet closes it. When you move on to selecting text or blocks, a plain result steps aside. Errors and Undo outcomes stay until they're read.

Tests

  • e2e/keyboard.spec.ts fakes visualViewport. It checks that the bar's bottom equals the keyboard top minus 8 px (tools, text bar, a toast), that it follows a scroll 1:1, that it hides on blur and comes back at its old place, that the caret stays above the bar, and that pinch zoom doesn't count as a keyboard.
  • e2e/toast-bar.spec.ts: the capsule becomes the toast with no Sonner stack, Undo works, and the bar returns afterwards. A toast that leaves its screen carries on (in the next bar on wide screens, on its own on a phone list).
  • inbar.test.ts (vitest) covers the queue's order, holding, hover, refresh and waits.
  • The layout-shift filters in editor and prefs-offline also allow the halo's announced morph.

Evidence (screenshots, frame strips, the phone checklist) is on the issue.

🤖 Generated with Claude Code

Closes #145. **Blur capsule.** The full-width progressive-blur band under the desktop editor's bar is gone. The blur is now a soft capsule hugging the bar, 14 px all round (`Morph` `halo`). It takes the same width, height and spring as the capsule, so it morphs with it through tabs, tools, the text bar and sheets. Three layers, each blurrier closer to the bar, fade out on all four edges. It's hidden on see-through pages, as the band was. **Keyboard docking (phone).** `lib/keyboard.svelte.ts` reads `visualViewport` (resize and scroll) and lifts the bar with a transform until its bottom edge is 8 px above the keyboard's top. That's above iOS's form bar, since the visual viewport ends there. It springs when the keyboard opens and follows 1:1 while Safari scrolls under it. When the text loses focus (✓, or a tap outside), the bar hides in place right away, then comes back at the bottom once the keyboard has gone. Pinch zoom isn't mistaken for a keyboard. The caret line is scrolled clear of the bar, and the page gets bottom room (`--kb-room`) so its last lines can scroll above the keyboard. Android Chrome already resizes the page (`interactive-widget=resizes-content`), so the same code works out a lift of zero there. **Toasts are the bar** (kayg, in the coordinator's message). `notify()` and every caller are unchanged. Only the presenter moves: - `primitives/toast/inbar.svelte.ts` is a one-at-a-time queue. A toast's time runs only while you can see it, and stops while the pointer is on it. - `ToastRow.svelte` is what a toast says (words, ×count, Undo). It's shared by Sonner's capsule and the bar. - `attachBar()` is called by the bar while it's mounted. With no bar on screen (a phone's list screens, sign-in), toasts go to the Sonner stack as before. A toast still waiting when its bar leaves carries on in the next screen's bar. - A toast that arrives over a sheet closes it. When you move on to selecting text or blocks, a plain result steps aside. Errors and Undo outcomes stay until they're read. **Tests** - `e2e/keyboard.spec.ts` fakes `visualViewport`. It checks that the bar's bottom equals the keyboard top minus 8 px (tools, text bar, a toast), that it follows a scroll 1:1, that it hides on blur and comes back at its old place, that the caret stays above the bar, and that pinch zoom doesn't count as a keyboard. - `e2e/toast-bar.spec.ts`: the capsule becomes the toast with no Sonner stack, Undo works, and the bar returns afterwards. A toast that leaves its screen carries on (in the next bar on wide screens, on its own on a phone list). - `inbar.test.ts` (vitest) covers the queue's order, holding, hover, refresh and waits. - The layout-shift filters in editor and prefs-offline also allow the halo's announced morph. Evidence (screenshots, frame strips, the phone checklist) is on the issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Bottom bar: rides on the phone keyboard, hugs its blur, and shows toasts (#145)
Some checks failed
CI / web (pull_request) Failing after 20s
CI / rust (pull_request) Successful in 2m1s
5964754b58
- The progressive blur behind the bar is a soft capsule around it (Morph's
  halo), the same size and spring as the bar, instead of a full-width band.
- On a phone the bar sits just above the keyboard (visualViewport), moved
  with a transform; it hides in place while the keyboard closes, and the
  caret line is kept clear of it.
- Toasts are the bar: the capsule morphs into the toast and Undo, then back.
  More than one wait their turn. notify() is unchanged; with no bar on
  screen they show on their own as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Landed in merge train #265.

Landed in merge train #265.
kayg closed this pull request 2026-10-14 12:13:53 +00:00
Some checks failed
CI / web (pull_request) Failing after 20s
CI / rust (pull_request) Successful in 2m1s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!240
No description provided.