vintage-frames 0.19.1 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +1 -18
  2. package/custom-elements.json +72 -13
  3. package/dist/components/vf-desktop.d.ts +36 -6
  4. package/dist/components/vf-desktop.js +41 -23
  5. package/dist/components/vf-window.d.ts +19 -0
  6. package/dist/components/vf-window.js +33 -3
  7. package/dist/text-control.d.ts +3 -3
  8. package/docs/SPEC.md +8 -6
  9. package/editor/vscode.html-custom-data.json +10 -5
  10. package/editor/web-types.json +25 -10
  11. package/package.json +4 -34
  12. package/dist/build/app-file.d.ts +0 -21
  13. package/dist/build/app-file.js +0 -42
  14. package/dist/build/box.d.ts +0 -57
  15. package/dist/build/box.js +0 -93
  16. package/dist/build/client.d.ts +0 -12
  17. package/dist/build/frame-picture.d.ts +0 -6
  18. package/dist/build/frame-picture.js +0 -4
  19. package/dist/build/frame.d.ts +0 -11
  20. package/dist/build/frame.js +0 -44
  21. package/dist/build/index.d.ts +0 -14
  22. package/dist/build/index.js +0 -4
  23. package/dist/build/plugins.d.ts +0 -41
  24. package/dist/build/plugins.js +0 -134
  25. package/dist/build/png.d.ts +0 -24
  26. package/dist/build/png.js +0 -116
  27. package/dist/shell/alert.d.ts +0 -40
  28. package/dist/shell/alert.js +0 -41
  29. package/dist/shell/app-file.d.ts +0 -115
  30. package/dist/shell/app-file.js +0 -189
  31. package/dist/shell/catalog.d.ts +0 -250
  32. package/dist/shell/catalog.js +0 -349
  33. package/dist/shell/clock.d.ts +0 -16
  34. package/dist/shell/clock.js +0 -36
  35. package/dist/shell/filing.d.ts +0 -39
  36. package/dist/shell/filing.js +0 -68
  37. package/dist/shell/finder.d.ts +0 -180
  38. package/dist/shell/finder.js +0 -676
  39. package/dist/shell/geometry.d.ts +0 -203
  40. package/dist/shell/geometry.js +0 -257
  41. package/dist/shell/index.d.ts +0 -30
  42. package/dist/shell/index.js +0 -12
  43. package/dist/shell/pure.d.ts +0 -13
  44. package/dist/shell/pure.js +0 -6
  45. package/dist/shell/shell.d.ts +0 -177
  46. package/dist/shell/shell.js +0 -277
  47. package/dist/shell/state.d.ts +0 -88
  48. package/dist/shell/state.js +0 -103
  49. package/dist/shell/storage.d.ts +0 -14
  50. package/dist/shell/storage.js +0 -41
  51. package/dist/shell/version.d.ts +0 -2
  52. package/dist/shell/version.js +0 -4
  53. package/dist/shell/windows.d.ts +0 -278
  54. package/dist/shell/windows.js +0 -447
  55. package/dist/styles/body-strike.d.ts +0 -12
  56. package/dist/styles/body-strike.js +0 -3961
  57. package/dist/styles/display-strike.d.ts +0 -12
  58. package/dist/styles/display-strike.js +0 -4343
  59. package/dist/styles/strike.d.ts +0 -30
package/docs/SPEC.md CHANGED
@@ -196,14 +196,15 @@ Every recipe also declares its size — `width`, and `height` where the window i
196
196
 
197
197
  #### `vf-desktop` (`VfDesktop`, vf-desktop.ts)
198
198
  Full-bleed classic desktop container.
199
- - **Attributes/props:** `width`, `height: number` (system px, defaults 512×342 — the compact Mac's raster) — the screen's own size, the way a WIND resource declared a window's. The host box renders at exactly `width + 2·bezel` per axis (a live `sysLength` written as host inline size on every update), always a whole number of system px. **Pure CSS sizing is not supported**: the inline size wins over any stylesheet, so the page sets the numbers — directly or via `fitWithin` — and positions the explicitly sized desktop with its own CSS, keeping any sub-system-pixel slack in the page's own layout. `bezel: number` (system px, default 0) — the black screen surround, the CRT's unlit margin between raster and case, added onto the declared screen on every side. The screen owns flow, an absolute window's containing block and the drag clip, so a dragged window crops at the raster's edge. Its four corners wear the `SCREEN_CORNER` mask (the bottom pair mirrored vertically — `steppedCornerClip`'s `edge`), painted above everything like the hardware mask — the top pair lands over a slotted menu bar's corners, so the bar needs no `rounded` of its own inside a bezeled desktop. The bezel width is written onto the host as `--vf-desktop-bezel` (self-set geometry, like `--vf-scale` — component-owned, not a theming token). `pattern: string` (default `'gray-50'`, reflected) — the desktop pattern, System 7's General Controls setting: a library name (docs/PATTERNS.md) or sixteen hex digits stating a custom 8×8; an unrecognized value warns once and keeps the dither. A `--vf-desktop-pattern` token override wins over it. It reflects so a page can observe a change on the attribute; the shell saves it that way. `window-top: number` (`windowTop`, system px from the screen's top, unset by default) — the least `top` a window may take, for a page that keeps a strip of its own under the menu bar, over the windows: a window dragged on the desktop stops there and `windowArea` starts there; icons still stop at the menu bar; below the bar's bottom it changes nothing.
200
- - **Methods:** `fitWithin(maxWidth, maxHeight)` (CSS px) → sets `width`/ `height` to the largest whole-system-px raster whose host box — bezel included — fits the bound, per the current effective scale, and returns `{ width, height }`. The page's half of the sizing contract: call it on `resize` and `onScaleChange` (zoom and density moves change what a system px costs in CSS px), as a full-screen faux desktop does. `clearActive()` → deactivate the whole document tier (see *Deactivation* under Behavior). `activeWindow: HTMLElement | null` (read-only) → the active document-tier window, or `null` while the tier is deactivated or has no shown window; utility windows and hidden windows are never the holder. `stackingOrder: HTMLElement[]` (read-only) → the slotted windows in stacking order, bottom-most first — the document tier, then the floating tier, each by its last raise — hidden windows included where they stand; a fresh array per read. The light-DOM order catches up with it only after a gesture, and a raise by keyboard focus moves no node, so a page that saves window depths or picks the window behind another reads this rather than the DOM. `workArea` (read-only) → `{ left, top, width, height }` in system px from the screen's corner, bezel excluded: the screen less a slotted, shown `vf-menu-bar` along its top edge (measured at the call; the whole screen without one). `windowArea` (read-only) → the work area deepened to `window-top` where that is lower — the area windows are dragged in. `placementAt(clientX, clientY, child?)` → a viewport point as a placement on the screen, `{ left, top }` in whole system px on the placement lattice, measured from the raster's corner with the bezel excluded — the pair a child dropped onto the desktop is written with; with `child`, the pair to write to that element, its `origin` offset folded in (`placementIn`, src/position.ts; the anchor sits in shadow, which is why it is a method).
199
+ - **Attributes/props:** `width`, `height: number` (system px, defaults 512×342 — the compact Mac's raster) — the screen's own size, the way a WIND resource declared a window's. The host box renders at exactly `width + 2·bezel` per axis (a live `sysLength` written as host inline size on every update), always a whole number of system px. **Pure CSS sizing is not supported**: the inline size wins over any stylesheet, so the page sets the numbers — directly or via `fitWithin` — and positions the explicitly sized desktop with its own CSS, keeping any sub-system-pixel slack in the page's own layout. `bezel: number` (system px, default 0) — the black screen surround, the CRT's unlit margin between raster and case, added onto the declared screen on every side. The screen owns flow, an absolute window's containing block and the drag clip, so a dragged window crops at the raster's edge. Its four corners wear the `SCREEN_CORNER` mask (the bottom pair mirrored vertically — `steppedCornerClip`'s `edge`), painted above everything like the hardware mask — the top pair lands over a slotted menu bar's corners, so the bar needs no `rounded` of its own inside a bezeled desktop. The bezel width is written onto the host as `--vf-desktop-bezel` (self-set geometry, like `--vf-scale` — component-owned, not a theming token). `pattern: string` (default `'gray-50'`, reflected) — the desktop pattern, System 7's General Controls setting: a library name (docs/PATTERNS.md) or sixteen hex digits stating a custom 8×8; an unrecognized value warns once and keeps the dither. A `--vf-desktop-pattern` token override wins over it. It reflects so a page can observe a change on the attribute; system-online-sdk's shell saves it that way. `window-top: number` (`windowTop`, system px from the screen's top, unset by default) — the least `top` a window may take, for a page that keeps a strip of its own under the menu bar, over the windows: a window dragged on the desktop stops there and `windowArea` starts there; icons still stop at the menu bar; below the bar's bottom it changes nothing.
200
+ - **Methods:** `fitWithin(maxWidth, maxHeight)` (CSS px) → sets `width`/ `height` to the largest whole-system-px raster whose host box — bezel included — fits the bound, per the current effective scale, and returns `{ width, height }`. The page's half of the sizing contract: call it on `resize` and `onScaleChange` (zoom and density moves change what a system px costs in CSS px), as a full-screen faux desktop does. `clearActive()` → deactivate the whole document tier (see *Deactivation* under Behavior). `activeWindow: HTMLElement | null` (read-only) → the active document-tier window, or `null` while the tier is deactivated or has no shown window; utility windows and hidden windows are never the holder. `stackingOrder: HTMLElement[]` (read-only) → the slotted windows in stacking order, bottom-most first — the document tier, then the floating tier, each by its last raise — hidden windows included where they stand; a fresh array per read. The light-DOM order catches up with it only after a gesture, and a raise by keyboard focus moves no node, so a page that saves window depths or picks the window behind another reads this rather than the DOM. The window on the full-screen tier is listed last. `fullScreenWindow: HTMLElement | null` → the window on the full-screen tier, or `null`; set it to a slotted window to put it there, `null` to put it back (see *Full-screen tier* under Behavior). `workArea` (read-only) → `{ left, top, width, height }` in system px from the screen's corner, bezel excluded: the screen less a slotted, shown `vf-menu-bar` along its top edge (measured at the call; the whole screen without one). `windowArea` (read-only) → the work area deepened to `window-top` where that is lower — the area windows are dragged in. `placementAt(clientX, clientY, child?)` → a viewport point as a placement on the screen, `{ left, top }` in whole system px on the placement lattice, measured from the raster's corner with the bezel excluded — the pair a child dropped onto the desktop is written with; with `child`, the pair to write to that element, its `origin` offset folded in (`placementIn`, src/position.ts; the anchor sits in shadow, which is why it is a method).
201
201
  - **Visual:** `display: block; position: relative;` — the paint lives on an inner screen surface (part `desktop`, `overflow: hidden` — the whole-system-px raster, inset by `bezel` when one is set). Screen surface = the `pattern` (the classic 50% dither, `gray-50`, by default), painted as the screen's own background by the pattern fill (*Patterns* under *Tiled fills*): black ink on the fill's opaque `--vf-white` paper, a repeating tile — the authentic black-on-white dither. A consumer `--vf-desktop-pattern` renders instead as a placed tile grid on the token's 30-system-px tile with no paper, so `var(--vf-desktop, #808080)` beneath shows only under a custom tile with transparent cells (or `none`). Forced colors: flat Canvas.
