Commit graph

36 commits

Author SHA1 Message Date
CRT
8eb9dba4d7 App menu and mode words in every catalog: find in page, update check, safe mode, report a problem, Custom/Re-apply
menuMessages.en.json stays the English source of the menu's words; en.json now carries them too
(the i18n test takes them into the source union) and 48 catalogs translate all 51 menu words and
the seven new mode words (plural forms per language for the match count).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:50:45 +07:00
CRT
2e3babd2f5 Plugin API: a style injected at document start waits for the document root
WebView2 runs document-start scripts before the document has an element, so
injectCSS threw ("reading 'appendChild'") for every CSS feature that was on at
start, e.g. hiding promoted posts, until the next flags delivery restarted it.
Such styles now wait (one MutationObserver for all of them), are attached as
soon as the document has a root, and get their CSP check then; a style cleared
while waiting is never attached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:39:16 +07:00
CRT
26658e14ae Modes: a mode that no longer matches the features reads "Custom (based on <mode>)" and offers "Re-apply <mode>"
A mode is a starting point, never a lock and never applied again on its own. modeStatus() (runtime)
compares the flags with the mode's preset: custom, what re-applying turns on and off, and which of
those the preset gained in an update since it was applied. mode_set keeps the preset of that moment
(modes.json "preset", flag names only, bounded; forgotten when the mode changes). Settings, the
first-run screen and the app menu's Mode row show Custom; Re-apply lists what it turns on and off
before it writes anything; a preset that grew is mentioned once on the mode row (Review), never applied.
Tests: exact match, changed by the owner, preset grew, choice made before the preset was kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:33:41 +07:00
CRT
b75246bef8 String packs: a page gets only the slice of the inventory its packs' keys need (sliceInventory, isPageInventory); a slice does not call other keys new
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:30:21 +07:00
CRT
006a7d160c Theme runtime: a static sheet for what does not depend on the site's stylesheet (components, variants, decoration, icons); refresh()
The site adapter's staticCss(choice, mode) is written to its own <style> (wb-theme-static), rebuilt
only when the theme or the site's mode changes or on refresh(); the palette pass never reads it back
as the site's rules, and the first-frame snapshot carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:30:34 +07:00
CRT
16957f4ea7 Theme engine v2, the document side: component tokens, layout variants, decoration, icon packs
A theme may now say how each piece of a site is built, not only one radius and one density:
- components (themeComponents.ts): 36 generic parts with clamped tokens (radius, border incl.
  98.css-style bevels, fill by color role / glass / gradient, padding, sizes, type, shadow, shape,
  hover/active/focus), presets and parents; compiled over a site's part map as soft values
  (--wb-part-*) and rigid rules; text on a chosen fill keeps its contrast.
- variants (themeVariants.ts): fixed words on six axes; the owner holds an axis or switches layout
  changes off; the look of a variant is the site's skin.
- decoration (themeDecor.ts): dividers, chrome (title bar, masthead, prompt), a focus ring that is
  always visible, selection, scrollbars, links, motion under reduced-motion.
- icon packs (themeIcons.ts): a standalone document of glyphs by semantic name (path data, colored
  layers or a small PNG, never an SVG document), with license, source and credits; a theme only
  names a pack; the owner's icon choice is separate; drawn as CSS masks of the current color over
  the site's own svg boxes, behind forced-colors and mask-support guards, per group.
- a theme document gains the optional groups components, variants, decor, icons and
  colors.actions; a theme without them is sanitized to exactly what it was.
- theme.schema.json and the new icons.schema.json are generated from the same tables
  (scripts/theme-schema.mjs; a test fails when they are stale).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:22:36 +07:00
