feat(theme): per-theme visual identity + desktop-OS chrome (#28) - #31
Merged
Merged
Conversation
… tokens Apply the approved spec's §3.2 matrix to all 7 presets (aero/cloud/breeze/ aqua/y2k/vaporwave/dreamcore) and extend AppThemeTokens with the §3.3 era chrome tokens (menu-*, tray-bg, button-shape, chrome-font, taskbar-height, window-shadow, inactive pair, bevel, pinstripe, glow). - Category fix: aero Classic->Modern, aqua Modern->Classic (spec §5-P0) - 11 values deviate from the spec matrix where the WEB-0023 contrast contract floors fail (hairline borders, muted rose, pressed-gray active); deviations recorded inline + DEC-0017/0018 - Contrast WORST baselines remeasured (2026-08-25, base dfebbe7) - New per-theme identity contract test pins signatures, categories, era tokens and wallpaper art markers Engine asks filed first in vectojs-docs 2fc127a (shadow, inactive chrome, caption anatomy, lifecycle hooks, composition, text glow, backdrop). Refs #28
…odels - chrome/shadow.ts: parseShadowToken for the composite desktop-window-shadow token + drawShadow stacking translucent rings (renderer blur is an upstream ask; finding filed first) - chrome/bevels.ts: Win98 4-tone raised/sunken bevels + Aqua pinstripes - model/clock-format.ts: per-era taskbar clock formats (modern two-line, aqua 'Mon 24 10:48', y2k '10:48 AM') - model/start-menu-model.ts: app filter, recent-cap and y2k program groups Refs #28
Replace the engine chrome through its public seams (spec §4 gaps #2-#4, - WebOSTaskbar replaces shell.taskbar: Start tile, pinned launchers, running entries with era indicator (Fluent underline / aqua dock dot / y2k pressed bevel), tray cluster + sunken tray well, two-line clock repositioned on every tick (supersedes the WEB-0032 structural guard) - WebOSStartMenu (search field, 6-col pinned grid, recent list, user+power footer) driven by an overridden toggleStartMenu; Escape and outside-click dismissal reimplemented; opener focus restoration preserved (PX-0077 contract); y2k era gets cascading program groups via ui ContextMenu instead (era-correct) - Desktop right-click menu (Refresh/wallpaper/Display/About) themed by desktop-menu-* tokens - Boot splash: 900ms era mark, non-projected, viewport-tracked so the audit overflow gate stays clean - Icon treatments per §3.4 keyed by preset id; desktop icons re-skin live via setSVGSource on theme switch - config boots with the era taskbar height so engine placement math agrees from the first mount Smoke: park-focus helper finds role=button mirrors (chrome is now entity-drawn). Engine-drawn titlebar hover/inactive/lifecycle gaps stay deferred (DEC-0017). Refs #28
…kbar Rebase resolution for origin/main 95d7b63 (#27/#29): per carryctx DEC-0019 the WEB-0034 WebOS-owned taskbar supersedes the WEB-0032 structural clock guard. The replacement bar owns both guarantees directly instead of pinning engine internals: - Clock placement: ClockView is repositioned from live bar width on every setGeometry/layout AND every tick (taskbar.ts updateClock), so the same-minute-resize stale-clock trap cannot recur. - Entries clipping: entriesHost.clipChildren with a width bounded by the tray cluster edge; rebuild() stops placing entries past it. Guarantees survive; the mirrored engine-private formulas and their dist test are deleted. The dist-contract pattern is ported to the new seam: webos-taskbar.dist.test.ts pins every member the engine touches on the public shell.taskbar field (setGeometry(width,y), destroy+remount on setTheme, dispose teardown, outside-click startButtonRight hit test) against the installed @vectojs/desktop dist. Changelog: merged the two Unreleased sections; #27 fix entry annotated as now provided by the WebOS bar; guard retirement recorded under Removed.
…rs (#28) Review F1: scaleAlpha only matched rgba() and the vaporwave glow path built its falloff with a `.replace(')', ...)`, which never matches a hex token - so #FF71CE glow overdraws and any hex shadow layer rendered at full opacity instead of fading. src/chrome/color.ts becomes the single source for token color math (parseColor, scaleAlpha, glowStackColors, scaleHex): shadow.ts scales ring alpha through it, the start-menu wordmark builds its 0.25/i halo via glowStackColors, and the taskbar clock imports scaleHex instead of keeping a private copy. Regression tests pin hex-shadow falloff and the shipped vaporwave glow stack; two dead `void` statements in start-menu go with it (F6 nit).
…28) Review F2: the Material-era feDropShadow filter was referenced from a zero-size invisible carrier rect, so it rendered nothing and Material icons shipped untreated - while the era-distinctiveness test counted the inert defs substring as evidence of treatment. The treatment now wraps the base glyph in a visible filter-bearing group (new `wrap` slot in IconTreatment), and the identity test asserts the filter attribute sits on a group containing the painted path with no zero-size carrier left behind.
#28) Review F6: the shell.taskbar assignment was cast through `{ taskbar: unknown }`, bypassing assignability on the public seam. A local minimal TaskbarLike interface (setGeometry, startButtonRight, bounds) now types the cast, so WebOSTaskbar must satisfy the surface the engine actually drives; webos-taskbar.dist.test.ts keeps pinning the runtime contract. Also removes the dead `void` statements flagged alongside: the unused spec param on PinnedTile and era field on ClockView (params dropped at both call sites) and the unused theme lookup in context-menu items.
…Id (#28) Review F4: every load paid 900ms mark + 220ms fade with no way to skip it for tests or perf benches. Following the established ?debug query-param convention, ?nosplash now skips the splash entirely; the boot smoke boots under it and asserts no Splash entity mounts. The per-era art gap is made explicit rather than implied: Splash took a presetId and voided it (all eras share one four-pane mark, palette-only variation), so the param is gone until the deferred artwork lands (carryctx DEC-0022; webos-docs TODO Deferred).
…#28) Review F5: era-distinctiveness was asserted via description strings only, so two presets shipping identical 21-token matrices would pass. The identity contract now also requires the full engine-token matrix to differ pairwise across all 7 presets - a real distinctness guarantee that fails loudly if a preset ever collapses onto another era.
User-visible outcomes of the fix round: ?nosplash escape hatch and the actually-rendering Material icon drop shadow (Added), hex-token alpha falloff for glow/elevation layers (new Fixed entry).
…switch applyTheme updated config.desktop.taskbarHeight (drives bar remount and placement) but never DisplayLayout, whose setTaskbar() is the only knob behind workArea()/clampRect(). After switching to an era with a different bar height the painted bar and the engine's usable area disagreed: windows clamped against the boot-time floor overlapped a taller bar or stranded above a shorter one (review PX-0163). applyTheme now calls shell.layout.setTaskbar() alongside the config write and re-clamps windows to the new work area; dist-contract test pins the setter against upstream renames. (#28)
5 tasks
Xuepoo
added a commit
that referenced
this pull request
Aug 25, 2026
…rk-area change (#30) (#34) * fix(desktop): shrink-clamp escaping windows and refit maximized on work-area change (#30) Viewport/DPR changes stranded windows outside the reachable work area (audit WEB-0038). Three compensations for engine gaps filed upstream in vectojs-docs forge/findings (commit 794340f): - clampRect in model/window-geometry mirrors the engine's DisplayLayout.clampRect shrink-then-pin semantics (with optional min floors): a position-only clamp left windows taller/wider than a shrunken work area stranded past the bottom/right forever (4/5 windows outside at 568x315). - fit() re-applies maximize after shell.resize so maximized windows track the new work area instead of keeping their stale pre-shrink box (E1, measured 1280x760 in a 568x355 viewport). - every window-manager event re-runs the clamp so restore()'s stale pre-maximize box is pulled back inside immediately (E2). * docs(changelog): note window work-area stranding fixes (#30) * fix(desktop): gate stream re-clamp to state events; test zero-height area (#30) Review round PX-0159..0163 on WEB-0038: - Restrict the window-manager stream clamp to "state" events (clampWindowsOnEvent): engine drag deliberately parks windows mostly off-screen (titlebar + 48px visible, Window.clampMovePosition), so an unrelated open/focus/close event must not yank them back on-screen. Restore/un-minimize still emit "state" and keep the E2 stale-box correction. - Cover the zero-height work-area edge: clampRect floors to a 1px box pinned at area.y, the documented DEC-0021 app-side deviation from the engine's applyGeom re-flooring. - Dist contract: pin the drag-parking permission so an engine change that forbids parking surfaces here before the event gate is relaxed. Rebased onto origin/main 8689e79 (#31): guardTaskbar dropped (the WebOS-owned taskbar self-subscribes to the wm stream), refitMaximized kept in fit(), and the per-era setTaskbar sync composes with the work-area clamp unchanged (applyTheme re-clamps after setTaskbar). Restores oxfmt-canonical single-quote formatting in main.ts and window-geometry.test.ts after an external editor session re-saved them with Biome defaults.
This was referenced Aug 25, 2026
Open
Xuepoo
added a commit
that referenced
this pull request
Aug 25, 2026
…nned (#32) (#38) * fix(notes): open persisted documents with their saved content The open path never read the VFS: create() seeded the editor with the welcome copy and only the click-driven reloadFromVfs() ever called vfs.read. After a full page reload StorageVfs had already replayed the snapshot (Files listed the document), yet a freshly opened Notes window rendered seed text until the user clicked Reload — confirmed E2E cross-engine on the previous deploy (PX-0099/PX-0108). Opening now restores persisted content immediately when the deterministic target document (/notes/note-N.txt) already exists. The restore stands down if the user typed while stat/read were in flight, so async loading can never clobber live edits; read errors keep the seed text and stay on the Reload path's status reporting. Regression test reproduces the defect through the real boot: write the next note's document into the restored VFS, shell.open('notes'), assert the editor value (red-checked against the unfixed tree). (#32) * test(desktop): pin start-menu dismissal focus contract, drop dead keys shim Re-derived WEB-0035 defect B against the post-rewrite architecture: the opener-focus restore survived PR #31 reimplemented inline in main.ts (closeStartMenu captures the opener at open and hands focus back when dismissal leaves it stranded on body). Escape, toggle and click-outside closes all run through that path. Add the missing outside-click close-path regression to the smoke suite, following the established post-prune focus pattern (overlay mirrors are not reliably projected under happy-dom). Remove src/desktop/start-menu-keys.ts: the pre-rewrite keyboard shim wrapped shell.toggleStartMenu for the engine StartMenu entity and owned the old restore logic; PR #31 stopped installing it (was main.ts:324) leaving zero references while its header still claimed ownership of focus restoration — exactly the drift this module list warns about. roving-focus.ts stays: pure tested util, not tied to the dead wiring. (#32) * chore(changelog): record WEB-0035 defect fixes under Unreleased * fix(desktop): refocus Start when a theme switch strands the menu opener applyTheme rebuilds the WebOSTaskbar before closeStartMenu runs, so an opener captured while the menu was open is already disconnected at restore time and focus() on the detached node silently no-ops — Escape, outside click and launch dismissal all strand DOM focus on body after a theme switch (review F3, PR #31 leftover). Re-validate the captured opener at restore time: keep the immediate focus() while it is still connected, otherwise defer focus to the live bar's Start tile mirror one sync pass out — rebuilt mirrors are projected by the next a11y sync pass, not on scene.add. The deferred restore yields if anything else took focus meanwhile, matching the immediate path's contract. WebOSTaskbar gains a startButton accessor for the fallback target. The regression test drives applyTheme with a captured opener and pins focus on the rebuilt bar's Start mirror; verified red without the fix. webos#36
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #28.
Implements the approved theme-identity & desktop-OS-feel spec (webos-docs/specs/2026-08-24-theme-identity-and-os-feel.md), P0+P1.
What ships
Validation
137 pass / 0 fail · tsc clean · oxlint clean · oxfmt clean · build 417.10 kB (+10% vs main, wallpapers+chrome) · contrast floors hold (16 floor-forced deviations documented) · review round + 6-finding fix round complete (hex falloff, icon filter binding, splash hatch, distinctness test, seam typing).