202
202
  - **Slots:** default (menu bar, windows, anything).
203
203
  - **Behavior:** manages stacking of slotted `vf-window` children: `pointerdown` *or `focusin`* on a window brings it to front (incrementing z-index counter) and sets its `active` attribute, clearing `active` on the others — the `focusin` half is the keyboard route to activation: Tab landing anywhere in a background window (its undrawn-but-focusable widgets included) raises it. A press on a title bar with ⌘ held (`metaKey`) raises nothing and activates nothing, so a background window is dragged where it stands in the stack. `npm run verify:desktop-activate` (⌘-DRAG). Listens via delegated pointerdown/focusin listeners + `slotchange`. Windows slotted before `vf-window` is defined are re-normalized once `customElements.whenDefined('vf-window')` settles, since the upgrade reflects each window's `active = true` default back out and upgrading a slotted node doesn't re-fire `slotchange`.
204
204
  - **DOM order follows z-order** (bottom-most first), so sequential focus order matches the visual stack and Shift+Tab mirrors Tab exactly. The sync runs in a task after a pointer gesture ends, or after a programmatic `bringToFront` — never mid-gesture (a node move would clear the pointer capture a title-bar drag or grow-box resize holds), never between a press's release and its `click` (Chromium drops a click whose mousedown node was re-inserted first, which used to cost a control in a background window its click), and never from a focus-driven raise (moving the window focus just entered would re-order the tab sequence mid-traversal); the move is `Element.moveBefore()` where the browser has it, which keeps the focus inside the moved window — a rename field, a caret — and otherwise a re-insert, after which the focus is restored without re-raising that window (a `vf-icon` rename whose field blurred in the move and has its focus back in the same task commits nothing). Non-window children (a menu bar, page content) keep their positions. `npm run verify:window-a11y`.
205
205
  - **Floating tier:** `vf-window[variant="utility"]` children stack in a z band `1_000_000` above the document tier (one shared monotonic counter, so a palette stays above every document window), restack only among themselves, and stand outside the single-active invariant both ways: clicking a palette doesn't deactivate the active document window, and activating a document window never clears a palette's `active` — System 7 windoid behavior while the app is frontmost. The tier test reads the `variant` property of an upgraded window and the attribute of one not yet upgraded, so both land right — and so does a palette made by script and appended at once, which is slotted before its attribute reflects. `npm run verify:desktop-activate` (SCRIPTED).
206
- - **Menu tier:** a slotted `vf-menu-bar` — or a free-standing `vf-menu` placed on the desktop — is restated at `z-index: 2_000_000` by the desktop's own `::slotted(vf-menu-bar), ::slotted(vf-menu)` rule (an outer-tree `::slotted` declaration beats the bar's inner `:host { z-index: 1000 }`), a band above the floating tier, so a dropped menu paints over palettes and document windows alike — the Menu Manager drew menus over every window. Only the drag outline, a window's opening rects and the screen-corner mask are in front. The full stack inside the screen's isolated context: consumer tile grid (−1) < document windows (counter) < utility windows (counter + 1_000_000) < menu tier (2_000_000) < drag outline and window rects (2_000_001) < corner mask (max). A popup *inside* a window (`vf-select`'s fixed panel) is a different case: it stacks within its window's context, so a palette can still cover it — KNOWN-BUGS.md. `npm run verify:archetypes` (MENU TIER).
206
+ - **Menu tier:** a slotted `vf-menu-bar` — or a free-standing `vf-menu` placed on the desktop — is restated at `z-index: 2_000_000` by the desktop's own `::slotted(vf-menu-bar), ::slotted(vf-menu)` rule (an outer-tree `::slotted` declaration beats the bar's inner `:host { z-index: 1000 }`), a band above the floating tier, so a dropped menu paints over palettes and document windows alike — the Menu Manager drew menus over every window. Only the drag outline, a window's opening rects, the full-screen tier and the screen-corner mask are in front. The full stack inside the screen's isolated context: consumer tile grid (−1) < document windows (counter) < utility windows (counter + 1_000_000) < menu tier (2_000_000) < drag outline and window rects (2_000_001) < the full-screen window (3_000_000) < corner mask (max). A popup *inside* a window (`vf-select`'s fixed panel) is a different case: it stacks within its window's context, so a palette can still cover it — KNOWN-BUGS.md. `npm run verify:archetypes` (MENU TIER).
207
+ - **Full-screen tier:** `fullScreenWindow` puts one slotted window on a tier of its own, `z-index: 3_000_000`, above everything else the screen shows — both window tiers, the menu bar and any dropped menu, the drag outline and the window rects, which belong to the windows and icons it covers — and under the corner mask alone, so the screen keeps its rounded corners: the tier a window takes the whole screen on, the bar covered rather than hidden, so its band stays in `workArea` and nothing else's geometry moves. The tier stacks and nothing more: the page sizes the window to the screen (`width` × `height`, at `0, 0`), draws it without chrome (`vf-window frameless`) and brings it forward (`bringToFront`); setting it activates nothing. One window holds it: setting another puts the first back, and `null` puts the holder back in its own tier where it stood — its z-index there is kept, and a raise while it holds the tier (`bringToFront`, a press) moves that kept place, as it would have moved the window, and activates as ever. The DOM-order sync orders the holder by its kept place, so taking and leaving the tier move no node and the focus inside it stays put. A holder that leaves the desktop gives the tier up, its kept place written back, so one put back in lands in its own tier. Anything but a window slotted in the desktop warns and changes nothing. `npm run verify:archetypes` (FULL-SCREEN TIER).
207
208
  - **The drag surface:** the Finder drew a dragged icon's outline on the *screen*, clipped at its edge and phase-locked to its raster, so the screen renders the layer that outline draws on — `.drag-surface`, a full-bleed child of `.screen` after the slot, `pointer-events: none` (never a hit for the page's `elementsFromPoint`), inside the raster's clip. It is deliberately no stacking context (no z-index of its own): a stacking context is an isolated group, and the outline's `mix-blend-mode` would then composite against the empty layer instead of the windows and dither beneath; the tier goes on its children (`.drag-surface > * { z-index: 2_000_001 }`), which places the outline in `.screen`'s own context, above the menu bar and under the corner mask. An icon reaches it through the `vf-drag-surface-request` handshake (§5 vf-icon): the desktop listens on its host, and the nearest desktop fills a still-null `detail.surface`. A window asks the same way and draws there with the same pen: its zoom rects when it opens from or closes to a rect (§5 vf-window, `show` and `hide`), its outline under `outline-drag`. Not the top layer: stable Safari cannot blend an element there against the page (`XOR_UNAVAILABLE`, src/cursor.ts). `npm run verify:icon-drag` (OUTLINE).