CRT
16d89de712 Theme color model: a foreground the site paints dimmer than its secondary text becomes the theme's secondary text
A site may color an icon or text with one of its border grays (X's composer toolbar: 1.7:1 on its
own black). Placed by axis position it landed between a theme's background and its secondary
text, under every contrast a theme guarantees. In text usage that stretch of the axis (above the
most raised surface, below secondary text) now maps to the theme's secondary text; surface-dark
foregrounds (labels on a filled surface), borders and backgrounds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:58:48 +07:00
CRT
c7437ac504 String packs, phase 1: data-only packs that replace the wrapped site's own UI strings. Pack and inventory formats, validator (only inventory keys, parameter names, plural and select kinds, no markup), ordered idempotent engine that keeps what a restore needs, coverage and drift report; the per-site catalog hook adapter with a self-check (ok, hook not matched, no catalog seen), a fail-open webpack chunk array hook, a small DOM text net; waybetter strings check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:38:07 +07:00
CRT
632ba863c9 Feature health on the area cards: a feature that paused itself is named by its area's "health" palette entry, and the panel reads it for the row and the card (no new report to the host)
The entry opens the notes in the page; its keywords carry the paused flags of that area. English
for the three new words; the other catalogs follow with the words step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:00:04 +07:00
CRT
eed6bdd8f4 Route listeners: one removed by an earlier listener in the same round is not called (a feature stopped by the route re-added its stylesheet); the route poll never keeps a test process alive
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:19:02 +07:00
CRT
30a004b93b Theme engine: a rule is read as it takes effect (a Tailwind opacity utility's nested @supports value, not its solid fallback); unsupported @supports groups are left out; reach() is exported for site adapters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:04:12 +07:00
CRT
046f409d23 Feature modules: a feature ported from a plugin keeps that plugin's words (words), so nothing is retranslated; the keep-my-changes question takes its own line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:53:11 +07:00
CRT
381d55466b Feature modules in a new app, i18n rename, and what the first area port needed
- create-waybetter: the generated app has a feature table (one area, one feature, three modes), a feature module, the registry in its runtime, the cards and the first-run screen in its panel
- waybetter i18n rename --map <file> [--copy]: keys move or are copied in every catalog with each language's own value
- feature context: win (no globals in a module), text() for a feature's words (t('...') is read as a whole catalog key by the i18n tools)
- probes: `when` (only counts while the part of the page it lives in is there)
- cards: a caret, an empty area says its features come later; the first-run screen takes the whole panel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:25:15 +07:00
CRT
dc7d6f815a Panel: area plugin cards with per-feature toggles, the Fixes list, modes (first-run screen and Settings)
- PanelSite.features: the app's feature table; AreaPlugins draws one card per area (switch, count, grouped features, Settings button that runs the area's command in the site tab) and the core flags as Fixes
- a feature still backed by an old plugin file is switched through plugin_set_enabled from its card and left out of the JavaScript plugins list
- Modes: Onboarding (three cards, recommended preselected; an existing setup may be kept) and ModeSettings with the keep-my-changes question; openOnboardingWhenDue for ShellSite.start
- flags are saved sparsely (only what differs from the table's defaults)
- PanelSite.appearance.extra; palette commands of an area are listed under its name
- the kit's words for feature settings and health notes; English in the framework catalog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:34:14 +07:00
CRT
ca9eec05ab Feature modules: a feature table (areas, flags, modes), a registry that runs a module only on its surfaces, one shared observer for every feature and plugin, settings from a schema, selector health with self-pause
- featureTable.ts: areas, per-feature flags, core flags, modes as presets; sparse storage, ownership (an area that is off turns off what it owns), mode switching that keeps or drops manual changes, table validation
- featureModules.ts: createFeatureRegistry (flag + surface gating, scoped context, palette commands under the area with a budget of 5, settings dialog, probes, hotfix selector overrides and kill flags)
- settingsForm.ts: a settings schema normalizes stored values and draws the controls from the UI kit
- domHub observeSelector; api.observe goes through the shared hub (a root of the caller's own keeps its observer)
- createFeatureFlags: a delivery is the whole state (a flag it does not name is at its default), resolve() for ownership, current()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:11:42 +07:00
CRT
8b9ecca5d6 Comments: X's own panel sections are Tweaks and Drafts (My Space removed for now)
wayBetter for X took My Space (profile customization) out; the framework only named it in
comments as an example of an app's own section and runtime member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:52:50 +07:00
CRT
c654a5ac48 locales: the Windows installer's words (wb.installer.*) and its language files, generated from the catalogs
Moved from wayBetter for X: 27 messages in 51 languages, and windows/languages/<Language>.nsh for the 17 NSIS languages Tauri does not ship (scripts/installer-languages.mjs writes and checks them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:48:04 +07:00
CRT
d3ca1d7b16 runtime: the site runtime's composition (siteRuntime, pageStrings, panelButton, commandReport)
What every app's page runtime entry did by hand (moved from wayBetter for X's inject/entry.tsx):

- siteRuntime.ts: pageInvoke (the IPC bridge read at call time), createFeatureFlags (parts that
  install on enable and dispose on disable, the flags native code delivers, a deck column's
  overrides on top of them, recorded at once and applied when <html> exists) and
  installSiteRuntime (the runtime object with the members native code calls, the plugin API views,
  developer-mode errors, its window names, the queued native deliveries, the command report).
- pageStrings.ts: the page's language (English built in, deliveries checked as data, the
  bundled-plugin namespace rule).
- panelButton.ts: the way into the panel from the page (the site's own navigation entry when the
  adapter mounts one, else a floating button that hides under the site's overlays and sits above
  its bottom bar).
- commandReport.ts: the palette command report and developer-mode start errors.
- tests/siteRuntime.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:28:54 +07:00
CRT
7a3f23aa82 locales: the panel's words (wb.*), 51 languages
- 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>
2026-09-30 14:03:09 +07:00
CRT
01f2ec0417 runtime + host: the theme engine (theme documents, choice catalog, page runtime, fonts; native theme state)
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>
2026-09-29 13:07:59 +07:00
CRT
27fa4294c4 locales: the shell frame's words (wb.*) and the install screen's source line, 51 languages
- 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>
2026-09-29 12:46:57 +07:00
CRT
03fbfccaf5 shell: the Store view and install screen as framework React components, strings in the framework catalogs
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>
2026-09-29 11:08:04 +07:00
CRT
d5b5c3f71d pageLang: report X's language even when installed before <html> exists or before the IPC bridge
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>
2026-09-29 11:01:53 +07:00
CRT
d36e2d8709 i18n: framework catalogs for the 51 languages X offers, and 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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:53:49 +07:00
CRT
5f62b471f0 i18n: dependency-free message runtime, catalog checks, plugin strings, native strings
- 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>
2026-09-29 09:53:48 +07:00
CRT
2d4831da0a runtime: styles for created roots are constructed sheets (CSP-proof)
- 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>
2026-09-29 04:09:14 +07:00
CRT
74f4d02944 runtime: plugin commands for the command palette (api.commands / scope.commands: register, list, run, owner = the starting plugin, removed on stop, caps, in-page shortcuts); host: start/stop scripts own and drop a plugin's commands; dev_start_script with sourceURL + devError for live reload; devErrors (file:line from a start failure)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 01:35:44 +07:00
CRT
ee7907740a Plugin permissions and app installs: @permissions header, per-plugin API views, validated install/remove
- 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>
2026-09-29 01:08:52 +07:00
CRT
0344c55fa8 runtime: font roles in the theme API (fonts per role, --wb-font-<role> vars, UI kit uses the dialog/mono roles); theme schema: font.roles with Google Fonts source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:56:37 +07:00
CRT
abdc29ba55 runtime column: set the column attributes on scan (config delivered at document start has no <html> yet), no scrollbar in columns, unread only above both the read items and what is on screen, a hidden 1 px webview counts as off screen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:42:21 +07:00
CRT
b3f2500314 Deck columns (Power User Mode): shell deckModel (bounded decks, paging layout, focus, unread totals), runtime column mode (hide site chrome, fill width, filters, unread tracking, no requests), host ColumnConfig bounds, TabRole, deck shortcuts, capped unread in TabState; column boxes skip rail rules
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:55:57 +07:00
CRT
3ae7744387 runtime: plugin UI kit (api.ui: toast, alert, confirm, prompt, dialog, modal) and theme API (api.theme, --wb-* vars); theme JSON Schema; waybetter 0.2.0
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>
2026-09-28 21:47:09 +07:00
CRT
d0cb4a2c06 npm: publish as one package waybetter (waybetter/runtime/*, waybetter/shell/*); @waybetter scope is taken
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:54:03 +07:00
CRT
9fa94efe4f README: projects using wayBetter (wayBetter for X first)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:46:46 +07:00
CRT
7cd8005027 packages: make runtime and shell publishable to npm
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>
2026-09-28 05:41:01 +07:00
CRTkafa
41267a22fd Extract the wayBetter framework core from wayBetter for X
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>
2026-09-28 03:01:33 +07:00