Skip to content

Glassy modal for mobile Command Universe#4183

Merged
raineorshine merged 29 commits into
mainfrom
fbmcipher/issue-3943
May 27, 2026
Merged

Glassy modal for mobile Command Universe#4183
raineorshine merged 29 commits into
mainfrom
fbmcipher/issue-3943

Conversation

@fbmcipher
Copy link
Copy Markdown
Collaborator

Closes #3943

CleanShot 2026-04-26 at 02 13 29@2x
  • Implements the liminal UI redesign for the modal — adds all layers from Liminal glass overhaul for Dialog component #3943 (blurred background, rainbow refractions, and glass sheet effect)
  • Responsive across mobile sizes, portrait/landscape, iPhone + Android
  • Styles consolidated into a single dialogRecipe slot recipe. This allows coupled values to live next to each other in the recipe

Copilot AI and others added 24 commits April 6, 2026 02:18
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
Copy link
Copy Markdown
Contributor

@raineorshine raineorshine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Code is approved. I did a brief test on desktop and will let @BayuAri do more thorough mobile testing.

@BayuAri This is only a visual change, so just look out for visual glitches or responsive design issues.

@raineorshine
Copy link
Copy Markdown
Contributor

I think we lost the bottom scroll gradient.

Tested in Chrome Device Emulator.

Current

f00044e

image

Expected

main

image

@BayuAri
Copy link
Copy Markdown
Collaborator

BayuAri commented Apr 28, 2026

@raineorshine @fbmcipher

Question please
Search field, Sort and X button have black background on the current built but it is different with the mock up, is this acceptable?

I would prefer to match with mock up to match the blur background.

image

Command Universe fits all screens small, mid and large (iOS) without any cut off as it is on main

No other issue from my end.

Tested on

  • iPhone 13 Mini iOS 18.7.6
  • iPhone 16e iOS 18.7.8
  • iPhone 14 PM iOS 26.2.1
  • Samsung A56 OS 16

@fbmcipher
Copy link
Copy Markdown
Collaborator Author

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`.
@fbmcipher
Copy link
Copy Markdown
Collaborator Author

fbmcipher commented Apr 28, 2026

The code was still there, but for whatever reason the gradient didn't show up. Replaced the div gradient approach with a mask, which has the same effect:

CleanShot 2026-04-28 at 23 19 51@2x

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 (isExecutable is false).

@raineorshine
Copy link
Copy Markdown
Contributor

The code was still there, but for whatever reason the gradient didn't show up. Replaced the div gradient approach with a mask, which has the same effect:

It's still not working for me on Mobile Safari. It cuts off sharply. Are you seeing something different? Tested 73cd690.

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 (isExecutable is false).

The commands should be dimmed on desktop only. I'll create an issue for that!

fbmcipher added 2 commits May 3, 2026 02:52
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.
@fbmcipher
Copy link
Copy Markdown
Collaborator Author

CleanShot 2026-05-03 at 03 27 14@2x

Fixed now @raineorshine – it was working in Safari desktop, but it wasn't showing on Mobile Safari due to a dvh/vh viewport sizing disparity. Fixed fc1ecae (left)

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.

@raineorshine
Copy link
Copy Markdown
Contributor

raineorshine commented May 6, 2026

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 main, it is only partially masked before being cut off by the bottom edge of the modal.

< main | a277e78 >

image image

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.

@fbmcipher
Copy link
Copy Markdown
Collaborator Author

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 main.

CleanShot 2026-05-22 at 00 20 36@2x

@raineorshine
Copy link
Copy Markdown
Contributor

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).
@fbmcipher
Copy link
Copy Markdown
Collaborator Author

@raineorshine Done 52131a7. Sorry for missing that before. It uses the same animation-timeline-based mechanism as the top mask, so no JS needed.

Copy link
Copy Markdown
Contributor

@raineorshine raineorshine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks

@raineorshine raineorshine merged commit f6f5d35 into main May 27, 2026
10 checks passed
@raineorshine raineorshine deleted the fbmcipher/issue-3943 branch May 27, 2026 18:41
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.

Liminal glass overhaul for Dialog component

4 participants