208
209
  - **Deactivation:** on a real System 7 machine clicking the desktop clicked the *Finder* — the frontmost application's windows lost their stripes. `clearActive()` is that gesture's handler: it clears `active` from the whole document tier, and **zero active windows is a legal state**, held until a press or `focusin` re-enters a document window or a new one is slotted (opening a window brings its application forward — a newly slotted window ends a deliberate deactivation, where a mere survivor never does: removing a *background* window while deactivated promotes nothing, while removing the holder outside a deactivation promotes the topmost survivor). The desktop never takes the decision itself — a press on its own bare dither changes nothing; desktop furniture is slotted light DOM, so only the page knows which of its children mean "the Finder". Utility windows keep their `active` through a deactivation (their dots stay drawn). **A hidden window never holds the active state.** A page that closes windows by hiding them (`vf-window.hide()`, or the `hidden` attribute set directly — the desktop watches the attribute on every slotted window) gets no activation from the hidden ones: a window slotted hidden waits to be shown, `bringToFront()` on one gives it its place in the stack and activates nothing, and the holder hiding hands the state to the topmost shown document window, or to none. A window shown into a tier where none is active takes the state, unless the tier was deliberately deactivated. Every change of holder — window to window, window to none, none to window — fires `vf-activate`, once per change; re-asserting the current holder is silent. `npm run verify:desktop-activate`.
209
210
  - **The work area:** a slotted `vf-menu-bar` along the screen's top takes its band off the screen (`workArea`), and the gestures on the screen stay below it: a window dragged up stops with its title bar under the bar — or at `window-top`, for windows alone (`windowArea`) — where it can still be grabbed, and an icon dropped, nudged, moved (`moveTo`, `dragTo`) or carried in a group on the desktop lands below the bar. A gesture measures the box it is clamped into once, at its start (`PlacementController.seed()`), by dispatching `vf-placement-bounds-request` (bubbles, non-composed — the drag surface's handshake); the desktop answers for a host placed on its screen (`offsetParent` the desktop: a slotted window, an icon in an unplaced field) with the screen's size, bezel excluded, and the area's top as the floor, rounded up to the host's placement lattice so a snapped landing never rounds above it (21 on a lattice of 3). A host placed in a box of its own — a window body, a placed field — is clamped in that box, from 0. `npm run verify:window` (WORK AREA), `npm run verify:icon-drag` (WORK AREA).
@@ -212,7 +213,7 @@ Full-bleed classic desktop container.
212
213
 
213
214
  #### `vf-window` (`VfWindow`, vf-window.ts)
214
215
  The desktop-window shell: the classic document window (see DragThing screenshot), parameterized down to the windoid (see the Group A recipe table).
215
- - **Attributes/props:** `heading: string` (title text), `width: number` / `height: number` (**declare them both** — whole system px, so the window keeps its proportion to the chrome inside it at every density. A window is a fixed box in both axes, the way a WIND resource was: one that grows with its body is one the user can neither predict nor control via the grow box. Each missing dimension falls back to something different — width to block layout, height to the content — and the window names whichever are missing, once, in the console), `active: boolean` (default **true**; reflect), `closable: boolean` (default false, like every boolean attribute, so markup can leave it out; the document window and modeless dialog box recipes state it), `zoomable: boolean` (default false), `movable: boolean` (default false), `outline-drag: boolean` (`outlineDrag`, default false — the title-bar drag moves an outline and the release moves the window; see Behavior), `resizable: boolean` (default false), `min-width` / `min-height: number` (default 80 / 54) and `max-width` / `max-height: number` (unbounded) — the grow box's sizeRect, whole system px (see Behavior), `variant?: 'utility'` (the slim windoid chrome), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails), `header-height: number` (`headerHeight` — the header's height in whole system px, rule included; unset, the header is as tall as its content plus the rule), `pattern?: string` (a 1-bit fill for the body as on `vf-container`, under the content and anchored at the content region's corner; under `scrollbars` the built-in scroll area's plane carries it, so it scrolls with the content — docs/PATTERNS.md). Read-only: `sizeLimits` → `{ minWidth, minHeight, maxWidth, maxHeight }`, the sizeRect as it applies in whole system px — the declared mins or the 80×54 floor, the declared maxes or `Infinity` — for a page that sizes the window itself to stay in the grow box's range. The chrome around the content is `windowChrome()` (§2, src/window-chrome.ts): the insets in system px between the outer box and the body, or under `scrollbars` the scrolled plane's viewport, for a set of declared options — `{ variant, scrollbars, headerHeight, status }` — at the default tokens. `npm run verify:window` (CHROME).
216
+ - **Attributes/props:** `heading: string` (title text), `width: number` / `height: number` (**declare them both** — whole system px, so the window keeps its proportion to the chrome inside it at every density. A window is a fixed box in both axes, the way a WIND resource was: one that grows with its body is one the user can neither predict nor control via the grow box. Each missing dimension falls back to something different — width to block layout, height to the content — and the window names whichever are missing, once, in the console), `active: boolean` (default **true**; reflect), `closable: boolean` (default false, like every boolean attribute, so markup can leave it out; the document window and modeless dialog box recipes state it), `zoomable: boolean` (default false), `movable: boolean` (default false), `outline-drag: boolean` (`outlineDrag`, default false — the title-bar drag moves an outline and the release moves the window; see Behavior), `resizable: boolean` (default false), `min-width` / `min-height: number` (default 80 / 54) and `max-width` / `max-height: number` (unbounded) — the grow box's sizeRect, whole system px (see Behavior), `variant?: 'utility'` (the slim windoid chrome), `frameless: boolean` (default false, reflected — no chrome at all, the body the whole box; see Visual), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails), `header-height: number` (`headerHeight` — the header's height in whole system px, rule included; unset, the header is as tall as its content plus the rule), `pattern?: string` (a 1-bit fill for the body as on `vf-container`, under the content and anchored at the content region's corner; under `scrollbars` the built-in scroll area's plane carries it, so it scrolls with the content — docs/PATTERNS.md). Read-only: `sizeLimits` → `{ minWidth, minHeight, maxWidth, maxHeight }`, the sizeRect as it applies in whole system px — the declared mins or the 80×54 floor, the declared maxes or `Infinity` — for a page that sizes the window itself to stay in the grow box's range. The chrome around the content is `windowChrome()` (§2, src/window-chrome.ts): the insets in system px between the outer box and the body, or under `scrollbars` the scrolled plane's viewport, for a set of declared options — `{ variant, scrollbars, headerHeight, status }` — at the default tokens. `npm run verify:window` (CHROME).
216
217
  - **Visual:** `vfChromeFrame` + `vfTitleBar` (§4), plus a full-size flex-column layout on the frame — title bar, header, body, status strip. `display: block`. Sets `--vf-surface: var(--vf-white, #fff)` on itself.
217
218
  - Title bar: from `vfTitleBar` — height `var(--vf-titlebar-height, 18px)`, white bg, bottom `1px solid black`, contains `.vf-stripes` layer (only when `active`). `touch-action: none` only when `[movable]`.
218
219
  - Title: centered, bold, on a white patch (`padding: 0 6px`; cap band on interior rows 4..12, 7px of white between ink and stripes — see §4 `vfTitleBar` for the traced geometry and the lattice hold) above the stripes, with `--vf-title-inset: 60px` of clearance so it ellipsizes before reaching the widgets. Inactive: no stripes, widgets undrawn (transparent ink — they keep their tab stops; see A11y below), the grow box's nested squares hidden, and every managed scroll rail inside the window blanked (see "always-a-rail" §5 vf-scroll-area) — but the title text stays black (System 7 never grayed the window title).
