Skip to content

fix(desktop): pin taskbar clock and clip entries against runtime reflow (#27) - #29

Merged
Xuepoo merged 2 commits into
mainfrom
carryctx/web-0032-taskbar-clock
Aug 24, 2026
Merged

Xuepoo merged 2 commits into
mainfrom
carryctx/web-0032-taskbar-clock

Conversation

@Xuepoo

@Xuepoo Xuepoo commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #27.

Root cause (measured, dist-verified)

Engine Taskbar.updateClock() couples clock repositioning with the minute string — the x/y assignment sits inside the if (s === this.clockText) return branch. At boot the taskbar mounts while the canvas is still at its default 300px width, so the clock parks at x=236; when the app's fit() 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)

  • New src/desktop/taskbar-guard.ts: pins clock x/y (mirroring engine metrics) and clips the entries host, reasserted on fit(), theme remount, and windowManager events (open/close/retitle) — not just viewport resize.
  • Entries truncate at the host edge (16px clear of the clock, ≥150px bar widths; engine collapse preserved below).
  • Clock discriminator requires Text + non-interactive + non-selectable + a11yProjection==='never'.
  • test/desktop/taskbar-guard.dist.test.ts pins the mirrored engine formulas against the installed @vectojs/desktop dist so a bump fails loudly instead of drifting.

Validation

  • bun test 116 pass / 0 fail (1 pre-existing todo) · tsc --noEmit clean · oxlint --deny-warnings clean · oxfmt clean · vite build ok
  • Live-verified at 950×600 (clock pinned 886, no overlap), 420px instant re-pin, 9-entry stress, theme-remount re-guard.
  • Review round 1 findings (stale base, ancestry claim, drift, discriminator) all addressed; rebased onto dfebbe7.

Xuepoo added 2 commits August 25, 2026 00:03
…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
Xuepoo merged commit 95d7b63 into main Aug 24, 2026
4 checks passed
@Xuepoo
Xuepoo deleted the carryctx/web-0032-taskbar-clock branch August 24, 2026 18:45
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)
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.

Taskbar clock overlapped by entry buttons at ~950px viewport (stale clock x from boot-time width)

1 participant