fix(desktop): pin taskbar clock and clip entries against runtime reflow (#27) - #29
Merged
Merged
Conversation
…ow (#27) The engine's Taskbar.updateClock() only repositions the clock when the formatted minute string changes, so the bar mounted at default canvas width (measured: clock parked at x=236 while the scene was 950) and any same-minute resize left it stale — into the entry-button region. Main's clipTaskbarEntries (audit #25, landed via PR #26) clips the entries host once at boot only; this guard supersedes it — that function is removed — and extends coverage to the boot-time clock misplacement, same-minute resizes, and the taskbar remount that setTheme performs. - taskbar-guard.ts: re-pin the clock to the live bar size and clip the engine-private entries host (structural contract, fails safe to a no-op on an unrecognized subtree). The structural clock lookup requires the markers @vectojs/desktop 0.7.0 sets on clockLabel — non-interactive Text with a11yProjection "never" and selectable off (an ordinary Text is non-interactive by default, so interactivity alone cannot discriminate) — and a dist-contract test asserts every mirrored constant against the installed package so drift fails CI. - Wired into fit(), applyTheme's post-setTheme remount, and the window-manager stream so open/close/retitle reassert the guard with no viewport event. - Entries truncate at the host edge instead of overlapping the clock; narrow-viewport behavior unchanged (host collapses exactly as the engine does below 150px). Engine limitation recorded in vectojs-docs forge findings (desktop-webos 2026-08-24). Covers #27.
Xuepoo
added a commit
that referenced
this pull request
Aug 25, 2026
* feat(model): era-faithful engine token matrices + app-side era chrome 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 * feat(chrome): fake-elevation shadows, classic bevels and era chrome models - 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 * feat(desktop): replace engine chrome with era-aware webos-owned anatomy 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 * docs(changelog): unreleased WEB-0034 theme identity + chrome anatomy entry * style(model): oxfmt app-theme derivation * refactor(desktop): retire engine-taskbar guard superseded by WebOSTaskbar 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. * fix(chrome): hex tokens get real alpha falloff via shared color helpers (#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). * fix(desktop): bind cloud icon drop-shadow to the painted glyph group (#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. * refactor(desktop): compile-checked taskbar seam + dead statement sweep (#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. * feat(desktop): ?nosplash boot escape hatch; drop unused splash presetId (#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). * test(model): pairwise engine-token matrix distinctness across presets (#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. * docs(changelog): review-round fixes in Unreleased (#28) 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). * fix(desktop): sync engine work area with era taskbar height on theme 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)
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 #27.
Root cause (measured, dist-verified)
Engine
Taskbar.updateClock()couples clock repositioning with the minute string — thex/yassignment sits inside theif (s === this.clockText) returnbranch. At boot the taskbar mounts while the canvas is still at its default 300px width, so the clock parks atx=236; when the app'sfit()fires with the real scene size,updateClock()early-returns (same minute) and the clock stays inside the entries region until the next minute tick. Same-minute resizes leave it stale/off-edge the same way.rebuild()also places an entry before checking remaining host width (~160px spill).Fix (app-side, supersedes PR #26's boot-only clipTaskbarEntries)
src/desktop/taskbar-guard.ts: pins clock x/y (mirroring engine metrics) and clips the entries host, reasserted onfit(), theme remount, and windowManager events (open/close/retitle) — not just viewport resize.a11yProjection==='never'.test/desktop/taskbar-guard.dist.test.tspins the mirrored engine formulas against the installed @vectojs/desktop dist so a bump fails loudly instead of drifting.Validation