@@ -224,6 +225,7 @@ The desktop-window shell: the classic document window (see DragThing screenshot)
224
225
  - Status bar (`status` slot): the classic bottom readout strip ("40px x 40px") — a 1px black rule (`vfRule`'s `.vf-rule-top`) over a white interior, `--vf-status-bar-height` (15px) in all, the grow box's own height, so a `resizable` window's grow box sits flush in the strip's right end (its top/left borders take over the rule there; the strip reserves 21px of right padding under `resizable` so text clears the cell). Slotted content rides the body face's native 12px line (`--vf-line-height`), whole-pixel centered in the 14px interior, `padding-inline: 6px`, nowrap + clipped — vertically by the slot, below the rule. Takes **no space** until the slot is populated, and collapses again when it empties. Composes with `scrollbars`: the edge rails' bottom overhang lands its frame line exactly on the strip's rule, so the two never double up — though the grow box then sits in the strip rather than the rail corner cell.
225
226
  - Edge scroll rails (if `scrollbars`): the body slot renders inside a shadow `vf-scroll-area` (its `axis` = the attribute's value, `label` = the heading, `viewport` part re-exported) carrying the TeachText composition internally — `calc(100% + 2px·scale)` with `margin: -1px·scale`, one system px under the frame on every side, so the area's own frame border repaints the window's border lines and a `resizable` window's grow box (z-index 1) lands exactly over the rail-corner cell. the area's viewport carries no inset, so content and the (0,0) of placed children sit at the content region's corner; a `resizable` window sets the area's `corner`, so a single-axis rail stops at the corner cell too — unless the status strip is populated, when the grow box sits in the strip and the rail runs edge to edge onto the strip's rule. Same caveats as the slotted composition (see vf-scroll-area §5).
226
227
  - Utility variant (`variant="utility"`): the slim windoid bar traced from `Windows/utility-window.png` — `--vf-titlebar-height-utility` (12px = 11px interior + 1px rule), the `vfDots` dither instead of stripes (flush to the side borders — see §4 vfDots), 7×7 widgets (`top: 2px`; close `left: 7px`, zoom `right: 8px` — the art is asymmetric by that pixel) with a 2px patch ring where the striped bar's is 1px (`--vf-widget-ring`, internal geometry: the windoid sheet clears two px of dither beside its widgets), and the nested zoom square shrunk so its edges land at sprite col/row 3. No title patch: the display face's 16px line box can't sit in an 11px interior, so `.vf-title` is `display: none` under the variant (a retheming consumer can re-show it via `::part(title)`) and the heading names the widgets. A pressed windoid widget inverts whole — black interior under a white (invisible) borderline — rather than flashing the big bar's 9×9 sunburst, which can't land on a 5×5 interior.
228
+ - Frameless (`frameless`): no chrome — no title bar, so no close or zoom box, no frame stroke or hard shadow, no grow box, and no header or status strip whatever their slots hold. The body is the whole box: `pattern` fills it edge to edge, and a placed child's (0,0) is the box's corner. Under `scrollbars` the built-in scroll area fills the box, `margin: 0` and no overhang — there is no frame for its frame line to land on, so that line sits on the box's edge — and keeps no corner cell, there being no grow box: the scrolled viewport sits 1px in, and a rail's 15px more on its side. `windowChrome()` describes the framed window; a frameless one has no chrome to state. The chrome is hidden (`display: none`, the stroke and shadow off), not left out of the render, so switching it on a connected window moves no node and the focus in the body stays where it is; the title patch stays in the tree, so `heading` still names the group, as the utility variant's hidden patch does. In a `vf-desktop` the window keeps its tier for activation and `hidden`, and `show({ from })` and `hide({ to })` run their rects from and to its box, the host's. The window that takes the screen on `vf-desktop`'s full-screen tier draws this way. `npm run verify:window` (FRAMELESS).
227
229
  - **A11y:** the close/zoom `aria-label`s are qualified by the title when there is one (`Close ${heading}` / `Zoom ${heading}`, falling back to bare `Close` / `Zoom`) — several windows are open at once by design, so a bare repeated "Close" gives an AT user no way to tell which window a widget belongs to. The frame is `role="group"`, named by the title patch via `aria-labelledby` when there is a heading (the utility variant's hidden patch still names it — AccName resolves hidden labelledby targets) — `group` rather than `region` deliberately, so a desktop of windows doesn't pollute landmark navigation; the title bar is a `<div>`, never a `<header>`, which would map to an unnamed `banner` landmark even inside the shadow root. An inactive window's widgets stay in the tree and the tab order but paint no ink (transparent border/background/patch ring — the bare System 7 bar): a background window whose body holds nothing focusable is still reachable, activated by `vf-desktop`'s `focusin` raise the moment Tab lands on a widget, and never drops focus to `<body>` when it deactivates. `npm run verify:window-a11y`.
228
230
  - **Behavior:** close box click → `vf-close` (does NOT remove itself; consumer decides). Zoom box click → `vf-zoom`. If `movable`: dragging the title bar moves the window — the drag seeds its origin from the current offset position (once, converting to system px) and then states `left`/`top` via pointer capture, in whole system px like any authored placement. With `outline-drag` the drag moves an outline instead: the press draws a one-pixel ring around the window's border box (the frame, shadow excluded) on the desktop's drag surface, with the drag outline's pen (`xorPenCanvas`, dotted at the screen phase — black over the dither), and each step moves it to the landing the live drag would write (`PlacementController.resolve()`: the grabbable-strip clamp, snapped), so the outline stops where the window can go; the release writes the last landing once (one `vf-placement-change`) and a release with no step writes nothing, and Escape — a gesture-scoped document capture listener armed at the press, the key stopped there — or a `pointercancel` takes the outline down and writes nothing. With no `vf-desktop` on its path the drag moves the window live. `npm run verify:window` (DRAG OUTLINE). If `resizable`: dragging the grow box states `width`/`height` the same way, each axis clamped into the sizeRect (`min-width`/`max-width`, `min-height`/`max-height` — GrowWindow's: the app stated the rectangle of sizes and the Window Manager clamped the drag to it; the mins default to the 80×54 floor a window can still be worked at, the maxes are unbounded, and a min equal to its max locks the axis — the strip that scrolls sideways and never grows taller). The clamp runs after the lattice snap, so a bound lands exactly the way an authored size does even off the lattice (an odd height at 2×); where a min and max cross the max wins, so a window authored under the floor and held there never jumps to it. The rect bounds the gesture only: a size authored outside it renders as declared, and the first grow-box move brings it inside. The drag fires `vf-resize` (detail `{ width, height, commit }`, sizes in whole system px) — one event per size the drag actually writes (`commit: false`), fired *after* the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles (release or cancel), only when it changed the size. Window content can follow the grow box from this event alone, no ResizeObserver. Fired by the gesture only: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.
229
231
  - **Methods:** `placementAt(clientX, clientY, child?)` → a viewport point as a placement in the content region — `{ left, top }` in whole system px on the placement lattice: the body's corner, or under `scrollbars` the scrolled plane with the scroll offset already in it (the built-in area's own `placementAt`). The pair a child dropped into the window is written with; with `child`, the pair to write to that element, its `origin` offset folded in. `measure()` → the built-in scroll area's `measure()`, a no-op without `scrollbars`; the area re-measures by itself on its content's box, its slot and every placement write (`vf-placement-change`, §2), so this is the escape hatch for a range that changes with none of those. `show(options?)` → `Promise<boolean>`: clears `hidden`, and with `options.from` — a box in viewport CSS px (`{ left, top, right, bottom }`; a `DOMRect` fits, a zero-size box is a point), such as `vf-icon.cellRect()` — opens the window from it with the zoom rects. The window knows nothing about icons: `from` is any box. 14 rects, rect k at the fraction 0.7^(14−k) of the way from the box to the window's border box — each edge `from + trunc((frame − from) · t)`, so they bunch at the box and the last stops at 0.7 — four on screen at a time, 17 ms per step, then four steps draining; the window draws after the eighteenth (`WINDOW_RECT_*`, `windowRectFraction`, src/motion.ts). Each rect is a canvas of its own on the desktop's drag surface (the `vf-drag-surface-request` handshake), framed once with the drag outline's pen (`xorPenCanvas`, `paintSelectionRect`, src/open-art.ts) and dotted at the screen phase (`penPhase` — black lines over the dither), and a Web Animation (`element.animate()`) shows it for its four steps, every one padded with `endDelay` to the run's length so all of them finish when the window draws. Where two rects share a pixel the later one inverts it back: each canvas blends over those beneath it. Same-task writes to `left`/`top`/`width`/`height`, before or after the call, land first, so the rects end on the frame the page set. Until the rects drain the window matches `:state(opening)` and has `opacity: 0` — laid out, focusable and a hit target, so a page can focus into it at once and a press lands on it — never `visibility`, which would drop focus and pass the press through. A press anywhere or Escape (consumed in the capture phase) finishes the rects' animations (`finish()`), as they finish the walk; a second `show()` finishes the first, as it finishes a `hide()`'s; a removal, or a `hide()` before the window draws, cancels them (`cancel()`) and resolves `false`, while a move — `vf-desktop` re-inserting a raised window — is not a removal (the disconnect checks `isConnected` a microtask later, and the reconnect re-attaches the finishers). Resolves once the window draws: `true` when the rects ran, `false` when it showed at once — no `from`, `prefers-reduced-motion`, no `vf-desktop` on its path, nothing of the rects on the screen, or not connected. Raises and activates nothing; a window already on screen goes blank for the run, so a page brings an open window forward with `bringToFront` instead. `npm run verify:window` (OPEN). `hide(options?)` → `Promise<boolean>`: sets `hidden`, and with `options.to` — a box as for `show` — closes the window to it: the window's border box is read at the call, the window hidden, and the same 14 rects run the other way, rect k shown from step 13 − k, so the trail starts at the rect nearest the frame, closes in on the box and drains into it, eighteen steps in all. A press anywhere or Escape finishes them, a `show()` finishes them and shows the window, and a second `hide()` finishes the first. Removing the window leaves them running — they are the desktop's, and the window is already gone — so a page can remove a closed window straight after the call. A `show()` still opening is taken down (cancelled, so it resolves `false`) and the window hides at once. Resolves once the rects are done: `true` when they ran, `false` when the window hid at once — no `to`, `prefers-reduced-motion`, no `vf-desktop` on its path, nothing of the rects on the screen, not connected, already hidden, or still opening. `npm run verify:window` (CLOSE).
@@ -234,7 +236,7 @@ The desktop-window shell: the classic document window (see DragThing screenshot)
234
236
  #### `vf-dialog` (`VfDialog`, vf-dialog.ts)
235
237
  The modal-dialog shell: movable modal by default (the dBoxProc double frame with the striped title bar set into it — traced from a 2× System 7 capture of a movable modal), the modal dialog box with `frame="plain"` (the bare double frame; see the Group A recipe table).
236
238
  - **Attributes/props:** `open: boolean` (reflect), `heading: string`, `top: number` / `left: number` (whole system px, in **viewport** coordinates — see §1 Explicit placement; unset means centered), `width: number` / `height: number` (**declare them both** — whole system px, the same fixed box `vf-window` is. A native `<dialog>` is `width: fit-content` measured against the space left beside its own offsets, and stating an offset is how the movable modal is positioned, so an undeclared dialog squeezes itself and reflows as it is dragged toward an edge. The two fall back differently — width to 260 system px, height to the content — and it names whichever are missing, once, on the open that first shows it), `label: string` (accessible name for a dialog with no `heading`), `closable: boolean` (default **false** — the bare movable-modal bar; the close box is opt-in because the HIG's Chapter 6 text denies a movable modal one while its Figure 5-1 grants it — the parameter enables either reading), `light-dismiss: boolean` (`lightDismiss`; default **false** — a click outside the frame closes the dialog with `vf-close` reason `'outside'`. Off by default because the classic modal ignored an outside click, and a dialog that asks a question should keep ignoring it; opt in for the About box and the splash, which the classic Mac did dismiss on a click. Reflected, and read at the release, so it can be toggled on an open dialog), `frame?: 'plain'`, `returnValue: string` (property only, as on `<dialog>`: the dialog's answer, cleared on each open).
237
- - **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. **Answers** (`VfModalDialog`): `close(value)` closes with `value` as `returnValue`, and a `<form method="dialog">` inside the dialog closes it on submit with the submitter's `value`, a submitter's `formmethod="dialog"` too. The platform's own rule looks for a `<dialog>` around the form, and this one is in the shadow root, so the base listens for `submit` on the host and closes at the end of the submit's path (`deferActivation`), not at all if anything cancelled it. `vf-button`'s submit proxy carries its `value` with or without a `name`. `vf-close`'s detail carries `returnValue`: the value that close carried, or null (Escape, a click outside, the close box, `close()` alone, removal). Return in a text field runs the form's implicit submission through the ringed `variant="default"` submit button when the form has one, ahead of the first in tree order, so a Cancel/OK row answers OK (`requestImplicitSubmit`). `npm run verify:dialog`, ANSWERS. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Keyboard: the Dialog Manager's two rules** (`VfModalDialog`). *Initial focus:* right after `showModal()`'s own focusing steps, focus is handed to `initialFocusTarget` — the first slotted control carrying `autofocus` or `autoselect`, else the first enabled text-entry control (`vf-text-field`, `vf-number-field`, `vf-text-area`, or a native text input/textarea), else the default button (`defaultButton`: the first enabled `vf-button[variant="default"]`, looked for among the slotted content and then the shadow tree), else nothing, leaving the browser's pick. A field marked `autoselect` has its whole text selected on every open, right after `vf-show` and once the field has rendered, so a value set before `show()` or in a `vf-show` handler is the text selected. Otherwise the modal selects no text: whether a field opens selected is the page's call. The classic dialog put the insertion point in its first text item and gave nothing else focus; the web needs a focused control, and the default button is the one that makes Return and Space work without a Tab. Left alone, `showModal()` focuses the first focusable thing in flat-tree order — Cancel in a Cancel/OK row, or a link in the body — so Return pressed the wrong thing. Both getters are `protected`, for a consumer's own modal to override. *Return/Enter:* a `keydown` listener on the `<dialog>`, bubble phase, attached while open with the light-dismiss trio. An uncancelled, unmodified Enter activates `defaultButton` (its `click()`, so a `type="submit"` default runs its proxy path) and cancels the keydown, which is what keeps a focused Cancel from firing itself — Space presses the focused control, as on the classic Mac. Anything that took Enter for itself cancelled the keydown first and is left alone: `vf-select`'s list, `vf-list`, an icon's rename, and a text field whose form ran its implicit submission (`requestImplicitSubmit` now reports whether a form owner took the press, and the fields cancel on true — so the submission runs once, not once per layer). A link keeps its own Enter. A multi-line editor (`<textarea>`, contenteditable) keeps Return for the newline; only `code === 'NumpadEnter'` fires the button there — the split a TextEdit item drew between the two keys. With no enabled default button the press is left native. Escape is unchanged (the `cancel` path above). **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **Opened again before a close's `close` event comes** — the native event is queued, so a `close()` and a `show()` in one turn do it — the open runs the funnel for that close first: its `vf-close` comes before the open's `vf-show`, and the late event finds the modal open and changes nothing, where it used to close the dialog that had just opened. That `vf-close` fires inside `show()`, so a one-time listener for the open's own answer goes on after `show()` (the shell's `ask()` does). The open drops any close value, reason or keys still pending, so a close asked for in that `vf-close` leaves nothing for a later one. `npm run verify:dialog`, REOPEN. **The placement re-settles while open** whenever the dialog's own box resizes — slotted content upgrading after `showModal()`, `--vf-scale` moving under zoom — the viewport resizes, or the scale changes. What survives depends on how the modal got where it is: an unplaced one re-centers (it never claimed a spot, and the stranded case is exactly this one), while a dragged or authored origin is kept and only re-clamped on screen.
239
+ - **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. **Answers** (`VfModalDialog`): `close(value)` closes with `value` as `returnValue`, and a `<form method="dialog">` inside the dialog closes it on submit with the submitter's `value`, a submitter's `formmethod="dialog"` too. The platform's own rule looks for a `<dialog>` around the form, and this one is in the shadow root, so the base listens for `submit` on the host and closes at the end of the submit's path (`deferActivation`), not at all if anything cancelled it. `vf-button`'s submit proxy carries its `value` with or without a `name`. `vf-close`'s detail carries `returnValue`: the value that close carried, or null (Escape, a click outside, the close box, `close()` alone, removal). Return in a text field runs the form's implicit submission through the ringed `variant="default"` submit button when the form has one, ahead of the first in tree order, so a Cancel/OK row answers OK (`requestImplicitSubmit`). `npm run verify:dialog`, ANSWERS. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Keyboard: the Dialog Manager's two rules** (`VfModalDialog`). *Initial focus:* right after `showModal()`'s own focusing steps, focus is handed to `initialFocusTarget` — the first slotted control carrying `autofocus` or `autoselect`, else the first enabled text-entry control (`vf-text-field`, `vf-number-field`, `vf-text-area`, or a native text input/textarea), else the default button (`defaultButton`: the first enabled `vf-button[variant="default"]`, looked for among the slotted content and then the shadow tree), else nothing, leaving the browser's pick. A field marked `autoselect` has its whole text selected on every open, right after `vf-show` and once the field has rendered, so a value set before `show()` or in a `vf-show` handler is the text selected. Otherwise the modal selects no text: whether a field opens selected is the page's call. The classic dialog put the insertion point in its first text item and gave nothing else focus; the web needs a focused control, and the default button is the one that makes Return and Space work without a Tab. Left alone, `showModal()` focuses the first focusable thing in flat-tree order — Cancel in a Cancel/OK row, or a link in the body — so Return pressed the wrong thing. Both getters are `protected`, for a consumer's own modal to override. *Return/Enter:* a `keydown` listener on the `<dialog>`, bubble phase, attached while open with the light-dismiss trio. An uncancelled, unmodified Enter activates `defaultButton` (its `click()`, so a `type="submit"` default runs its proxy path) and cancels the keydown, which is what keeps a focused Cancel from firing itself — Space presses the focused control, as on the classic Mac. Anything that took Enter for itself cancelled the keydown first and is left alone: `vf-select`'s list, `vf-list`, an icon's rename, and a text field whose form ran its implicit submission (`requestImplicitSubmit` now reports whether a form owner took the press, and the fields cancel on true — so the submission runs once, not once per layer). A link keeps its own Enter. A multi-line editor (`<textarea>`, contenteditable) keeps Return for the newline; only `code === 'NumpadEnter'` fires the button there — the split a TextEdit item drew between the two keys. With no enabled default button the press is left native. Escape is unchanged (the `cancel` path above). **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **Opened again before a close's `close` event comes** — the native event is queued, so a `close()` and a `show()` in one turn do it — the open runs the funnel for that close first: its `vf-close` comes before the open's `vf-show`, and the late event finds the modal open and changes nothing, where it used to close the dialog that had just opened. That `vf-close` fires inside `show()`, so a one-time listener for the open's own answer goes on after `show()` (system-online-sdk's `ask()` does). The open drops any close value, reason or keys still pending, so a close asked for in that `vf-close` leaves nothing for a later one. `npm run verify:dialog`, REOPEN. **The placement re-settles while open** whenever the dialog's own box resizes — slotted content upgrading after `showModal()`, `--vf-scale` moving under zoom — the viewport resizes, or the scale changes. What survives depends on how the modal got where it is: an unplaced one re-centers (it never claimed a spot, and the stranded case is exactly this one), while a dragged or authored origin is kept and only re-clamped on screen.
238
240
  - **Visual (default chrome):** `vfModalFrame` with `vfTitleBar` set into it (§4) — the bar is literally the recipe `vf-window` uses, so it is identical by construction (stripes + centered title) rather than by matching copies; the frame is the modal double frame, **not** the window's shadowed `vfChromeFrame`. Outside → in: 1px outer rule; the 18px bar directly under it, 2px in at either end with its stripes starting 2px from the rule; the 2px band as the bar's floor (the bar's rule + the inner box's 1px top border); 2px gap + 2px band down the sides and along the bottom; no shadow. The body begins 20px below the dialog's top and 5px inside its other three edges. The bar takes the default `--vf-title-inset` (16px) — 60px when `closable`, the same clearance as `vf-window`, since the centered title needs symmetric room for the widget — and sets `touch-action: none` unconditionally, having no immovable state. `closable` renders the shared close box (`vfWindowWidgets` + `closeBox()` — byte-identical to `vf-window`'s, per the `moveable modal dialog.png` reference). Body is WHITE (`--vf-surface: #fff`) and is the content region, with no inset of its own: the positioning anchor, so a child placed with `top`/`left` measures from the frame's inner edge, and flow content starts at the same corner. Both chromes are full-height flex columns and the body takes the slack, for the same reason `vf-window`'s frame is: the declared `height` lands on the `<dialog>` (see `dialogSize`), and the recipes are skin only. The frame is the flex child of the `<dialog>` itself (`dialog[open]` is a flex column in `modalDialogStyles` — `[open]`-scoped, or it would out-cascade the UA's `dialog:not([open]) { display: none }`), not a `height: 100%` block: a percentage can't resolve against the undeclared-height dialog that only the UA's `dialog:modal` max-height caps, and that spill was how a viewport-tall modal stranded its buttons off-screen. **The box never grows, but over-stuffed content scrolls instead of clipping**: the body holds a `.content` scroll region (the default slot; part `content`). Placed children anchor to the body, outside that scroller, so they hold still while it scrolls. While the content fits, nothing matches. Once it overflows (`ScrollStateController`, the always-a-rail machinery), the region reserves the rail's 15px as its own right padding, wears the drawn `vfScrollRail` rail (§4) flush against the frame's inner band, which supplies the cell's outer line, and becomes a keyboard stop (`tabindex="0"`, `role="group"`, the kit's dotted ring) so the copy is scrollable without a pointer. The rail rides the body as an overlay pinned to its right edge, deliberately out of the layout flow: a rail *column*'s two fixed 15px arrow cells would hand the region a 32px minimum height, and a short dialog would then measure as fitting with the rail shown and overflowing without it — flip-flopping forever. The drop-open exemption is unchanged.
239
241
  - **The CSS and the controller state the same thing.** `.content` is `overflow-y: hidden` until the controller flags overflow, then `overflow-y: scroll`. It is deliberately never `auto`: the controller ignores the body face's negative half-leading (`LEADING_SPILL_SYS`, src/scroll-state.ts — `vf-paragraph` sets a 12-system-px line box under a 16-system-px em, so the inline box spills 2 inkless system px past the block box and `scrollHeight` counts it), and `auto` has no way to know that. With `auto` the two disagreed, and a fixed info dialog whose copy ends in a `vf-paragraph` rubber-banded under the wheel with no rail shown — 6 CSS px at scale 3. `hidden` still scrolls programmatically, so `scrollIntoView` on a focused control is unaffected. `npm run verify:contract`, OVERFLOW group.
240
242
  - **Visual (`frame="plain"`):** the same `vfModalFrame` (§4 — 1px outer, 2px gap, 2px inner band, no shadow, per `Windows/modal dialog.png`) with no title bar, and immovable like the original dBoxProc dialog (nothing renders a drag handle). The body begins 5px inside every edge. With no bar, `heading` only names the dialog and `closable` is ignored; a title in the body is the composer's, placed like any other item.
@@ -314,7 +316,7 @@ The color-swatch button: a well of solid color — a palette cell.
314
316
  - **Attributes/props:** `value`, `placeholder`, `disabled`, `readonly`, `autoselect`, `type: string` (default `'text'`; pass through to input), `name`. The input-behavior attributes — `autocomplete`, `inputmode`, `enterkeyhint`, `maxlength`, `pattern`, `spellcheck`, `autocapitalize` — are **forwarded verbatim** from the host onto the inner input (observed attributes, not reactive properties: four of them are globals with IDL accessors already on `HTMLElement`, and a Lit `@property` would shadow the platform member — the `align`/`draggable` trap). The platform only honors them on the element that actually takes the input, which is in the shadow root.
315
317
  - **Visual:** inner `<input>`: white bg, `1px solid black`, NO radius, height `var(--vf-control-height, 22px)`, `padding: 0 6px`, font tokens but `font-weight: var(--vf-font-weight, 700)`. `user-select: text`. Focus: for a **keyboard** focus, the kit's 1px dashed rule (`vfFocusUnderline`, §4) one blank system px row under the well — no dotted outline and no thickened border. A click leaves it unmarked: the insertion point is already the answer to where focus went (see §4 on why this can't be `:focus-visible`). Disabled: the text dims to gray; the black border stays. Selected text inverts to solid black-on-white (`.vf-field::selection`, using `--vf-highlight`/`--vf-highlight-text`) — the 1-bit System 7 selection, shared by all three editable fields via the `vfField` skin.
316
318
  - **Structure:** the `<input>` sits in a `.vf-field-well` wrapper, which is what the focus rule hangs from and what carries `vf-snap`. A replaced element draws no pseudo-element of its own, and the host is never the thing grid snapping moves, so the wrapper is the only box that is both the well's exact shape and on the corrected grid. Same wrapper in all three fields, assembled by `VfTextControlBase.wellClass` so the focus gate can't drift between them. Two consequences when embedding: the rule paints 2 system px **below the host's own box** (`pointer-events: none`, so it never takes a click meant for what sits under it, but a tight `overflow: hidden` ancestor clips it), and a `width` set on `::part(input)` sizes the control without moving the wrapper the rule spans — a field's width belongs on the host or on `--vf-field-width`.
317
- - **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. `select()` selects the whole value, as a native field's does, on the inner input; with a new value not yet rendered it selects once the value is in, so `field.value = x; field.select()` selects `x`. `focus()` focuses the inner input and keeps its caret (Chromium selects all of an input's text when a host's `delegatesFocus` hands the focus on). `autoselect` is `autofocus` plus a selection: the first control carrying either takes the opening focus of its `vf-dialog`, which selects the field's whole text on every open, or of a window the shell's window manager opens, which selects it on the window's first open only (SHELL.md). A click still places the caret. A commit — native `change`, leaving the field, or Return in a form — is announced once: a commit that brings nothing new since the field took focus or since the last commit fires nothing. In a form, Return commits first, then runs the form's implicit submission, the order a native input keeps, so `change` comes before the form's `submit`. That submission works the way HTML defines it — activating the form's default button (first submit button in tree order, `vf-button` included), so the submission carries a real `submitter` and that button's `name`/`value`, a disabled default button submits nothing, and only a form with no submit button falls back to a bare `requestSubmit()` (`requestImplicitSubmit`, text-control.ts). When a form owner took the press the keydown is cancelled, so an enclosing `vf-dialog` does not route the same Enter to its default button a second time; with no form the press goes on to the dialog, whose default button it activates.
319
+ - **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. `select()` selects the whole value, as a native field's does, on the inner input; with a new value not yet rendered it selects once the value is in, so `field.value = x; field.select()` selects `x`. `focus()` focuses the inner input and keeps its caret (Chromium selects all of an input's text when a host's `delegatesFocus` hands the focus on). `autoselect` is `autofocus` plus a selection: the first control carrying either takes the opening focus of its `vf-dialog`, which selects the field's whole text on every open, or of a window system-online-sdk's window manager opens, which selects it on the window's first open only (that SDK's docs/SHELL.md). A click still places the caret. A commit — native `change`, leaving the field, or Return in a form — is announced once: a commit that brings nothing new since the field took focus or since the last commit fires nothing. In a form, Return commits first, then runs the form's implicit submission, the order a native input keeps, so `change` comes before the form's `submit`. That submission works the way HTML defines it — activating the form's default button (first submit button in tree order, `vf-button` included), so the submission carries a real `submitter` and that button's `name`/`value`, a disabled default button submits nothing, and only a form with no submit button falls back to a bare `requestSubmit()` (`requestImplicitSubmit`, text-control.ts). When a form owner took the press the keydown is cancelled, so an enclosing `vf-dialog` does not route the same Enter to its default button a second time; with no form the press goes on to the dialog, whose default button it activates.
318
320
  - **Parts:** `input`.
319
321
  - **Events:** `vf-input` detail `{ value }` on every keystroke; `vf-change` detail `{ value }` on commit. Plus the native pair per §2: the inner input's own composed `input` crosses the boundary itself; `change` is re-dispatched from the host.
320
322
 
@@ -251,7 +251,7 @@
251
251
  },
252
252
  {
253
253
  "name": "vf-desktop",
254
- "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. A press on a title bar with ⌘ held\nraises nothing, so a background window is dragged where it stands in the\nstack. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
254
+ "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. A press on a title bar with ⌘ held\nraises nothing, so a background window is dragged where it stands in the\nstack. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the drag outline, the full-screen tier and the screen-corner\nmask are in front of it.\n\n**The full-screen tier.** fullScreenWindow puts one slotted window\nabove everything else the screen shows — both window tiers, the menu bar\nand its dropped menus, the drag outline and the rects — and under the\ncorner mask alone: the tier a window takes the whole screen on. The page\nsets it and clears it, and sizes, draws and activates the window itself\n(`vf-window`'s `frameless`, the screen's `width` and `height`,\nbringToFront); cleared, the window goes back to its tier where it\nstood.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
255
255
  "attributes": [
256
256
  {
257
257
  "name": "width",
@@ -966,7 +966,7 @@
966
966
  },
967
967
  {
968
968
  "name": "autoselect",
969
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
969
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
970
970
  "values": []
971
971
  },
972
972
  {
@@ -1624,7 +1624,7 @@
1624
1624
  },
1625
1625
  {
1626
1626
  "name": "autoselect",
1627
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
1627
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
1628
1628
  "values": []
1629
1629
  },
1630
1630
  {
@@ -1701,7 +1701,7 @@
1701
1701
  },
1702
1702
  {
1703
1703
  "name": "autoselect",
1704
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
1704
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
1705
1705
  "values": []
1706
1706
  },
1707
1707
  {
@@ -1734,13 +1734,18 @@
1734
1734
  },
1735
1735
  {
1736
1736
  "name": "vf-window",
1737
- "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" closable movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close', altKey, shiftKey, metaKey, ctrlKey }`, the modifier keys held on the click (shape-compatible with vf-dialog's `vf-close`), so an Option-click can mean more than a click. The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
1737
+ "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" closable movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n\n**Frameless.** With `frameless` the window draws no chrome — no title bar\nor widgets, no frame or shadow, no grow box, no header or status strip —\nand the body is the whole box: a window that takes the whole screen, say,\non `vf-desktop`'s full-screen tier. The chrome is hidden, not torn down, so\nthe switch moves no node and the focus in the body stays where it is.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close', altKey, shiftKey, metaKey, ctrlKey }`, the modifier keys held on the click (shape-compatible with vf-dialog's `vf-close`), so an Option-click can mean more than a click. The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
1738
1738
  "attributes": [
1739
1739
  {
1740
1740
  "name": "variant",
1741
1741
  "description": "Chrome variant. Omit for the standard 18px striped bar; `'utility'` for\nthe slim windoid bar (dot-grid dither, 7×7 widgets, no title patch — the\nheading still names the widgets). Inside a `vf-desktop`, utility windows\nfloat above the document tier and never take the single-active state.",
1742
1742
  "values": [{ "name": "utility" }]
1743
1743
  },
1744
+ {
1745
+ "name": "frameless",
1746
+ "description": "Draw no chrome: no title bar, so no close or zoom box, no frame or\nshadow, no grow box, and no header or status strip, whatever their slots\nhold. The body is the whole box, `pattern` filling it and a placed child\nmeasured from its corner; under `scrollbars` the built-in scroll area\nfills the box, its own frame line on the box's edge. `heading` still\nnames the window. The chrome is hidden rather than left out of the\nrender, so switching this on a connected window moves no node and the\nfocus in the body stays where it is. In a `vf-desktop` the window keeps\nits tier for activation and `hidden`, and `show()` and `hide()` run\ntheir rects from and to its box.",
1747
+ "values": []
1748
+ },
1744
1749
  {
1745
1750
  "name": "heading",
1746
1751
  "description": "Title text shown centered in the title bar.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "vintage-frames",
4
- "version": "0.19.1",
4
+ "version": "0.20.1",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -577,7 +577,7 @@
577
577
  },
578
578
  {
579
579
  "name": "vf-desktop",
580
- "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. A press on a title bar with ⌘ held\nraises nothing, so a background window is dragged where it stands in the\nstack. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
580
+ "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. A press on a title bar with ⌘ held\nraises nothing, so a background window is dragged where it stands in the\nstack. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the drag outline, the full-screen tier and the screen-corner\nmask are in front of it.\n\n**The full-screen tier.** fullScreenWindow puts one slotted window\nabove everything else the screen shows — both window tiers, the menu bar\nand its dropped menus, the drag outline and the rects — and under the\ncorner mask alone: the tier a window takes the whole screen on. The page\nsets it and clears it, and sizes, draws and activates the window itself\n(`vf-window`'s `frameless`, the screen's `width` and `height`,\nbringToFront); cleared, the window goes back to its tier where it\nstood.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
581
581
  "doc-url": "",
582
582
  "attributes": [
583
583
  {
@@ -685,9 +685,14 @@
685
685
  },
686
686
  {
687
687
  "name": "stackingOrder",
688
- "description": "The slotted windows in stacking order, bottom-most first: the document\ntier, then the floating tier above it, each in the order its windows\nwere last raised. Hidden windows are included where they stand. This is\nthe order the eye sees; the windows' light-DOM order catches up with it\nonly once a gesture ends, and a raise by keyboard focus moves no node.",
688
+ "description": "The slotted windows in stacking order, bottom-most first: the document\ntier, then the floating tier above it, each in the order its windows\nwere last raised, and the window on the full-screen tier last. Hidden\nwindows are included where they stand. This is the order the eye sees;\nthe windows' light-DOM order catches up with it only once a gesture\nends, a raise by keyboard focus moves no node, and the full-screen\nwindow keeps the light-DOM place of where it stood.",
689
689
  "type": "HTMLElement[]"
690
690
  },
691
+ {
692
+ "name": "fullScreenWindow",
693
+ "description": "The window on the full-screen tier, or null. A slotted window set here\nstacks above everything else the screen shows — both window tiers, the\nmenu bar and any dropped menu, the drag outline and the window rects —\nand under the corner mask alone, so the screen keeps its rounded\ncorners: the tier for a window that takes the whole screen. Sizing it to\nthe screen and drawing it without chrome (`vf-window`'s `frameless`) are\nthe page's, and so is bringing it forward: setting this activates\nnothing. One window holds the tier at a time: setting another puts the\nfirst back, and null puts the holder back in its own tier where it\nstood — a raise meanwhile moves that place, as it would have moved the\nwindow. A holder that leaves the desktop gives the tier up. Anything but\na window slotted here warns and changes nothing.",
694
+ "type": "HTMLElement | null"
695
+ },
691
696
  {
692
697
  "name": "top",
693
698
  "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
@@ -2217,7 +2222,7 @@
2217
2222
  },
2218
2223
  {
2219
2224
  "name": "autoselect",
2220
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
2225
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
2221
2226
  "value": { "type": "boolean", "default": "false" }
2222
2227
  },
2223
2228
  {
@@ -2318,7 +2323,7 @@
2318
2323
  },
2319
2324
  {
2320
2325
  "name": "autoselect",
2321
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
2326
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
2322
2327
  "type": "boolean"
2323
2328
  },
2324
2329
  {
@@ -3790,7 +3795,7 @@
3790
3795
  },
3791
3796
  {
3792
3797
  "name": "autoselect",
3793
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
3798
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
3794
3799
  "value": { "type": "boolean", "default": "false" }
3795
3800
  },
3796
3801
  {
@@ -3881,7 +3886,7 @@
3881
3886
  },
3882
3887
  {
3883
3888
  "name": "autoselect",
3884
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
3889
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
3885
3890
  "type": "boolean"
3886
3891
  },
3887
3892
  {
@@ -3997,7 +4002,7 @@
3997
4002
  },
3998
4003
  {
3999
4004
  "name": "autoselect",
4000
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
4005
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
4001
4006
  "value": { "type": "boolean", "default": "false" }
4002
4007
  },
4003
4008
  {
@@ -4088,7 +4093,7 @@
4088
4093
  },
4089
4094
  {
4090
4095
  "name": "autoselect",
4091
- "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window the shell's window manager opens. A dialog\nselects the text on every open, a window on its first open only. A click\nstill places the caret.",
4096
+ "description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
4092
4097
  "type": "boolean"
4093
4098
  },
4094
4099
  {
@@ -4159,7 +4164,7 @@
4159
4164
  },
4160
4165
  {
4161
4166
  "name": "vf-window",
4162
- "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" closable movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close', altKey, shiftKey, metaKey, ctrlKey }`, the modifier keys held on the click (shape-compatible with vf-dialog's `vf-close`), so an Option-click can mean more than a click. The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
4167
+ "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" closable movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n\n**Frameless.** With `frameless` the window draws no chrome — no title bar\nor widgets, no frame or shadow, no grow box, no header or status strip —\nand the body is the whole box: a window that takes the whole screen, say,\non `vf-desktop`'s full-screen tier. The chrome is hidden, not torn down, so\nthe switch moves no node and the focus in the body stays where it is.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close', altKey, shiftKey, metaKey, ctrlKey }`, the modifier keys held on the click (shape-compatible with vf-dialog's `vf-close`), so an Option-click can mean more than a click. The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
4163
4168
  "doc-url": "",
4164
4169
  "attributes": [
4165
4170
  {
@@ -4167,6 +4172,11 @@
4167
4172
  "description": "Chrome variant. Omit for the standard 18px striped bar; `'utility'` for\nthe slim windoid bar (dot-grid dither, 7×7 widgets, no title patch — the\nheading still names the widgets). Inside a `vf-desktop`, utility windows\nfloat above the document tier and never take the single-active state.",
4168
4173
  "value": { "type": "'utility' | undefined" }
4169
4174
  },
4175
+ {
4176
+ "name": "frameless",
4177
+ "description": "Draw no chrome: no title bar, so no close or zoom box, no frame or\nshadow, no grow box, and no header or status strip, whatever their slots\nhold. The body is the whole box, `pattern` filling it and a placed child\nmeasured from its corner; under `scrollbars` the built-in scroll area\nfills the box, its own frame line on the box's edge. `heading` still\nnames the window. The chrome is hidden rather than left out of the\nrender, so switching this on a connected window moves no node and the\nfocus in the body stays where it is. In a `vf-desktop` the window keeps\nits tier for activation and `hidden`, and `show()` and `hide()` run\ntheir rects from and to its box.",
4178
+ "value": { "type": "boolean", "default": "false" }
4179
+ },
4170
4180
  {
4171
4181
  "name": "heading",
4172
4182
  "description": "Title text shown centered in the title bar.",
@@ -4302,6 +4312,11 @@
4302
4312
  "description": "Chrome variant. Omit for the standard 18px striped bar; `'utility'` for\nthe slim windoid bar (dot-grid dither, 7×7 widgets, no title patch — the\nheading still names the widgets). Inside a `vf-desktop`, utility windows\nfloat above the document tier and never take the single-active state.",
4303
4313
  "type": "'utility' | undefined"
4304
4314
  },
4315
+ {
4316
+ "name": "frameless",
4317
+ "description": "Draw no chrome: no title bar, so no close or zoom box, no frame or\nshadow, no grow box, and no header or status strip, whatever their slots\nhold. The body is the whole box, `pattern` filling it and a placed child\nmeasured from its corner; under `scrollbars` the built-in scroll area\nfills the box, its own frame line on the box's edge. `heading` still\nnames the window. The chrome is hidden rather than left out of the\nrender, so switching this on a connected window moves no node and the\nfocus in the body stays where it is. In a `vf-desktop` the window keeps\nits tier for activation and `hidden`, and `show()` and `hide()` run\ntheir rects from and to its box.",
4318
+ "type": "boolean"
4319
+ },
4305
4320
  {
4306
4321
  "name": "heading",
4307
4322
  "description": "Title text shown centered in the title bar.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintage-frames",
3
- "version": "0.19.1",
3
+ "version": "0.20.1",
4
4
  "description": "Lit web components that emulate the look and feel of classic Mac OS System 7",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,21 +38,6 @@
38
38
  "types": "./dist/components/vf-*.d.ts",
39
39
  "import": "./dist/components/vf-*.js"
40
40
  },
41
- "./shell": {
42
- "types": "./dist/shell/index.d.ts",
43
- "import": "./dist/shell/index.js"
44
- },
45
- "./shell/pure": {
46
- "types": "./dist/shell/pure.d.ts",
47
- "import": "./dist/shell/pure.js"
48
- },
49
- "./build": {
50
- "types": "./dist/build/index.d.ts",
51
- "import": "./dist/build/index.js"
52
- },
53
- "./build/client": {
54
- "types": "./dist/build/client.d.ts"
55
- },
56
41
  "./package.json": "./package.json"
57
42
  },
58
43
  "files": [
@@ -76,15 +61,12 @@
76
61
  "build:pages": "vite build --config vite.pages.config.ts",
77
62
  "preview:pages": "vite preview --config vite.pages.config.ts",
78
63
  "prepack": "npm run build && npm run verify:manifest",
79
- "preversion": "npm run check:app-api",
80
- "version": "npm run analyze && node scripts/write-version.mjs && git add custom-elements.json editor app-api.json src/shell/version.ts",
81
- "analyze": "cem analyze && node scripts/app-api.mjs",
82
- "check:app-api": "npm run analyze && node scripts/check-app-api.mjs",
64
+ "version": "npm run analyze && git add custom-elements.json editor",
65
+ "analyze": "cem analyze",
83
66
  "verify:manifest": "node scripts/verify-manifest.mjs",
84
67
  "typecheck": "tsc --noEmit",
85
68
  "preview": "vite preview",
86
69
  "embed:cursors": "node scripts/embed-cursor-art.mjs",
87
- "embed:frame": "node scripts/embed-frame.mjs",
88
70
  "verify:buttons": "tsc src/pixel-frame.ts --outDir scripts/.tmp --target es2022 --module esnext --moduleResolution bundler --skipLibCheck && node scripts/emit-clips.mjs && python3 scripts/verify-button-clips.py",
89
71
  "shot:buttons": "npx playwright screenshot --wait-for-timeout=2000 http://localhost:5173/pixel-test.html shots/buttons.png",
90
72
  "shot:verify": "python3 scripts/verify-button-screenshot.py shots/buttons.png",
@@ -128,23 +110,11 @@
128
110
  "verify:snap": "node scripts/verify-snap.mjs",
129
111
  "verify:contract": "node scripts/verify-contract.mjs",
130
112
  "verify:names": "node scripts/verify-names.mjs",
131
- "verify:forced-colors": "node scripts/verify-forced-colors.mjs",
132
- "verify:shell-unit": "tsc -p tsconfig.unit.json && node --test --test-reporter=tap \"test/*.test.mjs\"",
133
- "verify:shell-front": "node scripts/verify-shell-front.mjs",
134
- "verify:shell-windows": "node scripts/verify-shell-windows.mjs",
135
- "verify:shell-finder": "node scripts/verify-shell-finder.mjs"
113
+ "verify:forced-colors": "node scripts/verify-forced-colors.mjs"
136
114
  },
137
115
  "dependencies": {
138
116
  "lit": "^3.2.0"
139
117
  },
140
- "peerDependencies": {
141
- "vite": ">=8.0.0"
142
- },
143
- "peerDependenciesMeta": {
144
- "vite": {
145
- "optional": true
146
- }
147
- },
148
118
  "devDependencies": {
149
119
  "@custom-elements-manifest/analyzer": "^0.11.0",
150
120
  "@types/node": "^24.19.1",
@@ -1,21 +0,0 @@
1
- /**
2
- * Writing app files. The box's own chunks are copied as they are, so the
3
- * picture is never re-encoded; any app chunks already there are dropped,
4
- * and the manifest's and the code's go in before IEND.
5
- */
6
- import type { AppManifest } from '../shell/app-file.js';
7
- /** What {@link packApp} packs. */
8
- export interface PackAppInput {
9
- /** The manifest's fields but its format, app API and icon, which the packer fills in. */
10
- manifest: Omit<AppManifest, 'format' | 'api' | 'icon'>;
11
- /** The icon's PNG, 32 × 32. */
12
- icon: Uint8Array;
13
- /** The application's code: one ES module whose default export is its factory. */
14
- code: string;
15
- /** The artwork's PNG, for the box. Without it the icon is stamped there. */
16
- artwork?: Uint8Array;
17
- }
18
- /** `box` with `manifest` and `code` in its chunks. */
19
- export declare function writeAppFile(box: Uint8Array, manifest: AppManifest, code: string): Promise<Uint8Array>;
20
- /** The whole app file: the box in the kit's frame, then the manifest and the code. */
21
- export declare function packApp({ manifest, icon, code, artwork }: PackAppInput): Promise<Uint8Array>;