webOS polish review — 24 September 2026
An informal comparative review requested outside the OpenSpec workflow: how does
the installed handheld shell compare with Palm/HP webOS’s visual craft, and
where is ours less than perfectly polished. This is not the formal
the-handheld-gets-a-design-and-ux-review change (active, owned by a separate
worktree, with its own rubric and docs/research/handheld-ux/review-round-2/
artifacts) and does not duplicate its scope or task IDs. It also does not
replace docs/design/handheld-shell/visual-gap-audit.md
(24 September, same day) — it corrects one of that audit’s framings (below),
adds webOS-specific reference points with citations, and adds several
code-grounded findings that audit did not cover. A coordinator folding this
into the formal review round should treat it as raw input for
references-and-gap.md and findings.md, not a finished replacement.
No board or /dev/ttyACM0 access was used. Every finding below is grounded in
either a committed capture under docs/evidence/ (with its date and source
commit checked against git log and git merge-base) or the current
origin/master source tree read-only. No source file was edited to produce
this review.
0. A correction: which deck chrome is “ours” right now
Several evidence directories show three different generations of the live card deck’s title/status chrome, and conflating them changes the review’s priorities:
- Oldest — a permanent top tab bar (
Apps / Windows / Keyboard / System) plusPrevious / Next / Back / Closebuttons. Visible indocs/evidence/launcher-gestures/edge-states/*.pnganddocs/evidence/card-shell/board-first-trial/*.png. This is exactly the permanent Back/Home/pagination chrome the user has already rejected; it survives only as inert legacy code, never the default. - Middle —
"Cards"title,"Tap to return. Swipe up to close."subtitle, and a"Selected: …"-prefixed card label, withPrevious / Next / Closebuttons. This is the rollback session (SWAY_K230_CARD_TOUCH_FIRSTunset/0), defined innix/card-shell/adapter.c:773-834(rebuild_chrome, else-branch) andnix/card-shell-policy/card-shell-policy.c:692-707(cs_message_text, whose strings still say “Use Windows or Back” — leftover text for a bar that mode doesn’t draw, but is at least never shown outside the rollback path). It appears indocs/evidence/coherent-shell/real-theme-paired-qemu/*,docs/evidence/coherent-shell/themed-installed/deck.pnganddocs/evidence/omarchy-themes/board-switching/*.png. - Current default —
"Home"title, a status cue only when relevant, and"Swipe up for apps", with no buttons at all. This isnix/card-shell/adapter.c:786-808(rebuild_chrome,touch_first()branch).nix/shell.nix:830-863setsSWAY_K230_CARD_TOUCH_FIRST = "1"unconditionally whenevercfg.coherentShellis enabled — i.e. this is the shipped path, not an opt-in extra. Confirmed reachable indocs/evidence/omarchy-themes/community-board-fixed/community.png(source3c502a37, an ancestor of currentorigin/mastera68014de), where the deck’s top strip peeking above the drawer reads"Home", not"Cards".
Generation 2 predates generation 3 by a handful of commits the same day
(git merge-base --is-ancestor confirms f4952fc2 “Quiet touch-first deck
chrome and card labels” — which introduced generation 3 — is an ancestor of
3c502a37, while board-switching’s source df2c34b1 and
real-theme-paired-qemu’s source predate f4952fc2). Practical effect:
visual-gap-audit.md’s P0 finding #1 (“oversized generic Cards title,
procedural subtitle, Selected: shell@… line”) was written against
generation 2 evidence. That specific title/subtitle text is already gone from
the shipped default. The underlying problem — an unbounded raw client window
title used as the card’s only identity — is not fixed, just relabeled; see
finding P0-1 below, re-scoped to the actual current code path.
Recommendation for evidence hygiene (P2, process not code): recapture the deck
under an explicitly touch-first build before relying on any deck screenshot in
future polish reviews, and consider whether generations 1–2 should be pruned
once the physical acceptance gates for generation 3 (openspec tasks 5.1–5.3 of
the-handheld-presents-a-coherent-shell) land, per AGENTS.md’s “close
deliberately” guidance.
1. What ours actually looks like today
Evidence below is all dated 24 September 2026 (today) and each source commit
was checked with git merge-base --is-ancestor <source> a68014de against the
current origin/master head; captures whose source is not an ancestor of
master are marked so and treated as historical/branch-local, not “current.”
| Surface | Best current capture | Source commit | On master? |
|---|---|---|---|
| Deck (touch-first, peeking above drawer) | docs/evidence/omarchy-themes/community-board-fixed/community.png | 3c502a37 | yes |
| Drawer (icon grid, real board, dark) | same capture | 3c502a37 | yes |
| Drawer (icon grid, synthetic, dark/light) | docs/evidence/omarchy-themes/board-switching/{dark,light}.png | df2c34b1 | yes (predates the “Home” quieting, deck strip is stale — drawer itself is current) |
| Settings / Themes / Theme preview / Shade (host, dark+Latte) | docs/evidence/coherent-shell/rust-visual-themes-host/{dark,latte}/*.png | f9978fe6 | not checked as ancestor of a68014de (divergent branch state); Rust chrome code for these routes is unconditional (no touch_first gate) so the screens themselves are representative even if the exact commit isn’t on master |
| Theme preview with a real decoded still | docs/evidence/omarchy-themes/portrait-preview-host/preview.png, docs/evidence/omarchy-themes/theme-preview-host/catppuccin-preview.png | host fixture | code path confirmed live in render.rs:405-520 |
| Wi-Fi list / detail / password entry | docs/evidence/wifi-settings/paired-qemu/*.png | paired QEMU, same day | representative (nix/rust-shell-client/src/wifi_ui.rs/render.rs paint_wifi, unconditional) |
| Notification shade with many items, scrolled | docs/evidence/coherent-shell/notification-history-motion-qemu/{initial,scroll-stable}.png | 0a414434 | representative |
| App icons in the drawer | docs/evidence/coherent-shell/rust-icons-host.png | host fixture | representative (SVG-based decode, see §2) |
| On-screen keyboard | docs/evidence/keyboard-gestures/{native-qemu,supervised-installed}/*.png | same day | representative |
2. Where ours is not yet polished
Findings are ordered P0 (obviously broken or ugly) → P1 (noticeably unpolished) → P2 (refinement). Each names the screen/capture, the webOS reference point, the concrete fix, and the code location.
P0-1 — Card header shows the raw client window title, not the app’s name
Screen: live card / expanded app header, all themes.
Capture: docs/evidence/coherent-shell/themed-installed/deck.png (shows
"Selected: shell@nixos: ~" — generation-2 chrome, but the underlying title
source is identical in the current generation-3 path, minus the "Selected: "
prefix).
Code: nix/card-shell/adapter.c:364-376 (card_display_title) maps only
three hardcoded app_id strings to friendly names —
k230-terminal→"Terminal", k230-monitor→"Monitor", nnn→"Files" — and
falls through to the client’s own title (e.g. a shell prompt like
shell@nixos: ~, which changes as the user types) for every other app. That
raw string is then squeezed into a fixed-width, ellipsized label at
adapter.c:709-716 (label_update, height 44/font 24 in the current compact
mode).
webOS reference: a card’s header always showed the app’s name paired
with its icon (from the app’s manifest), never the live content of the window
underneath — a terminal card read “Terminal,” not whatever the shell prompt
currently said.
Fix: resolve the card header the same way the drawer already resolves
.desktop entries (task 1.4 of the-handheld-presents-a-coherent-shell) —
i.e. app_id → .desktop Name/Icon — instead of a 3-entry strcmp allowlist,
and put a small icon glyph next to the name in the header (matching the
drawer’s icon+label pattern), so an app whose app_id isn’t one of the three
special-cased strings doesn’t silently regress to raw window-title text. This
is inside the existing open scope of tasks 1.4/4.2; call it out explicitly
when picked up.
P0-2 — Secondary panels fill the full screen height regardless of content, leaving large dead voids
Screens: Settings, Themes list, Theme preview, Wi-Fi (list/detail/error),
Notifications shade.
Captures: docs/evidence/coherent-shell/rust-visual-themes-host/dark/settings.png
(content ends ~35% down, remaining ~65% flat #1e1e2e-ish fill),
.../dark/themes.png (two rows of content, ~90% of the panel empty),
docs/evidence/omarchy-themes/theme-preview-host/catppuccin-preview.png (one
row of content below the preview image, ~55% empty),
docs/evidence/wifi-settings/paired-qemu/wifi-list-dark.png (four rows, ~55%
empty), docs/evidence/coherent-shell/notification-history-motion-qemu/scroll-stable.png
(even scrolled mid-way through 12 real rows, the area below the shade’s own
65%-height cap is a flat, undifferentiated dark fill all the way to the
bottom edge, not a dimmed peek of anything).
Code: nix/rust-shell-client/src/render.rs:1061-1070:
let panel_y = if route == Route::Drawer { h * 0.19 } else { 0.0 };
let panel_h = if route == Route::Shade { h * 0.65 } else { h - panel_y };
Every non-Shade route (Drawer, Settings) always fills the entire
remaining height with the panel background, no matter how short its content
list is; Shade is capped at a fixed 65% regardless of content too. There is
no “size to content, cap at a maximum, show the deck behind for the rest”
behavior anywhere in this function.
webOS reference: webOS’s Dashboard/notification area and the app-menu
overlays were always sized to their content, floating over the (dimmed) card
stack — never a full-bleed sheet of empty color. The archived HP webOS
developer notification guide describes brief banners followed by a
content-sized dashboard, not a full-screen panel.
Fix: compute panel_h from actual content extent (row count × row height
- chrome), clamped to a sane minimum and maximum, and use the freed space to
show the deck/wallpaper behind, dimmed, consistent with what the
Shaderoute already gestures at with its 65% constant. This single function change improves every secondary screen in the shell simultaneously — it is the highest-leverage fix in this review.
P0-3 — Wi-Fi failed-password state buries the error and reuses the connect keyboard’s dead space
Screen: Wi-Fi password entry after a rejected password.
Capture: docs/evidence/wifi-settings/paired-qemu/wifi-auth-error-dark.png.
Observation: "Password was not accepted. Check it and try again." sits
in small red text wedged between the numeric row and the Cancel/Connect
buttons, while ~330px of vertical space between the password field and the
keyboard (visible in the sibling wifi-masked-dark.png, same layout) stays
empty. This is the same root cause as P0-2 (panel/keyboard region not sized to
its actual content) but worth calling out separately because it’s the one
place an error gets the same “buried in dead space” treatment as a merely
empty list — a failed action deserves more visual weight, not the same
treatment as nothing-to-show.
Code: nix/rust-shell-client/src/render.rs:682-1034 (paint_wifi);
search for the error-message text(...) call near the connect button.
Fix: once P0-2’s panel-sizing fix lands, give the error message its own
elevated treatment (icon + border-accented banner directly under the field it
refers to) rather than a single line of colored text lost in a large blank
keyboard-adjacent region.
P0-4 — Settings capability-row spacing is hand-typed and visibly uneven
Screens: Settings, every theme, every capture that reaches this screen
with real content.
Captures: docs/evidence/coherent-shell/rust-visual-themes-host/dark/settings.png,
.../latte/settings.png (placeholder “Unavailable” rows), and
docs/evidence/wifi-settings/paired-qemu/wifi-settings-dark.png (same
layout, populated Wi-Fi/Brightness/Keyboard/Motion rows) — three independent
captures, two themes, two content states, same visible defect.
Code: nix/rust-shell-client/src/render.rs, Route::Settings branch
(~lines 1497–1503) positions the four capability cards at literal
y = 162.0, 326.0, 452.0, 590.0 for a fixed 110px card height. That produces
gaps of 54px after the first card, 16px after the second, and 28px after the
third — three different gap values in one four-item list, visible in every
capture regardless of theme or which rows have content. This is independent
of P0-2’s panel-height problem: even once the panel is sized to content,
these four y values stay wrong relative to each other.
webOS reference: the O’Reilly Palm webOS developer text describes
dashboard/list chrome as fixed-height, content-sized strips (§3) — the
implication for any list-of-cards UI in the same visual language is a single
consistent row rhythm, not per-row hand-tuned offsets.
Fix: replace the four literal constants with a computed series — card
height (110) + one fixed gap (e.g. 16) ⇒ y = 162.0, 288.0, 414.0, 540.0 —
and update the two positions computed relative to the old constants in the
same block: the Brightness −/+ control text (currently w - 162.0, 366.0, tied to the old y=326) and the “Power” heading/Reboot/Power-off cards
(currently 722.0/750.0/828.0, tied to the old y=590 end). Cleanest fix is a
shared row(index) helper feeding all of these instead of five independent
literals.
Evidence class: pure arithmetic, verifiable by direct source read; no
board access needed to confirm the bug or the fix.
P1-1 — Two renderers, two unrelated font declarations, no shared source of truth
Screens: every screen — the deck (C) sits directly adjacent to the
drawer/shade/settings (Rust) in the same composited frame.
Capture: docs/evidence/omarchy-themes/community-board-fixed/community.png
shows the deck’s "Home" title immediately above the drawer’s "All apps"
heading in one screenshot.
Code: nix/rust-shell-client/src/render.rs:171,194 hardcodes the literal
family "DejaVu Sans" for every Rust-drawn label, in exactly two weights
(pango::Weight::Normal / Bold, text()/heading() at lines 183-189).
nix/card-shell/render.c:112 requests the generic Pango alias "sans" for
every C-drawn label (deck title, status, button captions) — this is not
guaranteed to resolve to the same family/metrics as the Rust side; it depends
on fontconfig’s default sans mapping on the target image, which neither file
pins. Compare with the project’s own design-study target,
site/src/styles/handheld-design.css, which specifies a deliberate
three-family system: “IBM Plex Sans Condensed” for display headings, “IBM
Plex Mono” for section labels/timestamps/gesture hints, “IBM Plex Serif” for
body copy.
webOS reference: webOS used one named system font consistently from the
status bar through the launcher, card labels and Settings — “Prelude” from
2.x onward (an evolution from the earlier Cronos/slab-style 1.x face) — so
type never looked like “two different apps” even though webOS itself was
built from many independent Mojo “scenes.”
Fix: define one explicit font family as a shared constant (or read from
the same theme-token pipeline that already carries colors between C and
Rust), use it in both render.rs and render.c, and add a third weight tier
(a “medium”/“semibold” for section-eyebrow labels like "YOUR DEVICE" /
"Device controls") so headings, section labels and body read as one
designed hierarchy instead of two hand-rolled ones that happen to coexist.
P1-2 — Gesture-hint text has three different typographic treatments for the same job
Screens: Drawer footer, Shade subheading, Deck footer. Code / exact strings:
nix/rust-shell-client/src/render.rs:1232-1240—"SWIPE DOWN TO RETURN TO CARDS", all-caps, small tracked “kicker” style, centered.nix/rust-shell-client/src/render.rs:1246-1252—"Swipe up above the list to close", sentence case, left-aligned body style.nix/card-shell/adapter.c:802-807—"Swipe up for apps", sentence case, different margin/vertical-rhythm logic (computed fromcfg->height - top_reserved - bottom_reserved - 58, unrelated to the Rust side’s fixed offsets). webOS reference: webOS’s gesture area itself was the affordance (a persistent physical/capacitive strip with its own visual “wave” cue); textual coaching, where it appeared at all, was confined to first-run tips, not permanent on-screen chrome repeated on every visit. Fix: converge on one hint style (recommend sentence case, muted color, one consistent size, one consistent bottom margin formula) across all three call sites; longer term, consider showing each hint only until its gesture has been performed once (a small persisted flag), which also better matches both the webOS precedent and the user’s stated rejection of permanent chrome.
P1-3 — Theme preview’s palette/image treatment is visually thin relative to its own supporting content
Screens: Theme preview.
Captures: docs/evidence/omarchy-themes/theme-preview-host/catppuccin-preview.png
(good — real Totoro still renders correctly) vs.
docs/evidence/omarchy-themes/portrait-preview-host/preview.png (a themed
fixture with no decodable still falls back to a plain two-color split
rectangle captioned “Screen crop / Full-height preview” with no indication
that the split colors are a placeholder rather intentional imagery).
Code: nix/rust-shell-client/src/render.rs:405-520 (ThemePage::Preview
branch) — the preview_image Option correctly renders a real decoded still
when present (this works; earlier suspicion from visual-gap-audit.md that
the code “shows no image” was a host-fixture limitation, not a code gap —
worth stating positively). When there is no image, the code paints a plain
filled rounded rect with no visual cue that this is a fallback/error state
rather than a themed background.
Fix: give the no-image fallback a distinct visual treatment (diagonal
hatch, a small “no preview available” glyph) so it cannot be mistaken for an
intentional two-color wallpaper. P2 if the true no-image case is rare in
practice; kept at P1 here because “video unavailable” themes (visible in the
same screen, e.g. Fixture Night) hit this path in ordinary use, not just in
degraded fixtures.
P1-4 — Notification shade’s “preview” tile duplicates the history row directly beneath it
Screens: Shade, both a single-notification state and a many-notification
state.
Captures: docs/evidence/coherent-shell/rust-visual-themes-host/dark/shade.png
(one fixture notification: the preview tile and the first history row show
the identical source/summary text, “System / Connection needs attention”,
back to back) and docs/evidence/coherent-shell/notification-history-motion-qemu/initial.png
(12 notifications: the preview tile instead reads “No active preview”
immediately above a fully populated history list — adding no information the
list below doesn’t already show).
Code: nix/rust-shell-client/src/render.rs, Route::Shade branch, the
preview service_card (~lines 1265–1310) versus the history loop starting at
NOTIFICATION_TOP a few lines later (~1355 onward). Also in the same block:
the notification-count text is built as format!("{count} notifications")
(~line 1257) with no singular form, so a single notification literally reads
“1 notifications”.
webOS reference: a webOS banner (a temporary, ephemeral
below-the-app alert) and a dashboard (the persistent, content-sized
history/action panel) were deliberately two different objects with two
different lifetimes — a banner disappeared once acknowledged or superseded by
its dashboard entry; it never sat permanently stacked directly on top of that
same dashboard entry repeating the same text (§3, notifications).
Fix: either drop the preview tile whenever the shade is already showing
History (reserve it for a genuinely different, ongoing/ephemeral state not
yet in history), or suppress it specifically once its event is the top
history row so the two never say the same thing at once; separately, fix the
count string’s singular case.
Evidence class: host render + QEMU capture, both direct reads of current
code; no board access needed.
P1-5 — The live-content translucency fix landed for the Drawer only, not Shade or Settings
Screens: none yet observed with this defect on Shade/Settings — this is a
latent-risk finding, not a reproduced one.
Reference capture for the underlying failure mode: docs/evidence/coherent-shell/real-theme-paired-qemu/latte-drawer.png
(paired QEMU, source 14a2bdc6, predates the fix below) shows exactly this
class of bug on the Drawer: the community Latte theme’s authored
launcher.background alpha (0.95) let the ghost text Selected: Card one: accep… show through the drawer’s lower panel.
Code: nix/rust-shell-client/src/render.rs (~lines 1086–1097). The
shared panel_brush lookup (theme_brush(theme, section, "background"),
line 1086) is used by Route::Drawer, Route::Shade and Route::Settings
alike (section is "launcher"/"notifications"/"controls" respectively,
lines 1080–1084), but the opaque pre-fill that prevents ghosting
(if route == Route::Drawer { ... }, lines 1091–1097, landed same day as the
Drawer fix) guards only Route::Drawer. Any theme that authors
notifications.background or controls/menu.background with alpha below
1.0 would reproduce the identical ghosting on the shade or Settings panel;
nothing in the currently committed evidence set rules this out, since no
captured theme so far sets that token below 1.0 on those two sections.
webOS reference: HP’s Dashboard and Settings-equivalent surfaces used
consistently opaque system chrome regardless of which app or theme was
underneath (§3, notifications/status bar).
Fix: move the opaque pre-fill out of the route == Route::Drawer guard
so it covers Route::Drawer | Route::Shade | Route::Settings alike, ahead of
the shared panel_brush fill that follows it.
Evidence class: source-code inference from the same commit/file that
fixed the Drawer case; needs a community theme with alpha < 1.0 on
notifications/menu background tried on the board (or in QEMU) to become
a reproduced-and-fixed bug rather than a preventive one.
P2-1 — Corner-radius drift between the “panel” family and the preview image frame
Code: nix/rust-shell-client/src/render.rs:207-299 establishes card/tile
radius 16.0 (service_card, line 239) with its border stroke correctly
inset to 15.25 (concentric with a 1.5px stroke). The palette swatches use a
smaller, appropriately-scaled 12.0/11.25 for a 66×66 chip (line 434/437),
and the phone-bezel frame in the theme preview intentionally uses a tighter
6.0 (line 494) — these two are deliberate, proportional choices, not bugs.
The one true drift: the preview image panel itself uses 15.0
(lines 470, 483) where every other panel-scale element uses 16.0 — a 1px
inconsistency with no apparent reason. Converge it to 16.0.
P2-2 — Icon rendering is not actually a problem, correcting a plausible worry
Checked because the task brief called out “icon sizing and blurriness” as a
common polish issue to look for: nix/rust-shell-client/src/icon.rs resolves
icons through the installed icon theme’s index.theme metadata (score(),
lines 96-113) and decodes SVGs through librsvg at the exact requested size
with a proper Cairo scale() (lines 224-273), so icons stay vector-crisp at
whatever size they’re painted. docs/evidence/coherent-shell/rust-icons-host.png
confirms this visually — the Terminal/Monitor/Video icons are sharp, not
blurred or pixelated. No fix needed here; recorded so a future reviewer
doesn’t re-flag it without checking.
3. webOS reference points used above
Primary design-intent source already in this repository:
Palm Pre User Guide
(pp. 19–20, 23–26, 32–33 for card/launcher/scroll/notification behavior, per
docs/design/handheld-shell/motion-research.md) and the archived
HP webOS developer notification guide.
The rest of this section is new research for this review, done directly
against the web (not against Palm imagery reproduced here — no screenshot is
copied into this repository; every claim below cites its source instead).
Coverage is Palm/HP webOS 1.x–3.x on the Pre, Pixi, Veer and TouchPad, plus
LuneOS where it preserves the same interaction model. Numeric values are only
stated where a source gives them; everything else is described qualitatively
and marked as such.
Card view. webOS’s defining metaphor was a literal “deck of cards” for multitasking: each running app renders as a live, still-updating rectangular thumbnail (“card”) that can be rifled through left/right, tapped to maximize, or flicked upward off the top of the screen to close/kill the app — mimicking handling a physical deck of cards (PCWorld, Engadget Palm Pre review). Cards stayed live/updating while minimized, not frozen snapshots (PCWorld). A small round center button in the gesture area toggled between the current full-screen app and card view; pushing a card up but releasing before it left the screen produced a multi-card overview rather than closing it (PCWorld). Reviewer John Gruber, writing in 2017 with the benefit of hindsight, singled out the cards’ rounded-rectangle geometry and gesture model as design that “proved ahead of its time,” later echoed in iOS’s own app-switcher (Daring Fireball). No source found here gives an exact corner-radius, shadow, gutter or scale-on-release value in pixels; those specifics are not documented in the sources reviewed and should not be treated as known constants — only the qualitative behavior (live content, rounded cards, spacing between them, flick-to-close) is sourced.
Launcher and quick-launch bar. The Launcher was webOS’s equivalent of a home screen, holding every installed app in an icon grid; it opened via the dock’s dedicated arrow icon, the gesture-area center button in card view, or (with advanced gestures enabled) a swipe up from the bottom (weboshelp.net glossary; webOS 3.0/TouchPad specifics: TechSpot). On phones, a fixed Quick Launch dock held five icons (commonly dialer/contacts/email/calendar plus the Launcher icon itself) always visible at the bottom of the screen regardless of which app or view was active (Bigbeaks blog). On the TouchPad (webOS 3.0), the Launcher organized apps into four categorized pages — Apps, Downloads, Favorites, Settings (TechSpot).
Gesture area. Early webOS phones (Pre, Pixi) had a dedicated touch-sensitive gesture area below the display glass, physically distinct from the screen, extended down so on-screen content itself didn’t have to be covered by a gesture’s starting touch (PhoneArena; via search). It replaced hardware nav buttons entirely and handled entering card view, going back and going forward (PhoneArena). The signature “wave” gesture was an upward swipe starting in the gesture area and continuing onto the screen: done slowly it summoned the Quick Launch dock/taskbar, which visually “stuck to your thumb” and rippled like an actual wave as it followed the finger — an explicit physical metaphor for a system with no dedicated hardware buttons (PCWorld).
Notifications: dashboards and banners. webOS distinguished two notification forms. A banner appeared briefly below the current app (with an icon, short message and sound), then, after a few seconds, was replaced by a dashboard — a small, content-sized, persistent panel reachable for deferred action, “constrained windows that span the full screen width and about 10% of the screen height in portrait mode; on the Palm Pre, that’s 320 pixels wide and 48 pixels high” (O’Reilly, Palm webOS, ch. 10, via search index — the book republishes Palm’s own Mojo developer-guide text). Dashboards could also host ambient/background-app content without requiring a full card. The key structural point sourced here: the dashboard was always sized to its content (a fixed ~10%-height strip), never a full-screen sheet — it never grew to fill unused vertical space just because nothing more had arrived.
Status bar. The always-visible top status bar carried network/carrier name, a digital clock, signal and battery indicators (GSMArena Palm Pre review). Tapping it opened a semitransparent menu for Wi-Fi, Bluetooth and Airplane/Flight Mode toggles plus date/battery detail, without leaving the current app or card — i.e. system-level quick controls lived one tap away from the persistent bar rather than as separate always-visible chrome (GSMArena; the later Palm Pre 2 kept the same tap-to-expand pattern per TechRadar).
Typography. webOS’s system typeface across 2.x/3.x devices (including the HP TouchPad) was Prelude, a custom sans-serif commissioned from Font Bureau co-founder David Berlow specifically for webOS: “an original sans serif design of simple, generous letterforms which provide a clear, comfortable, and inviting experience for navigation and readability,” released as a family of six weights, with publication designer Roger Black consulting alongside Matias Duarte and Peter Skillman on the typography program; a companion print/marketing family, Apres, was developed alongside it (Palm Infocenter, “Font Bureau Developed Custom Typefaces for WebOS”). The same source and an XDA forum thread confirm Prelude was shared across phone webOS and the TouchPad, i.e. one named system font family used everywhere, not a per-surface choice (XDA: “Is the same Prelude font used on both webOS smartphones and the touchpad tablet?”). No source reviewed here gives Prelude’s exact point sizes/weights per UI role (title vs. body vs. card label) — that mapping is not documented in the sources found and is not asserted as a numeric target.
Iconography. Reviewers described webOS’s overall visual craft, icons included, as elegant and cohesive: “detailed application icons tied together in an elegant way,” comparable in polish to the iPhone of the same era (TahawulTech reprint of the PCWorld piece). No source reviewed here gives a documented corner-radius or grid-size specification for webOS app icons; period screenshots (not reproduced here) show a rounded-square glyph treatment consistent with the general skeuomorphic/glossy icon style common across 2009–2012 mobile platforms, but that specific stylistic label is this reviewer’s own characterization of widely available screenshots, not a sourced Palm design-guideline claim, and is flagged as such.
Motion. Sourced motion facts are limited to the choreography, not
frame-level physics: cards could be dragged and held under the finger, thrown
upward to close, and swiped left/right to switch, all described consistently
across
PCWorld,
SlashGear
and the Daring Fireball retrospective.
No easing curve, duration in milliseconds, or velocity/deceleration constant
for the throw-to-close or app-open/close zoom transition was found in any
source consulted for this review; this project’s own
docs/design/handheld-shell/motion-research.md
independently reached the same conclusion after reading the Palm Pre User
Guide directly (“The sources provide interaction and spatial behavior, not
measured durations, velocity constants, spring curves”). This review does not
contradict that: no numeric motion target exists in the historical record
available to either document, and none should be invented.
“Just Type” and later evolution (webOS 3.x / TouchPad). HP’s TouchPad generalized the phone launcher into “Just Type”: a persistent search field across the top of the home screen doubling as a universal launcher, search (local + web/Maps/Wikipedia/Twitter/App Catalog) and quick-action box (e.g. turning typed text directly into a note, message or calendar entry) — extending Palm’s original minimalist, content-first philosophy from “one card at a time” to “one text box for every intent” (PCWorld TouchPad piece and search-aggregated TouchPad review material). The TouchPad’s Exhibition mode — an ambient full-screen clock/agenda/photo display activated automatically when docked on the Touchstone charger — was called out by reviewers as “one of the finer bits of good design on the TouchPad,” notable for being simple and full-screen rather than cluttered (search-aggregated TouchPad review material, uncredited single source not independently re-verified — treat as representative-opinion, not fact).
Overall design philosophy. webOS’s UI was led by Matias Duarte (later Director of Android UX at Google), and multiple period reviews describe the whole system as prioritizing live, real content over decorative chrome — a consistent typeface and icon language “tied together in an elegant way,” a single persistent multitasking metaphor (the card deck) instead of separate home/recents/app-switcher concepts, and system controls (status bar menu, gesture area) that stayed out of the way until invoked (PCWorld; Wikipedia, WebOS). This review’s findings below are checked against that philosophy — content-sized chrome, one consistent type/icon system, and no permanent decorative controls — not against a claim to literally reproduce any webOS screen.
LuneOS. The open-source webOS continuation (webOS-ports.org, rebuilt on Qt/QML rather than the original Mojo/Enyo stack) preserved the same gesture vocabulary rather than reinventing it: a left swipe from the gesture-area light goes back (or returns to card view from a full-screen app), a full swipe across the gesture area switches apps, swipe-up returns to card view, and — notably for the “card stacks” question this review’s brief asked about — LuneOS’s own feature list documents cards that “can be touched to move individually and stacked into groups” plus dedicated “Card Stacking,” “Card Zoom Gestures” and “Tap-to-Maximize Edge Cards” behaviors, i.e. same-app windows could be visually grouped into one stack rather than shown as separate full-width cards (LuneOS feature description via search index of webOS-ports/Preware catalog material; general project description via Wikipedia, LuneOS). This shell has no equivalent of card stacking for multiple windows of the same app — out of scope for this review (no evidence any installed app opens multiple windows today) but worth a note for whoever picks up card-view work next.
4. Evidence gates — what needs real glass vs. what’s already decidable
| Finding | Decidable from host/QEMU captures + code today | Needs board/glass observation |
|---|---|---|
| P0-1 card header identity | yes — code path and captures both show it | confirming legibility/contrast of the fix at arm’s length |
| P0-2 dead panel space | yes — code (panel_h) and every listed capture agree | confirming the “dimmed deck peek” replacement doesn’t look worse under real ambient light |
| P0-3 Wi-Fi error placement | yes | touch-target/legibility of the revised banner |
| P0-4 Settings row spacing | yes — pure arithmetic, both the bug and the fix | none to confirm the bug; a post-fix capture is good practice, not load-bearing |
| P1-1 font consistency | yes — both source files inspected directly | whether fontconfig’s "sans" alias actually diverges from "DejaVu Sans" on the installed image (host-only inspection can settle this without the board, but wasn’t done in this pass) |
| P1-2 gesture-hint styling | yes | whether removing permanent hints after first-use is discoverable without them, per task 5.3’s real-finger discovery gate |
| P1-3 preview fallback | yes | none beyond normal visual review |
| P1-4 shade preview/history redundancy | yes — both captures and code agree | none beyond normal visual review of the corrected layout |
| P1-5 translucency fix not generalized | yes as a preventive code read | needs a theme with alpha < 1.0 on notifications/menu background tried on board or QEMU to become a reproduced bug |
| P2-1 radius drift | yes (single source-line comparison) | none |
| P2-2 icon rendering | yes, already resolved | none |
| §0 chrome-generation correction | yes | none — this is an evidence-reading correction, not a claim needing new capture |
None of the above claims a physical/optical/real-finger pass; per AGENTS.md, any of these fixes still needs its own named evidence class (native build, QEMU, injected event, camera, or real-glass) recorded against the task that implements it, and none is checked complete by this document alone.