Commit graph

193 commits

Author SHA1 Message Date
CRT
7cd6c99007 Theme engine v3 step 10: the frame marker
installFrameMarker: framed parts inside the watched scopes get data-wb-void when nothing
in them renders and collapse (frameMarkerCss); a ResizeObserver per part and a
child-list/attribute observer on the scopes trigger the check, batched per frame. The
per-part 1.5 s polling of the X app's void cards is replaced.
2026-10-02 06:18:38 +07:00
CRT
306b6add47 Theme engine v3: component inks solved on the grounds they sit on
A part's text role is still swapped for a readable one on its own fill, and its ink is
then solved with inkOn: on its fill, or with no fill of its own on every surface it can
sit on, the page canvas included (Solarized Light's outline Post button in the rail:
accent on the page read 4.45:1). Roles made for a parent's fill (onAccent, onInverse,
bg) stand. Invariant suite: Solarized Light I1 107 -> 0.
2026-10-02 06:14:15 +07:00
CRT
898a14e510 host: no opener link handler in webviews (target=_blank clicks in site tabs were cancelled and never opened) 2026-10-02 06:11:38 +07:00
CRT
1f0e24f9c7 themeInvariants: controls are not empty frames (I6); a layer over the bar is not a bar hit-test failure (I4) 2026-10-02 06:02:30 +07:00
CRT
9ba52d6c87 Theme engine v3 step 9: the bar's label mode is measured
themeBar.ts: the sum of the whole labels (each in its own computed font, via canvas
measureText, so a hidden label still counts), icons, paddings and gaps against the
nav's room, with 16px hysteresis; writes html[data-wb-bar=labels|icons] on nav
resize, font loads and window resize. No width breakpoint decides it any more.
2026-10-02 05:58:34 +07:00
CRT
a9c48c5d35 Theme engine v3 step 8: the layer guard
themeGuards.ts: watches the site's layer container; every new popover (and one that the
site moves or that grows) is measured in an idle callback and shifted into the viewport
minus the bar insets with a translate (data-wb-clamped, --wb-clamp-x/y); taller than the
viewport it scrolls. Bounded: only nodes that hold a popover are measured, at most 4 per
pass; a moved popover is seen through its own style attribute and a ResizeObserver.
clampShift/viewportBox are pure and unit tested.
2026-10-02 05:46:52 +07:00
CRT
9606c2a86e Theme engine v3 step 5: SurfaceSet, the shell chrome reads it
themeDerive.ts: canvas, content, card, chrome, raised, panel, strong, accentFill and
tooltip, each with ink, inkSoft, link, line, hover, selected and focus solved on its
fill (both gradient ends). Hover and selected step back toward the fill until every
ink still reads on them; hover is neutral unless the theme sets one (no accent wash);
a strong gradient's end moves until one ink reads on both; targets no ink can reach
on a mid-tone fill are capped and noted. 5,000-palette property test.
chromeTheme.ts: legible() is the solver; the bevel title bar, selection and face inks
come from the strong and chrome surfaces. Chrome vars of the 13 built-ins: only the
faint/warn/danger inks move (closer to the theme colour, still 4.5:1).
2026-10-02 05:00:43 +07:00
CRT
e831d7a415 Theme engine v3 step 1: themeInvariants, the numeric invariant probe (I1-I8, I10)
A pure measurement over a rendered document (fixtures and live): text and icon contrast over the
painted backdrop, X colour leakage, truncated bar/nav/button labels, bar geometry and hit tests, X line
icons under a pack, empty and nested frames, popover bounds, target sizes. Grew out of theme-qa/probe.js.
2026-10-02 05:00:02 +07:00
CRT
038b08b2d1 Theme engine v3 step 4: inkOn, the contrast solver
themeInk.ts: OKLCH lightness search toward the pole with headroom, hue kept, chroma
cut only for the gamut; several fills (gradient ends, glass grounds); compositing.
reach() now runs on it (8% headroom when a colour has to move, for the site's tints
mixed from these surfaces). Property test: 5,000 random pairs reach 4.5:1 and 3:1;
the live 1.0.2 cases ("New" pill 1.19:1, HoverLabel 2.7:1, like pink 3.84:1) pass.
2026-10-02 04:56:04 +07:00
CRT
7fc23b1953 Store: status words follow the theme (ok/warn/danger ink tokens), with a token test
A literal #3ecf8e green read 1.03:1 on the Windows 98 silver card (theme QA r1). Status text now mixes
the status hue toward --wb-text; store.css may hold no literal text colours outside its token defaults.
2026-10-02 04:29:27 +07:00
CRT
c939fd2bc5 Icon names for X's newer rail items: history, studio, articles 2026-10-02 04:09:11 +07:00
CRT
18c034b9fd Shell: do not save the layout before the saved one is restored
A save timer queued before the async startup (settings, profiles, workspaces) finished fired on the still-empty dock and overwrote the stored layout, so a shell reload came back with one fresh Home tab. Saves now wait for layoutReady.
2026-10-02 04:03:23 +07:00
CRT
030d9e24d8 waybetter 0.7.0, create-waybetter 0.2.0 2026-10-02 02:57:36 +07:00
CRT
241cf4276d create-waybetter: the template's host command list gains menu_set 2026-10-02 02:35:49 +07:00
CRT
ce58f25f79 Native menu bar words in every catalog (42 wb.menu.* keys, macOS menu names per language); Media panel key is Cmd+Shift+M on macOS (Cmd+Shift+G is Find Previous), Ctrl+Shift+G elsewhere, shown in the palette and Settings from the platform 2026-10-02 02:12:17 +07:00
CRT
2639fbae5e shell test: unused parameter 2026-10-02 02:01:27 +07:00
CRT
062a203aea Native menu bar (macOS): the shell describes it (menu_set, shell-only), host validates and builds it, a chosen id goes back to the shell (native-menu); App/File/Edit/View/Account/Plugins/Window/Help from the in-app menu's state, words wb.menu.* 2026-10-02 02:00:15 +07:00
CRT
a365c00ef7 shell: menus readable under every theme; account rows no longer collapse in Windows 98
Owner, live 2026-10-02: the account switcher's rows overlapped (names and handles collided) and
"Not signed in yet" was unreadable. Causes: edges.css forced every menu button to 26px, which squashed the
two-line account rows; the status inks (#f0b14a signed out, #ff8a80 errors) and the hint ink (a 38% fade of
the secondary text) were Night Ink literals, 2.1 to 3.6:1 on other themes' menus. Now the forced height is
gone, status, hint and secondary inks are measured on the menu surface (the face in bevelled themes) at
4.5:1 (--wb-c-warn, --wb-c-danger-ink, --wb-c-faint, --wb-c-muted), every line of a selected bevelled row
takes the selection's ink, and wayBetter's own default hint ink is legible. Checked: every shell menu
(accounts, app menu, tab menu, workspaces, palette) under 14 themes with an overlap and contrast scan
(verification/design-pass/1.0.2/menus-shots.mjs); new frame test for the inks.
2026-10-02 01:43:36 +07:00
CRT
0fd945cdfe macOS: a visible pane whose real frame drifted from the cached placement (AppKit moved it behind our back) is placed again, with a diag line 2026-10-02 01:28:03 +07:00
CRT
8db79554e9 diag: keep the previous run's log as diag.prev.log 2026-10-02 01:12:33 +07:00
CRT
a1c4c38559 macOS: the red close button sends the window to the Dock and the app keeps running (Dock click restores, Cmd+Q quits); a pane shown after being parked is shown before it is sized and told to re-lay out (X drew an empty timeline) 2026-10-02 00:52:24 +07:00
CRT
4bc28f5927 README: make clip re-cut from the final launch film 2026-10-02 00:51:37 +07:00
CRT
c88b5e0b03 README: what wayBetter is, an architecture diagram, the npm create waybetter quick start with its real output, the packages, and wayBetter for X as the showcase (assets/readme: hero, SVGs, one muted looping clip) 2026-10-02 00:46:10 +07:00
CRT
034a2719b7 Theme icons: pixel-art glyphs render with crisp edges
A glyph made only of axis-aligned rectangles (M, h, v, z) is pixel art: its SVG gets
shape-rendering="crispEdges", so a 16-cell glyph stays sharp at sizes that are not a whole multiple
of its grid (X's rail draws 26px icons, the action bar 18.75px).
2026-10-01 16:59:03 +07:00
CRT
74b71c6e16 host: a pane's own theme wins over a theme preview
A preview (the Theme Builder's, developer mode's, or one an automation left on through theme_preview)
replaced every site tab's theme, pane themes included, also on reload; "Theme for this pane" then
showed its choice checked while the page kept the preview. Reproduced live: a preview of Windows 98
turned a Paper pane into Windows 98 until the preview ended. Now a pane with its own theme keeps it;
every other tab shows the preview, as before.
2026-10-01 16:51:42 +07:00
CRT
57b4e91ab8 host: tab_find doc names the Custom Highlight fallback 2026-10-01 16:19:12 +07:00
CRT
8c2404b4cc host: the drafts vault commands (draft_list/save/revisions/status, observation_list, data_export) are the host's (AppConfig::with_vault, commands::VAULT); the app takes the vault through vault_commands::lock 2026-10-01 16:17:30 +07:00
CRT
2e49577196 host: the drafts vault (CAS revisions, append-only history, archive, export, capture budget) with the app's VaultRules; feature vault 2026-10-01 16:08:40 +07:00
CRT
cbe5d1ce8d create-waybetter: the template's host command list gains tab_run_command (matches commands.rs again) 2026-10-01 16:07:58 +07:00
CRT
75d507ba8c host: AlertsConfig and ThemeConfig live in ungated modules, so the host compiles and tests without the tauri feature 2026-10-01 16:05:27 +07:00
CRT
13ac2827fe Theme icons: glyphs drawn whole in the icon's content box
Live 2026-10-01: X's search-field icon is a 28x16 box with 12px of padding; sized at 100% of that width a
pixel glyph lost its top and bottom and sat left of X's own icon. Glyphs are now sized with contain (a
scaled pack by the box's height) and placed in the content box, for masks and images alike.
2026-10-01 15:52:56 +07:00
CRT
48ed7c2d23 Locales: icon packs, install, Store tabs, area switch and the theme doctor in the remaining catalogs (47 languages, machine) 2026-10-01 14:57:53 +07:00
CRT
8a6ef635d2 Store: two catalog files from one source, so apps from before a kind keep a working Store
An app that does not know a catalog item's kind refuses the whole signed file, so
the icons and builtin kinds can never go into catalog.json. `waybetter store build`
now writes catalog.json (format 2: theme, plugin, strings only; what every app
reads today) and catalog.v3.json (format 3: every kind) from the same items;
`sign` and `verify` handle the pair; the rules refuse a format 2 file that lists a
newer kind (CLI, JSON Schema, host). The host reads catalog.v3.json first and falls
back to catalog.json only when the newer file is not there (a newer file that does
not verify is an error). An item of a kind a version does not know is skipped
instead of refusing the file, so the next new kind costs current apps nothing.
2026-10-01 14:41:29 +07:00
CRT
a444889daa shell: a pane's edges follow its own theme
themeStore marks every dock group whose theme (the pane's own, else the global one) is bevelled with
.wb-bevel; edges.css draws tabs, toolbar, address field, wB button and the settings panel per group.
The window-level parts (title bar, caption buttons, menus, palette, scrollbars) keep following the
global theme. A Windows 98 pane is bevelled inside a flat window, and a flat pane stays flat inside a
Windows 98 window.
2026-10-01 14:39:11 +07:00
CRT
bad1fb8b9c host: the popover host (webview, session, z-order, close rules) is the host's
popover_host owns the one popover webview of an app: creation (recreated above tabs created after
it), placement through popover::place, the session (seq, source tab, app meta flattened into the
JSON the page reads), the toggle-closed window, close on layout change, navigation (the app says
whether the context moved on), shell raise, and the popover_only / current caller checks. The app
passes a PopoverConfig (page url, open event, macOS ground). Tests: layout staleness and the
session JSON shape.
2026-10-01 14:33:10 +07:00
CRT
22544b8d91 Theme decor: selection colors with a bare ::selection
With an ancestor in front (:root:root ::selection) every node the site inserts cost extra style
recalculation: Terminal measured 4 elements per insertion on the theme matrix (6 after a bar theme,
over the budget of 5); a bare ::selection measures 2 (3 after a bar theme). The rule did not predate
the design pass's changes; it was in Terminal's decor from the start.
2026-10-01 14:26:29 +07:00
CRT
3876993bee host: popover geometry (card placement, label, constants) is the host's
Pure placement of a trusted local card under a button of a site tab, kept inside the tab, flipped
above when short, with the caret on the button; moved from the X app with its seven tests.
2026-10-01 14:26:25 +07:00
CRT
7091bca25d Store: Open With (files the OS hands over) and the icons and builtin catalog kinds
Open With: store_commands keeps each existing file the OS hands over (launch
argument, a second launch forwarded by single-instance with its working
directory, a macOS file:// open-URL) under a random id (Store::receive_file:
name says what it is, regular file, size cap, 8 kept, 15 minutes) and queues
LinkEvent.opened {id, name, kind}. The shell-only install_stage_opened {id}
reads that one file once and stages it exactly like a dropped file; it takes
an id only (testkit asserts that), so the shell cannot name a path. The shell
shows the same install screen; nothing installs without it. The NSIS template
lists the app under "Open with" for the declared extensions (OpenWithProgids)
and never makes it the default program, unlike Tauri's own macro.

Catalog kinds: icons (an icon pack, *.icons.json, at most 120 KB; validate_icons,
staged and committed as text for the shell, the install screen draws every icon,
.icons.json is no longer read as a theme) and builtin (a feature the app
already has, such as an area plugin: no url, sha256, size or file; never staged;
the Store view lists it as Built in with the app's own switch via the areas
prop). Rust: Kind, CatalogItem.check, verify_item, install links refuse builtin.
JS: storeModel, installController (requestOpened, icons adapter), InstallDialog,
StoreView, StoreSection (remove an icon pack, area switches), words. CLI and
JSON Schema: kinds, optional url/sha256/size on builtin, icons size cap; the
test schema validator learns not and if/then/else. docs/STORE.md.
2026-10-01 14:23:23 +07:00
CRT
1fae0a7236 host: the palette commands of site tabs are the host's (tab_commands, tab_run_command)
tab_commands keeps the list each site tab reported (validated by plugins::commands, accepted only
from a tab on the site's origin), clears it when a new document starts, tells the shell, and checks
the shell-only tab_run_command: only a command that tab reported, only while it is on the site.
tab_run_command joins commands::SHELL (guard checked by assert_host_guards); the app's tab_report
hands its `commands` to tab_commands::reported.
2026-10-01 14:22:00 +07:00
CRT
4a2e3850df String packs: rich-text entries, language conditions and word rewriting; theme doctor report; translate-mode words
- Rich sentences (a link or button inside text) can be replaced by data: the inventory gives a rich entry `slots`, a pack value places the site's own elements with {e0}, {e1}, ... (each once, the last may be left out; no markup, plural or other parameters). The engine returns a pieces + element order template; the page inventory carries `rich` slot counts.
- Packs may carry `when: { languages }` (applies only while the site shows one of them; `en` matches `en-GB`) and `rewrite: { words }` (whole-word rewording of the site's own text, messages keep their parameters' values; no sentences of the site in the pack). The hook tells the engine the site's language; a rewording pack makes the delivery carry the whole inventory.
- Apps can ship built-in packs (BuiltInPacks) delivered before installed ones; syncStringPacks hands the tabs a delivery at start only when it changed.
- Translate mode words are a function the app supplies (asked at each draw) and gain a `plural` word; host validates `when` and `rewrite` envelopes.
- Theme doctor: every finding says where (token path or CSS rule), what and how to fix, as catalog keys wb.doctor.*; the Theme Builder shows the full report; the import hint is translated. Doctor words are in the 17 supported catalogs.
- waybetter strings check describes conditions and rewrites.
2026-10-01 14:19:36 +07:00
CRT
b3feeb8991 host: keyword alert settings are the host's (AppConfig::with_alerts, commands::ALERTS)
alerts_commands keeps the owner's AlertSettings (saved in app data, validated by alerts), serves
the shell-only alerts_get / alerts_set (testkit::assert_alerts_guards) and builds the script that
hands the page runtime the keywords to watch. The app names the file and the runtime member
(AlertsConfig); store()/script() are the app's own writers (backup import).
2026-10-01 14:14:34 +07:00
CRT
69b67dbece shell: the active tab's mark and the palette's edge take the theme's accent instead of wayBetter's brush 2026-10-01 14:14:27 +07:00
CRT
b13849da99 macOS find: page matches drawn with the CSS Custom Highlight API (window.find showed nothing while the page was not the key view); --diag logs exit requests, window close/destroy and each native pane placement on macOS 2026-10-01 14:13:53 +07:00
CRT
6d6e6c5b62 host: site themes are the host's (AppConfig::with_themes, commands::THEME)
theme_commands serves theme_get / theme_set / theme_preview / theme_scopes_get / theme_scopes_set
(shell-only, each checks its caller first; testkit::assert_theme_guards) over the theme state the
host now keeps: the owner's theme, account and pane themes, the Theme Builder preview. The app
only names its built-in themes and the two files (ThemeConfig). apply() themes every site tab
(script, cached Google font bytes, native background); page_scripts()/tab_background() serve a new
document; set_theme/set_scopes/set_preview are the app's own writers (backup import, dev mode).
A font install or removal re-themes the tabs in the host itself.
2026-10-01 14:10:43 +07:00
CRT
fc4fdf171a shell: the window follows a theme's corners and, for a bevelled theme, its edges
Every radius in the chrome and the panel scales with the theme's own radius (--wb-c-rs = radius / 12,
0 to 1.5): a square theme gets a square window, a round one a rounder window; themes that keep the
site's colors keep wayBetter's own corners. A theme built from raised and sunken edges (ChromeTheme
edges: 'bevel') gets frame/edges.css on html[data-wb-edges=bevel], also from the first frame: a face
color under tabs, toolbar, menus and the panel, 98.css's four edge lines derived from that face,
the active title bar's gradient across the tab row, tabs as a tab control, small raised caption
buttons, a sunken address field, selected menu rows in the title bar's color, raised and default
buttons, etched group boxes and classic scrollbars in the panel.
2026-10-01 14:08:01 +07:00
CRT
b3f6960dc3 host: a restored window fits the monitor it opens on
A state saved on a larger or rearranged screen (2720x1800 at x=3347 on a smaller second monitor)
left the titlebar off-screen and the window could not be moved. After the window-state restore,
the window is moved inside, and shrunk to, the work area of the monitor it is mostly on (the
primary one when that monitor is gone). Maximized and full-screen windows are left to the OS.
window_fit::fit is pure and unit tested.
2026-10-01 11:29:35 +07:00
CRT
7a98146b73 shell: the app menu hangs from each pane's wB button; the titlebar logo is plain again
ShellMenu.trigger chooses the trigger: 'pane' (default; each pane's wB button opens the menu under
itself, lined up with its end edge and kept inside the window), 'button' (titlebar) or 'logo'.
Alt+F, F10, the palette and the account menu's last row open it under the focused pane's wB (the
first one on screen otherwise). The menu's Settings row and the other panel rows still open the
panel, and Ctrl+Shift+W still does. The logo is part of the drag region again, as before 613723e.
2026-10-01 11:22:41 +07:00
CRT
8a2df3d17d Theme engine: page flags instead of page-wide :has()
A theme rule gated by html:has(...) / body:not(:has(...)) made the browser
restyle the whole page for every node the site inserted. Measured live on
x.com under any theme: about 440 elements restyled per insertion, against 1.5
without a theme; with the gates gone, 2.5.

Sites name page states as selectors (ThemeRuntimeSite.flags). The engine
keeps html[data-wb-<name>] in step, checked once per DOM hub frame and also
inside the site's open shadow roots. It passes the flags that hold to css(),
so CSS for one state can be left out of the other. Changing flags rebuild the
theme (they are part of its render key). Tests: pageFlags, flagAttribute.
2026-10-01 10:42:45 +07:00
CRT
42a51390bd privacy-scan test: a neutral example context for the allowlist 2026-10-01 06:03:36 +07:00
CRT
b3f160e4c1 Theme in shadow roots: read the site's primitives inside the root
Live on X's chat embed: --x-white is declared only inside the embed's root, so
the document's values left .text-white unresolved and the sent bubble's label
stayed white on a light accent (2:1 under Nord). Each root's theme now reads
the site's primitive variables from the root's own content (our sheet off
while reading), falling back to the document's.
2026-10-01 05:34:23 +07:00