run_app now keeps and serves what every app's panel and page need, with the same caller checks
as the rest (commands::SHELL: shell_only first; testkit::assert_host_guards covers them):
- app.rs: PageState (the plugin store in app data `plugins`, the feature flags, ShellSettings)
managed by the host; commands plugins_list, plugin_set_enabled, plugins_open_folder,
custom_css_get/set, features_get/set, settings_get/set (moved from wayBetter for X). The app
reads the state through app::page(), gets a loaded document's scripts from app::page_scripts()
(flags, links, custom CSS, plugins) and writes through set_features/set_settings/set_custom_css
(a backup import). AppConfig takes the app's PluginHost (AppConfig::new(site, title, plugins))
and an optional home_url. SiteHooks::navigation_started and new_window_mode default to the
owner's link settings.
- settings.rs (pure, tested): ShellSettings { homeUrl, externalLinks, siteLinks, restoreTabs }
with its input form (missing fields take the defaults; `xLinks`, the name wayBetter for X used,
is read as siteLinks), links_script ({ site: mode }), feature validation and features_script.
- commands::SITE + open_panel: a tab on the site's origin asks the shell to open the app's panel
(site_tab_only first). testkit: assert_site_only(command, site) checks that every capability
granting a site command is a tab capability limited to the site's URLs; assert_host_commands and
assert_host_guards include the site commands.
- shell frame backupModel: a settings section that still says xLinks compares as siteLinks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/shell/src/panel (waybetter/shell/panel): an app's own tab. An app describes itself
(PanelSite: brand and the home page's words, its own sections and their order, its theme store,
catalog and the preview component a theme document is drawn in, Make's examples and docs, the
install controller, its languages, what its native side serves) and createPanel returns the
component for ShellSite.panel. It is in the shell package because it is drawn in the frame and
shares its stores, native bridge and language.
- Panel.tsx (header, settings search over every settings section, navigation, toasts; Accounts
and Updates), nav.ts (which built-in sections an app gets: each shows only when the app serves
what it needs; the app's sections in its order), Home.tsx (hero, highlights, About from the
app's data), controls.tsx + panel.css.
- Appearance.tsx, ThemeBuilder.tsx + builderModel.ts + builder.css, fonts/ (FontBrowser, FontRoles,
catalog, the offline Google Fonts list; scripts/google-fonts-catalog.mjs regenerates it):
the theme documents are the runtime's, the store, catalog, accents, draft palette, file suffix
and preview are the app's (PanelSite.themes).
- Plugins.tsx (the plugins list and custom CSS over the host's plugins_*/custom_css_*),
StoreSection.tsx, MakePanel.tsx + makeModel.ts + MarkdownDoc.tsx + markdown.ts + make.css (the
examples, the theme file and the docs are the app's), Settings.tsx (links and session over
settings_get/set with `siteLinks`, keyword alerts, the media panel), FeatureToggles.tsx
(useFeatures + FeatureGroups over features_get/set: the app names its flags and groups),
PowerSettings.tsx (deck defaults from site.deck: its flags and presets; DeckPreset gets `name`
and `hint`), BackupPanel.tsx + backup.css, DeveloperPanel.tsx, LanguageSettings.tsx, compare.ts.
- frame: language.ts (createLanguage: follow the site / the system / one language, i18n_set;
moved from the app), appI18n.ts (createAppI18n: the app's typed i18n instance, useT, dates and
numbers; the app passes React's useSyncExternalStore), formatDate in i18n.ts (ShellI18n.date).
- tests/panel.test.ts: every panel key has English and is used, sections per app, the builder
model, the font catalog, plugin header rules, the UI kit demos, Markdown, compare.
- npm package: exports ./shell/panel, ./shell/panel/*, ./shell/panel/*.css; the build is
scripts/build.mjs (the shell is compiled with the runtime sources it imports under one root and
its imports are pointed at dist/runtime; stylesheets and the font list are copied).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 668 wb.* messages for the panel (navigation, settings search, accounts, updates, Appearance, the
Theme Builder, fonts, plugins, custom CSS, Make, Power User, settings, backup and sync, developer
mode, the controls), moved with every translation from wayBetter for X's catalogs; `{site}`
stands where the English said "X" (49 keys; the app passes "X", so X reads the same). Keys that
named X got site-neutral names (settings.links.site*, settings.language.followSite*,
builder.leadingSite, builder.liveOnSite, fonts.asSite, make.siteDefaultLeading, features.default).
English source: shell/src/panel/panelMessages.ts.
- One translation could not take `{site}` (hu wb.settings.alerts.hint never named the site) and
falls back to English.
- locales/en.meta.json: the translator notes of the moved keys (40), among them the one fragment a
language may leave empty (wb.make.step2Before); the catalog test reads it.
- runtime/tests/i18n.test.ts: en.json is the kit, shell-model, store, shell-frame and panel source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/waybetter/windows/installer.nsi (shipped in the npm package) is the NSIS
template wayBetter for X used, made generic: an app that was renamed keeps its
registry keys by defining WAYBETTER_LEGACY_NAME in its installer hooks; without it
the keys are Tauri's defaults.
Run by hand (no /UPDATE) it installs only when it is newer than what is installed.
With the same or a newer version installed it installs nothing, opens the installed
exe (the single-instance guard shows the running window) and exits 0, so an old
setup can no longer replace or downgrade an install the updater has moved on.
/FORCE reinstalls the same version, /NORUN is kept, the updater path is unchanged.
create-waybetter apps use the template (bundle.windows.nsis, per-user).
wayBetter for X's page is waybetter.fyi/x in the READMEs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The site-independent theme engine moves from wayBetter for X into the framework:
- runtime/themeSpec.ts: theme document types, the schema's defaults (BASE_*), sanitizing,
import, overrides, ensureContrast, font roles/stacks, font choices, backgrounds.
- runtime/themeChoice.ts: ThemeChoice and createThemeCatalog(builtins) (normalize, base,
resolve, palette, all), choiceForId, serializeChoice.
- runtime/themeRuntime.ts: installThemeRuntime(win, site) (live CSS over the site's rules,
--wb-* vars, site color mode, first-frame snapshot), collectRules, resolvedFonts.
- runtime/fonts.ts: bundled fonts and owner-downloaded Google Fonts from bytes.
- themeApi exports hexToRgb, mix and MONO_FAMILIES (one copy: themeTokens and themeSpec use them).
- host theme.rs: ThemeChoice/ThemeScopes over a site's ThemeSite (envelope checks against
theme.schema.json, which theme a tab shows, page color, delivery script).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README: "The shell frame" (createShell, the ShellSite slots and features, frame words and styles,
bootScript, preview mocks, the native commands and events it uses), the layout, the create-waybetter
app, how wayBetter for X consumes it. npm README: waybetter/shell/frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app skeleton's src/shell/main.tsx calls createShell with the site (pages, account rules, search,
address chips, a Home link, a Plugins section) and src/shell/Panel.tsx (the app's own tab: the
plugins, switched on or off live); app.css themes it with the --wb-c-* tokens. build.mjs bundles
the shell with waybetter/build (JSX, CSS into dist/shell.css) and writes bootScript() as boot.js.
desktop.rs keeps the frame's titlebar (custom_titlebar: true) and forwards shortcuts and link drags
(BridgeOptions::ALL); the shell capability grants the frame's window calls. Native features the
skeleton does not wire up (workspaces, sync, developer mode, plugin commands) are off in the site.
Dependencies: react, react-dom, dockview, waybetter ^0.4.0.
Tests: sources import only published subpaths (patterns included), main.tsx uses createShell, the
shell capability has the resize permission, and the skeleton typechecks with the React types.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/shell/src/frame (waybetter/shell/frame): the whole shell of an app. An app describes its
site (ShellSite: names and pages, account rules, search, address chips and completions, its panel
tab, the new-tab page, palette items, deck column kinds and flags and an optional compose dock, the
media panel's composer rule, a theme store, overlays, the native features it serves) and passes
its Tauri API and i18n instance; createShell returns the root component.
- Shell.tsx: dockview host (tabs in the titlebar row, splits, drag-link drops, shortcuts, context
menu, deck back-and-forth, session restore, workspace on start, overlay raise); state.ts (pane
placeholders, tabs_sync one-in-flight via syncGate, shell_raise layer, profiles, layout).
- WebPanel (address bar), NewTabPage, CommandPalette + paletteModel (frame groups; site items),
DeckView + AddColumnDialog + deckStore (site column kinds), PaneTheme + themeStore
(createThemeStore over the app's ThemeAdapter) + chromeTheme (ChromeTheme input) + themeScopes,
Titlebar + TitlebarExtras + icons + brand (wB mark data URI), AccountSwitcher + accountModel,
workspaces + workspaceModel, backupModel, syncHost, devHost + devModel, pluginCommandStore,
MediaPanel + mediaModel, searchInput, hooks, bridge (installPreviewMocks), i18n, boot.ts
(bootScript: the first-frame boot.js), CSS themed with --wb-* tokens.
- paneModel: paneVisibility, raiseSupported, cssRgb; deckModel: isSourcePage.
- Includes the live-QA titlebar fix (dock ResizeObserver + rAF titlebar marks, dock sized by its
insets).
- tests/frame.test.ts: every frame key has English and is used, palette order with a site,
address fallback, layout gate, boot script, chrome variables, switcher rules, workspaces,
deck settings with site flags.
- npm package 0.4.0: exports ./shell/frame, ./shell/frame/*, ./shell/frame/*.css (copied by the
build); dockview and acorn optional peers; root devDependencies dockview, acorn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The source line ("wayBetter Store (signed catalog, sha256 verified)", "Link: <host>", "A file you
dropped") is built from the source kind and URL with the wb.install.source.* messages instead of
showing native code's English label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>