llama.cpp
610f7ab4 - ui: make the app feel native on phones (#31949)

Commit
3 days ago
ui: make the app feel native on phones (#31949) * ui: reveal the mobile sidebar by sliding the page aside On a phone the sidebar now stays put under the page, and the page slides right to uncover it, as a rounded card with the card border. The menu button rides on the page; a swipe from the left edge opens the sidebar, and a swipe back or a tap on the page closes it. Assisted-by: Claude (Opus 5.5 Medium) * ui: polish the mobile sidebar reveal and add a hold menu to rows Holding a conversation row on a phone opens its action menu anchored to the row, in place of the hidden three-dot trigger. The page no longer widens the document when it slides aside, so iOS stops panning it on the first open. The page and sidebar backgrounds start in the page color and blend into the sidebar tint as the sidebar opens, following a drag frame by frame, and a theme-color tag follows them for the browser bars. The sidebar's contents fade in on a log curve and scale up slightly, and sit at the same inset as the menu toggle, which now uses a two-line icon in a filled circle. Assisted-by: Claude (Opus 5.5 Medium) * ui: pin the context gauge on phones and lock the slid-aside page On a phone the context gauge leaves the chat form for the top right of the chat screen, in a round button across from the menu toggle. Both take the scroll-down button's frosted look. While the sidebar is out, and until the page is fully back in place, the page neither scrolls nor takes taps; a tap or drag on it still closes the sidebar. Assisted-by: Claude (Opus 5.5 Medium) * ui: open the mobile sidebar with a swipe from anywhere A rightward swipe from anywhere on the page now opens the phone sidebar too, beside the edge swipe that competes with the browser's own back and forward gesture. It starts after a short sideways move, skips text fields and content that scrolls sideways, and keeps the page from scrolling under it. Letting go opens or closes the sidebar past 30% of the way or on a flick. The top buttons and the chat form now hold their place on screen while the page is slid aside, instead of jumping to the far ends of a scrolled conversation. Assisted-by: Claude (Opus 5.5 Medium) * ui: hide the model org name on mobile unless a setting keeps it A phone's model selector is narrow, so it now drops the organization from model names by default. A new Display setting, "Show organization name on mobile", brings it back; it applies only while the organization name is shown at all. Assisted-by: Claude (Opus 5.5 Medium) * ui: close the mobile sidebar before following a tap in it On a phone, picking a conversation, New chat, Search, MCP or Settings in the open sidebar now slides it shut first and acts 180ms later, so the page or dialog appears once the sidebar is out of the way. With the sidebar already closed, or on a desktop, the action runs at once. Assisted-by: Claude (Opus 5.5 Medium) * ui: make the app feel native on phones and run it under the status bar Applies the mobile-native checklist: no tap highlight or landscape text inflation, instant taps on buttons and links, no label selection or callout on a held control, hover styles only where a pointer can hover, and 16px textareas on phones so focusing them does not zoom the page. The page now runs under the notch (viewport-fit=cover), so the installed iOS app tints its status bar from the page and the sidebar. The menu toggle, the context gauge, the sidebar and the top of the chat and models pages pad themselves clear of it, and the locked, slid page reaches down by the status bar's height, which iOS leaves out of it. Assisted-by: Claude (Opus 5.5 Medium) * ui: add a viewport height readout for debugging on phones With VITE_DEBUG set, a dev build shows the window, viewport unit and safe-area heights, and the elements that reach past the window's bottom and make the page scroll. It is loaded on demand, so production builds leave it out. Assisted-by: Claude (Opus 5.5 Medium) * ui: fill the screen from launch in the installed iOS app iOS 26 gives an installed app with viewport-fit=cover and a translucent status bar a viewport short of that bar (WebKit bug 301108), clipping the bottom of the page and lifting the chat form, but only while the page is shorter than the screen. In the installed app the page now reaches the screen plus the status bar, capped at the screen, so it gets the full screen from the start without ever scrolling. Assisted-by: Claude (Opus 5.5 Medium) * ui: raise the phone's corner controls in the installed app The installed app's status bar already sets the menu toggle, the context gauge and the open sidebar's logo apart from the screen's edge, so they sit just below it rather than a full 1rem lower. They share one value, which stays at 1rem below the inset in a browser tab. Assisted-by: Claude (Opus 5.5 Medium) * ui: add safe-area utilities for what is pinned to a screen edge The page runs under the status bar and over the home indicator, which an installed iPhone app always shows. New pt-safe, pb-safe, top-safe and bottom-safe utilities, each with a spacing step (pt-safe-12 is 3rem plus the top inset), keep pinned content clear of them instead of hand-written calc()s. A --safe-area-bottom joins --safe-area-top, and the chat screen and Models page use pt-safe-12. Assisted-by: Claude (Opus 5.5 Medium) * ui: open search in a full-height drawer on phones A phone used to leave for the /search page. It now opens the same search as the desktop dialog, in a bottom drawer that fills the screen below the status bar. Keyboard shortcuts, the Enter hint and the keyboard highlight stay on desktop, and so does the models manager, as in the phone sidebar. Assisted-by: Claude (Opus 5.5 Medium) * ui: show every dialog full screen on phones, clear of the status bar A phone now shows every dialog full screen, whatever size it asks for on a desktop, instead of each full-screen dialog repeating its own classes. Its top and bottom borders take the status bar's and the home indicator's height, transparent, so an installed app's dialogs keep their content clear of both with no change to their padding. The attachment preview, which has its own full-screen content, pads itself and lowers its close button the same way. Assisted-by: Claude (Opus 5.5 Medium) * ui: fade the page out under the status bar of the installed app on phones Assisted-by: Claude (Opus 5.5 Medium) * ui: show search in the full-screen dialog on phones instead of a drawer Assisted-by: Claude (Opus 5.5 Medium) * ui: start a phone page's title a row below the corner controls Assisted-by: Claude (Opus 5.5 Medium) * ui: pin the slid page's bottom bars against the screen in the installed iOS app Assisted-by: Claude (Opus 5.5 Medium) * ui: cast a soft shadow from the slid page onto the phone sidebar Assisted-by: Claude (Opus 5.5 Medium) * ui: hide the scroll-down key at the real bottom on iOS and show it only well above Assisted-by: Claude (Opus 5.5 Medium) * ui: fade the status-bar fade out as the page slides aside for the sidebar Assisted-by: Claude (Opus 5.5 Medium) * ui: draw the slid page's shadow above its rounded frame, so it follows the corners Assisted-by: Claude (Opus 5.5 Medium) * ui: show a phone chat's title and working directory at the top, and pick it in a drawer Assisted-by: Claude (Opus 5.5 Medium) * ui: give iOS Safari a solid top and fade the bottom of the screen on phones Assisted-by: Claude (Opus 5.5 Medium) * ui: let iOS Safari find the top strip, which it skips when it takes no taps Assisted-by: Claude (Opus 5.5 Medium) * ui: lengthen the top fade and lift the chat form a little in phone browsers Assisted-by: Claude (Opus 5.5 Medium) * ui: give iOS Safari's bottom toolbar the page's colour, not the chat form's Assisted-by: Claude (Opus 5.5 Medium) * ui: draw the phone's context gauge key like the menu key across from it Assisted-by: Claude (Opus 5.5 Medium) * ui: confirm opening the phone sidebar, its hold menu and its rows with a haptic tap Assisted-by: Claude (Opus 5.5 Medium) * ui: keep the screen-edge fades on the page as it slides aside for the sidebar Fading them out with the slide made the page flicker; the rounded frame over the slid page already clips them at its corners. Assisted-by: Claude (Opus 5.5 Medium) * ui: keep the working directory chip optional for hosts with their own trigger The phone layout opens the picker from the chat's header, so ChatForm hides the chip there. Master's version of the chip has no such switch; bring it back. Assisted-by: Claude (Opus 5.5 Medium) * ui: style layer 5's additions with master's tokens until the design system lands Assisted-by: Claude (Opus 5.5 Medium) * ui: drop the design system's stylesheets and fonts from app.css until it lands Assisted-by: Claude (Opus 5.5 Medium) * ui: keep master's font stacks until the design system brings its fonts Assisted-by: Claude (Opus 5.5 Medium) * ui: restore master's colour palette in app.css until the design system lands Resolving the safe-area commit took the design system's app.css, which drops the palette for its token files and aliases the shadcn names to semantic tokens. With the tokens gone those aliases point at themselves, so every colour went invalid: transparent surfaces and currentColor borders. Assisted-by: Claude (Opus 5.5 Medium) * ui: center alert dialogs on phones instead of sliding them up as drawers An alert is a short question, not a drawer: on a phone it is now a centered card, 90% of the screen wide and as tall as its text, scrolling only past the screen's height. It zooms in as on desktop, and its footer drops the margin the sheet needed. Assisted-by: Claude (Opus 5.5 Medium) * ui: space the working directory drawer for a phone The drawer reused the popover's compact picker: Browse sat against the search field, the divider ran past the rows and each row had its own inset. In the drawer the picker now takes the header's inset, gives Browse room, lines its rows up with the search results and sizes its text for a phone. Assisted-by: Claude (Opus 5.5 Medium) * ui: tap the haptic engine when a message sends, and once per hold A phone now confirms a sent message by feel, at the moment the send goes through (not on every press of the button), and before any await, so it still runs inside the tap or keypress that iOS needs to play its tick. The hold that opens a row's menu now taps the engine from the long-press hook itself, once: the hook vibrated and the row tapped as well, which buzzed twice on Android. Assisted-by: Claude (Opus 5.5 Medium) * ui: name the gauge dial sizes, the chat form's bottom offset and the sidebar fade Review follow-ups: the context gauge dial reads its grid, radius, stroke and track from one table per size instead of ternaries; the chat form's distance from the screen's bottom per platform and the sidebar content's fade duration are named constants. The viewport debug readout moves out of src, next to the tests, as it is a debug tool the app only loads in a dev build. Assisted-by: Claude (Opus 5.5 Medium) * ui: say why the microphone can't record instead of failing silently A llama-server opened over plain HTTP on the local network or over Tailscale is not a secure context, where browsers hide the microphone, so a tap on the mic did nothing but log to the console. The button now shows a crossed-out mic in the warning tone with the reason in its tooltip, and a tap says it in a toast, as a phone shows no tooltip. A start that fails names the browser's reason (access denied, no microphone, in use by another app) the same way, and the next tap tries again. The recorder passes the browser's error on instead of replacing it with a generic one. Assisted-by: Claude (Opus 5.5 Medium)
Author
Parents
Loading