- 245 wb.* messages for the shell frame (tabs, address bar, new-tab page, palette, account
switcher, decks, workspaces, chips, media panel, pane themes, backup preview), moved with every
translation from wayBetter for X's catalogs; `{site}` stands where the English said "X" (10
keys; the app passes "X", so X reads the same). English source: shell/src/frame/frameMessages.ts.
- wb.install.source.store|link|file: where an install comes from, translated in every locale
(native code's label is English only).
- runtime/tests/i18n.test.ts: en.json is the kit, shell-model, store and shell-frame source.
The generated desktop.rs is config + SiteHooks + its own commands; build.rs uses
commands::manifest; capability files grant the host's commands; tests/security.rs uses testkit;
scripts/build.mjs uses buildRuntime; waybetter.release.json and release scripts (placeholder URLs).
A test keeps HOST_COMMANDS in step with commands.rs.
release: signed NSIS/universal-macOS releases and latest.json (merge, built-in shape check plus the
app's own validator), upload steps printed only, configured by the app's waybetter.release.json;
the key is read only into the build environment or passed to the Tauri signer by path.
build: buildRuntime() bundles a page runtime IIFE with the app's esbuild (?inline CSS as text,
catalog strings limited to the runtime's prefixes). esbuild is an optional peer.
tab-bridge.js (moved from wayBetter for X) is served by bridge::SOURCE,
or by bridge::script(BridgeOptions) with shortcut and link-drag
forwarding switched off for a shell that does not handle them (a
forwarded shortcut is taken from the page). Tests: one options line,
only tab_report/tab_shortcut/tab_link_drag, every forwarded shortcut is
one tabs::valid_shortcut accepts. Generated apps now inject
bridge::script(BridgeOptions::REPORT_ONLY) instead of writing their own
duplicated src-tauri/runtime/tab-bridge.js.
- docs/STORE.md: one signed catalog for every app (format 2, `apps` per item, field rules), Ed25519
signing and key rotation, `waybetter store build|sign|verify` with an app config, install links,
the trust model, and how an app adds the Store (StoreConfig, command wrappers, InstallHost,
StoreView, tokens, strings).
- README: `store` in the layout and in what the host gives an app; the shell's Store modules.
- npm README: the Store components, models, schema and command.
Moved from wayBetter for X (scripts/build-store.mjs, scripts/sign-store.mjs) into the `waybetter`
npm package, generic over the app:
- bin/waybetter.mjs + cli/store.mjs. `build --config <store.config.mjs>`: the config names its app
and returns its items (metadata, file path + bytes, optional preview); build replaces only that
app's items in the shared catalog (other apps' items and files stay), removes files only its old
items used, fills url/sha256/size/apps, and validates the whole catalog with the rules apps apply
natively before anything is written. `sign` (and `sign --init`: create the key once, 0600, never
overwritten) signs the exact catalog bytes; `verify` checks the signature with any listed public
key (rotation) and the catalog rules. The private key is never printed, copied or moved; --key
defaults to $WAYBETTER_STORE_KEY. pluginHeader() for configs.
- store-catalog.schema.json (format 2, `apps` per item), exported with the CLI module.
- Tests: multi-app builds in one folder, refusals (bad items, duplicates, paths outside the folder,
hosts), schema vs CLI rules, sign/verify round trip incl. rotation and tampering, no key
material printed. jsonSchema.mjs learns minItems, uniqueItems and allOf.
Moved from wayBetter for X so every wayBetter app gets the Store UI; the app passes its native
bridge, theme adapter, translator and (optionally) its own controls and button classes.
- storeModel.ts: wire types of the native store commands (InstallPreview, StoreCatalogView,
LinkEvent, StoreItem with `apps`), permission badges in the site's words, categories, item state
(install/installed/update/changed locally/built in), search, free theme ids, versions, previews.
- installController.ts: the install screen's state machine (links incl. those queued before the
shell listened, dropped files, store items, cancel, confirm), generic over the app's theme format
(`ThemeAdapter`: parse, ids, facts, save) and its bridge (`StoreBackend`: invoke/listen).
- InstallDialog.tsx (`InstallHost`): the screen (Install enabled after 1 s, Enter never installs,
tabs hidden via setOverlay) and the window-wide drop target. StoreView.tsx: search, kinds,
categories, cards, permission badges, install/update/remove; default controls or the app's.
- store.css: both, themed with framework `--wb-*` tokens (documented; dark defaults).
- storeMessages.ts + every framework catalog: 120 `wb.store.*`, `wb.install.*`, `wb.permission.*`
messages moved from the app catalogs with all existing translations; the six that named X take
`{site}`; generic words the components need copied with their translations.
- npm package: exports for the new modules and store.css, optional react/react-dom peers; @types
for the typecheck. Tests: models, controller flows with a fake bridge, every looked-up key exists.
At document start (where the app installs the X runtime) there is no <html> element yet, so
the lang report never ran and "Follow X" never learned X's language (found live: siteLang
stayed null). It now reads lang again at readystatechange/DOMContentLoaded, and a report
that fails (no bridge yet) is retried with a bounded backoff. Tests.
Kit and shell-model words (18 keys) per locale, machine-drafted except English. Tests cover
locale normalization, fallback chains, direction, ICU-lite formatting and quoting, the runtime,
catalog rules (plural-only arguments, missing forms, allowEmpty), plugin strings, and that every
framework catalog passes the checks.
- runtime/i18n.ts: ICU-lite messages (placeholders as text, Intl.PluralRules plurals with exact
forms, selectordinal, select, number/date/time), fallback chains (pt-BR -> pt -> en, ar-x-fm ->
ar), X and legacy code aliases, RTL by locale, lazy catalogs, live onChange. Apostrophes are
literal except right before { } # or another apostrophe.
- runtime/i18nCheck.ts: rules for contributed catalogs (shape, keys, syntax, placeholder parity,
select options, markup/control characters, lengths). An argument English only uses to choose a
plural form may be dropped; missing plural forms are warnings; meta "allowEmpty" fragments.
- runtime/pluginI18n.ts: api.i18n bound per plugin (register/t/locale/dir/onChange/number/date/
relative/list); the UI kit's own words follow the app's language. pageLang.ts reports the
site's <html lang>. shell/messages.ts: shell-model words.
- waybetter-host i18n.rs: NativeStrings (validated plain text, caps, one-pass {name} fill, page
payload by prefix), system_locales via sys-locale; TabState.lang.
- JSON Schemas for catalogs and translator notes; npm exports waybetter/i18n, /i18n/check,
/i18n/plugin.
- styles.ts: adoptStyles(root, css) adopts a constructed stylesheet into a shadow root or document
(not governed by a page's CSP, parsed once, stays with the root when its host is moved);
<style> fallback where constructable sheets are missing or refused.
- UI kit: the kit's shadow root and each dialog's `css` use it (dialog CSS goes with the dialog).
- Plugin API: api.adoptCSS(root, css) and scope.adoptCSS (removed on dispose); injectCSS keeps its
<style> and, when a CSP leaves it without a sheet, applies the same rules as an adopted sheet.
- tests/styles.test.ts.
- host plugins.rs: PERMISSIONS, parse_permissions, ScriptPlugin.permissions/problems (None = legacy,
full API), validate_plugin (file name, 2 MiB, UTF-8 text without binary bytes, header with @name
and @version, valid @permissions, network:none refused when the code calls network APIs),
source_findings, PluginStore::install (atomic rename, never enables, keeps the enabled state on
update) / remove / is_enabled / file_hash. start/stop scripts run the module body, construction,
start() and stop() inside window.<global>.withPlugin(id, grants, fn) when the runtime has it.
- runtime permissions.ts: parsePermissions, scopePluginApi (undeclared gated members throw a
PluginPermissionError; scope().ui gated like api.ui), createPluginContext (the view of the plugin
whose code runs; legacy = full API). A declaration and guard rail, not a sandbox.
- The `commands` permission name comes from the parallel palette-commands work (its API lands there).
- plugin: *.plugin.js with the store header and start/stop on api.scope, storage, observe,
onKey, the UI kit (toast with action, ui.h settings dialog) and api.theme/--wb-* vars;
--ts makes a TypeScript project typed by the waybetter package (build keeps the header first)
- theme: JSON with "$schema" -> theme.schema.json, palette derived like themeFromColors,
font roles (family, tweet), presets
- app: Tauri 2 + waybetter-host + runtime + shell models: SiteSpec, SiteAdapter, shell/tab-chrome/
site-tab capabilities, tabs_sync, plugin store, a site-only command; presets for youtube.com,
kick.com, twitch.tv with placeholder selectors; host crate via submodule, path or git
- tests generate into temp dirs: headers via parse_meta (new plugin_meta example), plugin code
against createCorePluginApi's members, themes against the schema, app TS with tsc against the
built package, app Rust with cargo metadata/check/test
- wired into bun run check (typecheck incl. checkJs, build:packages, test:create) and pack:check
The kit draws in one shadow DOM host in the active theme's colors, corners and font and repaints
live; accessible dialogs (focus trap and return, Esc/Enter rules, alertdialog for alerts and
confirms), stacking with inert, toasts with actions, reduced motion, text-only content (ui.h),
and scope.ui closes a plugin's dialogs on dispose. api.toast routes to it.
Drop private from @waybetter/runtime and @waybetter/shell (root stays
private), add per-module exports with types/import conditions backed by
a tsc build (rewriteRelativeImportExtensions rewrites .ts specifiers to
.js in dist), files/sideEffects/engines/keywords/publishConfig, a
prepublishOnly that builds and tests, and a README + copied LICENSE per
package. Root gains build:packages and pack:check scripts. dist/ is
gitignored; the desktop app keeps importing raw src/*.ts by relative
path, unaffected.
Site-agnostic parts of the X app, behind a small site adapter:
- crates/waybetter-host: webview-label security policy and tab rect
clamp, account profiles with native-owned accounts (AccountRules),
plugin store with bundled-plugin upgrades (PluginHost), signed
data-only hotfix channel, update scheduling and crash handshake, tab
payloads/link routing/tab-chrome validation, and (feature "tauri")
webview helpers and the Windows system menu.
- packages/runtime: DOM hub, request hub, plugin API core, OKLab theme
color model, split-view rail hiding, link routing and account
reporting, each configured by a SiteAdapter.
- packages/shell: address bar, pane, link-drop, profile, palette and
update-chip models, and window controls with the Tauri API injected.
Tests moved/adapted from the app: 45 Rust (headless), 40 TypeScript.