Origin: the UX redesign that followed #1510. The original squeeze-chord proposal solved discoverability one gesture at a time, but the resulting grammar was still fragmented. This issue replaces it with the two-layer model we arrived at after prototyping the interactions on the real 240×320 RGB222 rendering path.
Prototype branch: prototype/two-drawer-ui-grammar
Prototype commit: 4d36ff8
The branch is a behavioral reference, not a visual specification. Its layout, icon weight, spacing, typography, and final animation curves are intentionally rough. It exists so the interaction can be exercised in the simulator before production architecture and visual polish are committed.
Goal
Give the four-button UI one small, predictable grammar that keeps frequent actions immediate while making secondary actions and settings easy to find:
- ordinary taps operate the current screen;
- Select holds remain local, explicit object/direct-manipulation actions;
- Back hold always escapes to the main menu;
- the upper squeeze opens device-wide controls from the top;
- the lower squeeze opens screen-specific actions and settings from the bottom.
A rider should not need to remember a collection of unrelated hidden gestures, and a screen-specific setting should have one obvious home rather than also appearing deep inside a central settings tree.
Interaction grammar
Always true
| Input |
Meaning |
| Select tap |
Primary action / confirm on the current screen |
| Back tap |
Back / cancel on ordinary screens |
| Back hold |
Go directly to the main menu from anywhere |
| Select hold |
Local action on the focused object or direct manipulation mode; never the generic way to open a menu |
| Up + Select |
Open the universal quick drawer from the top |
| Down + Back |
Open the current screen's contextual drawer from the bottom, when that screen declares one |
| Up + Down |
Reserved |
| Select + Back |
Reserved; shutdown no longer gets a hidden chord |
Object-specific Select holds stay direct. “Forget phone” on the paired phone is the canonical example: burying it in a generic menu would make it less discoverable, not more. The same principle may justify keeping map zoom/pan direct if hardware testing shows that the extra drawer step is too costly.
Riding exception
The two most frequent ride interactions remain one-tap actions with no holds or menus:
- Select tap pauses/resumes the ride.
- Back tap switches ride views.
Secondary ride actions such as Up ahead and Detour move into the contextual drawer. The existing ride compass menu is removed rather than retained as a third navigation grammar.
Universal quick drawer
Up+Select opens a compact sheet that animates down from the top. It is available globally except where a genuinely blocking safety/modal state must suppress all unrelated input.
The root contains exactly four icon-only buttons in the current prototype order:
- lightbulb — brightness;
- Bluetooth rune — BLE on/off;
- gear — central settings;
- universal power symbol — shutdown.
Do not add labels below these four icons. These symbols are established enough to be clearer and calmer without them. Selection/focus and BLE state still need unambiguous palette-safe visual treatment.
Behavior:
- Brightness: opens a nested value editor with a small number of discrete levels. Up/Down previews, Select commits, Back cancels. Production wiring needs a real backlight platform port and persistence policy.
- BLE: toggles
Settings::ble_enabled immediately, persists through the existing settings path, and reaches the board's existing radio-enable seam. The icon and state indicator update immediately.
- Settings: replaces the sheet with the existing central settings entry point. Central settings retain truly device-wide configuration, not screen-specific modifiers.
- Power: opens a clearly distinct confirmation page. A tap must never shut down. Hold Select fills the established guarded-action progress bar; Back or a tap cancels. Completion invokes a platform power-off port (system-off on hardware; a deliberate simulator behavior).
Nested pages transition horizontally while the containing sheet adapts its height. Back returns to the icon row without closing the underlying screen.
Contextual drawer
Down+Back opens an adaptive-height sheet from the bottom only on screens that actually have secondary actions or modifiers. Unsupported screens do nothing; they must not show an empty drawer.
Examples already explored in the prototype:
- ride views: Up ahead, Detour, POIs, Routes, Map display;
- Up ahead: POI filter and source selection;
- route planning from the POI flow: bike type and route options;
- weather screens: refresh and weather-specific settings;
- map: contextual display modifiers such as clock and scale-bar visibility.
The drawer is the only home for contextual settings. Migrate those settings out of the central settings tree rather than duplicating them. Truly global settings such as language and units remain central. Detailed Bluetooth pairing/device management can remain a settings screen even though the quick drawer owns the radio toggle.
Rows follow the normal Up/Down + Select + Back grammar. Values that need editing open a horizontal nested page:
- Up/Down changes the staged value;
- Select commits and returns to the drawer root;
- Back discards the staged value and returns;
- the committed value remains visibly marked while browsing alternatives.
The screen should provide declarative context content/state; the generic drawer owns navigation, transitions, commit/cancel behavior, and rendering. Avoid screen-specific drawer implementations.
Layering and rendering
Both drawers visually separate themselves from the current screen using the manually tuned device-64 dim LUT proven in the prototype. No alpha blending or expanded color format is needed.
Production behavior should freeze the background frame when a drawer opens:
- render/capture the current base once through the dim LUT;
- stop map, weather, and other base-screen visual updates while the drawer is active;
- animate and update only the drawer/hold overlay regions;
- invalidate the base once when the drawer closes.
This is especially important for map performance. Use the existing overlay/present damage machinery and verify the actual device transfer cost; the prototype currently favors iteration speed over an optimized frozen-base implementation.
Sheets should normally remain visibly attached to their edge and use only the height their content requires. If content would consume almost the whole display, prefer a bounded sheet with coherent scrolling before automatically turning every drawer into a full-screen page. Final geometry and visual styling require a separate tuning pass on hardware.
Production architecture
The prototype uses simulator/debug entry points. Production work must:
- recognize both chords in the global input plane before screen dispatch;
- swallow all constituent steps, taps, holds, and releases so opening a drawer never also changes the underlying screen or activates its first row;
- decide whether a short chord window/step deferral is needed after hardware testing;
- route Back hold globally to the main menu even from drawer subpages;
- add a small declarative per-screen context model/capability;
- keep the two drawers mutually exclusive and well-defined over existing overlays/modals;
- add real brightness and power platform ports;
- preserve settings dirty/persistence semantics for drawer edits;
- remove migrated contextual rows and the old ride compass menu once their replacements exist;
- localize all drawer row labels, editor text, and confirmation copy.
Do not carry the prototype's debug names, hard-coded English rows, or temporary brightness state into the final architecture.
Prototype coverage
The linked branch already demonstrates:
- real 240×320 RGB222 rendering;
- adaptive top and bottom sheet animations;
- the device-64 dim LUT;
- mutually exclusive simulator chords;
- nested POI-filter, bike-type, and brightness editors with commit/cancel behavior;
- Up-ahead filter state applied back to the underlying screen;
- a real persisted BLE toggle;
- guarded power confirmation and in-progress hold rendering;
- deterministic headless screenshot tokens and captured PNG/GIF design states under
design/context-drawer/ and design/quick-drawer/.
Run it with:
cargo run -p obc-sim --release -- apps/obc-sim/assets/grimsel-demo.obcm
Use the physical controls or keyboard equivalents:
- Left Arrow + Enter: top quick drawer;
- Right Arrow + Backspace: bottom contextual drawer;
- Left/Right: previous/next;
- Enter: Select;
- Backspace: Back.
Acceptance criteria
- The grammar above is consistent across the complete screen inventory, with intentional exceptions documented.
- Back hold always reaches the main menu.
- Ride pause/resume and ride-view switching remain one tap.
- Object-specific Select holds such as Forget phone remain direct.
- Quick controls work from every eligible screen and never leak constituent gestures.
- The contextual drawer exists only where content is declared.
- Every contextual setting has one home; migrated settings disappear from central settings.
- The ride compass menu is removed.
- BLE toggles the real radio setting; brightness and shutdown reach real platform ports.
- Power always requires explicit guarded confirmation and cancels cleanly.
- Drawer entry freezes/dims the base, and animation/redraw cost is measured on hardware—especially over the map.
- All UI fits the 240×320 display and RGB222 palette in every supported language.
- The final visual design is reviewed on simulator captures and physical hardware; the prototype styling is not accepted by default.
Verification
- Input-plane unit tests for both chord orders/timings, constituent-event swallowing, release suppression, and unchanged ordinary taps/holds/steps.
- App tests for global Back hold, drawer mutual exclusion, supported/unsupported contexts, commit/cancel, settings persistence, and guarded power.
- Overlay/present tests proving the frozen base and bounded drawer damage.
- Headless renders over map, ride, Up ahead, route planning, weather, settings, and representative modal states in every language.
- Device profiling of open/close animation transfer time and redraw regions.
obc test -p obc-app, obc test -p obc-sim, strict clippy, and affected board checks once platform ports are introduced.
Origin: the UX redesign that followed #1510. The original squeeze-chord proposal solved discoverability one gesture at a time, but the resulting grammar was still fragmented. This issue replaces it with the two-layer model we arrived at after prototyping the interactions on the real 240×320 RGB222 rendering path.
Prototype branch:
prototype/two-drawer-ui-grammarPrototype commit:
4d36ff8The branch is a behavioral reference, not a visual specification. Its layout, icon weight, spacing, typography, and final animation curves are intentionally rough. It exists so the interaction can be exercised in the simulator before production architecture and visual polish are committed.
Goal
Give the four-button UI one small, predictable grammar that keeps frequent actions immediate while making secondary actions and settings easy to find:
A rider should not need to remember a collection of unrelated hidden gestures, and a screen-specific setting should have one obvious home rather than also appearing deep inside a central settings tree.
Interaction grammar
Always true
Object-specific Select holds stay direct. “Forget phone” on the paired phone is the canonical example: burying it in a generic menu would make it less discoverable, not more. The same principle may justify keeping map zoom/pan direct if hardware testing shows that the extra drawer step is too costly.
Riding exception
The two most frequent ride interactions remain one-tap actions with no holds or menus:
Secondary ride actions such as Up ahead and Detour move into the contextual drawer. The existing ride compass menu is removed rather than retained as a third navigation grammar.
Universal quick drawer
Up+Select opens a compact sheet that animates down from the top. It is available globally except where a genuinely blocking safety/modal state must suppress all unrelated input.
The root contains exactly four icon-only buttons in the current prototype order:
Do not add labels below these four icons. These symbols are established enough to be clearer and calmer without them. Selection/focus and BLE state still need unambiguous palette-safe visual treatment.
Behavior:
Settings::ble_enabledimmediately, persists through the existing settings path, and reaches the board's existing radio-enable seam. The icon and state indicator update immediately.Nested pages transition horizontally while the containing sheet adapts its height. Back returns to the icon row without closing the underlying screen.
Contextual drawer
Down+Back opens an adaptive-height sheet from the bottom only on screens that actually have secondary actions or modifiers. Unsupported screens do nothing; they must not show an empty drawer.
Examples already explored in the prototype:
The drawer is the only home for contextual settings. Migrate those settings out of the central settings tree rather than duplicating them. Truly global settings such as language and units remain central. Detailed Bluetooth pairing/device management can remain a settings screen even though the quick drawer owns the radio toggle.
Rows follow the normal Up/Down + Select + Back grammar. Values that need editing open a horizontal nested page:
The screen should provide declarative context content/state; the generic drawer owns navigation, transitions, commit/cancel behavior, and rendering. Avoid screen-specific drawer implementations.
Layering and rendering
Both drawers visually separate themselves from the current screen using the manually tuned device-64 dim LUT proven in the prototype. No alpha blending or expanded color format is needed.
Production behavior should freeze the background frame when a drawer opens:
This is especially important for map performance. Use the existing overlay/present damage machinery and verify the actual device transfer cost; the prototype currently favors iteration speed over an optimized frozen-base implementation.
Sheets should normally remain visibly attached to their edge and use only the height their content requires. If content would consume almost the whole display, prefer a bounded sheet with coherent scrolling before automatically turning every drawer into a full-screen page. Final geometry and visual styling require a separate tuning pass on hardware.
Production architecture
The prototype uses simulator/debug entry points. Production work must:
Do not carry the prototype's debug names, hard-coded English rows, or temporary brightness state into the final architecture.
Prototype coverage
The linked branch already demonstrates:
design/context-drawer/anddesign/quick-drawer/.Run it with:
Use the physical controls or keyboard equivalents:
Acceptance criteria
Verification
obc test -p obc-app,obc test -p obc-sim, strict clippy, and affected board checks once platform ports are introduced.