Glassy modal for mobile Command Universe#4183
Conversation
Agent-Logs-Url: https://github.com/cybersemics/em/sessions/b0e8c68d-a69f-4958-9aca-4b507f073066 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
Agent-Logs-Url: https://github.com/cybersemics/em/sessions/b0e8c68d-a69f-4958-9aca-4b507f073066 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
…to DesktopCommandUniverse Part A: GestureCheatsheet → MobileCommandUniverse - File renames: openGestureCheatsheet.tsx, toggleGestureCheatsheet.ts, GestureCheatsheet.tsx, GestureCheatsheetIcon.tsx - State: showGestureCheatsheet → showMobileCommandUniverse - Command ID: openGestureCheatsheet → openMobileCommandUniverse - Action type: toggleGestureCheatsheet → toggleMobileCommandUniverse - All variable/component/function renames and JSDoc updates Part B: CommandPalette → DesktopCommandUniverse - File renames: commandPalette.ts (cmd+action), CommandPalette.tsx, CommandPaletteIcon.tsx → CommandUniverseIcon.tsx - State: showCommandPalette → showDesktopCommandUniverse - Command ID: commandPalette → desktopCommandUniverse - hideFromCommandPalette → hideFromDesktopCommandUniverse - CommandType: 'commandPalette' → 'desktopCommandUniverse' - All variable/component/function renames and JSDoc updates Part C: Tests and snapshots - Unit test imports, string literals, and descriptions updated - E2e test names, selectors, and snapshot identifiers updated - Puppeteer snapshot files renamed User-facing strings use singular "Command Universe" label. No functionality changes. Agent-Logs-Url: https://github.com/cybersemics/em/sessions/70d63002-3bab-4438-bbee-f8edb7ca0620 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
…nd Universe) Agent-Logs-Url: https://github.com/cybersemics/em/sessions/70d63002-3bab-4438-bbee-f8edb7ca0620 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
Conflicts resolved: 1. Import section: kept new imports (newThoughtCommand, outdentCommand) from main while using our renamed openMobileCommandUniverseCommand import 2. Test cases: kept the new 'should not show commands whose gesture does not start with the gesture in progress' test from main (#4123) while using our renamed test description 'should always show mobile command universe command' 3. Updated new test from #4124 that referenced old name 'openGestureCheatsheet' to 'openMobileCommandUniverse' Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
Conflicts resolved in src/hooks/__tests__/useFilteredCommands.ts: 1. Import section: PRs #4123 and #4124 on main added newThoughtCommand and outdentCommand imports alongside the old openGestureCheatsheetCommand. Resolution: kept the new imports, used our renamed openMobileCommandUniverseCommand. 2. Test case insertion: PR #4123 added a new test ('should not show commands whose gesture does not start with the gesture in progress') immediately before the existing cheatsheet test we had renamed. Resolution: kept the new test from main, preserved our renamed test description ('should always show mobile command universe command'). 3. New test references to old name: PR #4124 added a test that referenced 'openGestureCheatsheet' in its description and assertion. Updated both to 'openMobileCommandUniverse'. Also fixed pre-existing InstalledClock type in testTimer.ts (from @sinonjs/fake-timers bump on main). Agent-Logs-Url: https://github.com/cybersemics/em/sessions/83e897cb-e762-482e-8483-e6e47d7e37e0 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
…dClock removed in v15) The merge from main incorrectly kept our branch's InstalledClock import instead of main's Clock import. @sinonjs/fake-timers v15.3.2 ships bundled types that unify Clock and InstalledClock into a single Clock type. Agent-Logs-Url: https://github.com/cybersemics/em/sessions/39152e75-7dd4-43d3-8c5b-cf029b56d127 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
…erse" label Agent-Logs-Url: https://github.com/cybersemics/em/sessions/24f3c5cb-9f44-41d2-83d8-751c0653d365 Co-authored-by: fbmcipher <16063438+fbmcipher@users.noreply.github.com>
and delete `MobileCommandUniverseIcon` (previously `GestureCheatsheetIcon` which was just a duplicate of "New Subthought" icon)
for consistency with `openMobileCommandUniverse`
Introduces a floating DEBUG picker (bottom-right) that toggles individual decorative layers (BackgroundGlow, Highlight, Rainbow, GlassStroke, clipped variants, ContainerBackground, Content, Gradient) and exposes live sliders for tuning the container background gradient — hue in/out, saturation/lightness, alpha, midpoint, center, and radius. Picker minimizes to a compact DEBUG button and fades while dragging so the slider's effect stays visible. Also extracts ContainerBackground into its own masked/parameterized layer (moved out of the recipe), pins the unclipped Highlight to the viewport top, and guards the picker against the dialog's click-outside and touchmove handlers.
Replace the dialog recipe with a layered composition of decorative elements (background glow, highlights, glass stroke, container fill) to create a refractive, mist-like glass effect. Adds dedicated color tokens for the glass fills, strokes, mask falloff, and scroll shadow so values aren't sidestepped via JS variables. AVIF assets back the highlight/rainbow/background-glow layers.
The decorative AVIFs are several MB and are otherwise fetched only when the dialog first opens, causing visible lag. Mount hidden divs at the MobileCommandUniverse level (always rendered) so the browser fetches and caches them at app boot. Same workaround as CommandCenter's HiddenOverlay.
Allow grid cells to shrink past their natural min-width and wrap labels onto a second line so the two-column command grid fits inside the new 87.5%-wide / 500px-max Dialog. Drop the sticky section header (intentional in the new design — replaced by the dialog's scroll-edge gradient as the scroll affordance).
The default 'bg' token rendered translucent against the new glass dialog, causing the menu to read as washed out. Switch to 'darkgray' for a more solid surface that holds up against the glass backdrop.
The highlight and rainbow AVIFs were exported with Matrix Coefficients set to 0 (Identity / RGB-as-YUV), bypassing chroma decorrelation and killing AVIF's compression efficiency. Re-encode at 12-bit YUV444 with BT.709 primaries / sRGB transfer / BT.601 YUV matrix (matching the already-correct background-glow asset) and strip embedded XMP/Exif. Sizes drop from 1.4 MB → 34 KB (highlight) and 2.5 MB → 22 KB (rainbow), eliminating the multi-second decode hitch on first open. Bit depth is preserved so smooth gradients won't band at low opacity.
Define the ContainerBackground and GlassStroke mask radial gradients
as CSS variables in panda.config.ts globalCss, matching the existing
--active-glow-gradient pattern. Register dialogContainerBg as a
panda gradients token so ContainerBackground can use
backgroundGradient: 'dialogContainerBg'. The mask gradient is
referenced via var(--dialog-glass-stroke-mask) since panda's
{gradients.X} interpolation isn't a safe mapping for maskImage.
Move all dialog styling — Dialog, DialogTitle, DialogContent — into a single slot recipe, following the panelCommandRecipe pattern of using literals throughout and co-locating coupled values rather than introducing CSS variables. The previously-shared scrollGradient height (48) and contentBottomSpacer height (24) now sit five lines apart in the recipe with comments noting the relationship, replacing the GRADIENT_HEIGHT / CONTENT_BOTTOM_SPACER_HEIGHT constants that had to be kept in sync across files. The radial gradient strings move inline into their slots, removing the --dialog-container-bg-gradient and --dialog-glass-stroke-mask CSS variables and the dialogContainerBg gradient token from panda.config.ts. Components shrink to JSX + slot class refs; total dialog code drops by ~80 lines net.
Each div now carries a one-line comment naming its visual purpose, since the recipe-slot indirection makes the layered structure harder to scan at a glance than the previous named FCs.
Each slot now carries a JSDoc-style header naming what the visual layer does, mirroring the per-div comments in Dialog.tsx so the recipe reads as a labeled inventory of the dialog's parts. Also fix an inadvertent 'bottom center' on highlightClipped that should match highlightUnclipped's 'top center'.
The previous "Adjust Commands grid layout for narrower Dialog" commit removed `contain: 'layout paint'` from the CommandsGroup wrapper along with the sticky title, since the comment attributed the containment to the sticky-title workaround. But CSS containment also isolates layout and paint independently of position:sticky, and removing it produced subpixel rendering diffs in the table view used by CustomizeToolbar (81-pixel modal-customizeToolbar snapshot regression). Restore it.
# Conflicts: # src/components/dialog/MobileCommandUniverse.tsx
|
Question please I would prefer to match with mock up to match the blur background.
Command Universe fits all screens small, mid and large (iOS) without any cut off as it is on No other issue from my end. Tested on
|
|
Thank you for the speedy review @BayuAri! The buttons and search bar will be redesigned in the next issue #4185, so I made the decision to leave them until that task. Do you think that's acceptable @BayuAri @raineorshine? @raineorshine: I'll push a commit tonight to reinstate the missing scroll gradient. |
Similar visual effect, but we don't need to overlay a black gradient anymore, and we get to remove a `div`.
|
The code was still there, but for whatever reason the gradient didn't show up. Replaced the
Also @raineorshine: is it expected behaviour for unavailable commands to show up dimmed? In this screenshot, the titles for each command are dimmed because they're not available in the current context ( |
It's still not working for me on Mobile Safari. It cuts off sharply. Are you seeing something different? Tested 73cd690.
The commands should be dimmed on desktop only. I'll create an issue for that! |
The `glassSheet` parent caps at `80dvh` (dynamic viewport — shrinks when iOS chrome shows). The scrollable `content` capped at `70vh` (static viewport — equals the largest possible). When the iOS address bar is visible, `70vh` resolves larger than `glassSheet`'s available budget, so `content`'s bottom edge — including the scroll-fade mask — gets clipped off by `glassSheet`'s `overflow: hidden`. Matching units keeps both heights tracking the same viewport.
Fixed now @raineorshine – it was working in Safari desktop, but it wasn't showing on Mobile Safari due to a I also took the opportunity to add a corresponding top scroll mask in a277e78 (right), which uses the new animation-timeline API to hide/show the mask as the user scrolls with zero JS. |
|
I can see a bit of a mask on the bottom, but it's still cutting off too sharply. Below I have positioned the bottom edge so that it cuts through Jump Back/Jump Forward. Instead of fading all the way to black like in < Aside: I think we will want to lighten the bottom edge of the modal, as it is disappearing into the background a little too much. |
|
Sorry for the back-and-forth! Could you eyeball the image on the far right (which represents latest commit 4c68180) and let me know if that's what you're looking for? In that image, I've slightly brightened the dialog, and made sure the mask properly fades to zero like in
|
|
That looks good. Now we just need to hide the mask when we are at the bottom of the document. Since it's a scroll hint, it should only be shown there is more content to scroll to. |
Complements the existing top-edge fade with a bottom-edge fade that collapses into the static tail as the user reaches the bottom of the scrollable content, removing the scrollability cue once there is nothing left to scroll to. Both fades are driven by `animation-timeline: scroll(self)` via the multi-animation shorthand. The new `--dialog-content-mask-fade-bottom` custom property uses a hardcoded 60px initial value because the CSS Properties & Values API requires a computationally independent length (rem isn't allowed).
|
@raineorshine Done 52131a7. Sorry for missing that before. It uses the same |








Closes #3943
Dialogcomponent #3943 (blurred background, rainbow refractions, and glass sheet effect)dialogRecipeslot recipe. This allows coupled values to live next to each other in the recipe