Rxova

Updates

What is moving across the projects. Each entry links out to the release it came from — the per-project changelogs stay canonical for exactly what changed, and the blog covers why.

  1. @rxova/brand 1.0.0

    Brandreleasebreaking@rxova/brand@1.0.0

    @rxova/brand is 1.0, developed in the open for the first time: the package moved from a private repository into the public rxova-website one. It is also smaller than 0.15: design tokens, typefaces and project data, nothing else. The components, astro.css, chrome.css, starlight.css and sharedStarlightConfig have moved to @rxova/astro-ui, which shipped alongside it.

    That move is the breaking change. A site that imported a component or a stylesheet from @rxova/brand takes it from @rxova/astro-ui now; tokens.css, fonts.css, the assets and the project data stay where they were. chrome.css no longer defines the short aliases (--bg, --fg, --max, …), so styles read the --rx-* tokens directly. projectFromBase is now exported from the entry point.

    Jonatan Kruszewski

  2. @rxova/astro-ui 0.1.0

    WebsiteBrandreleasefeature@rxova/astro-ui@0.1.0

    @rxova/astro-ui is a new package, and the first release is everything on rxova.org that renders: the chrome (SiteShell, Header, SiteFooter, ProjectSwitcher, ThemeToggle, ThemeScript), the Starlight preset and its overrides, the stylesheets, and the primitives (PageHeader, BackLink, ShowMore, VisuallyHidden) that the landing, the blog and the updates stream used to copy around. Until now these lived in @rxova/brand.

    A docs site spreads sharedStarlightConfig from @rxova/astro-ui/starlight. A plain Astro site imports @rxova/astro-ui/styles/document.css and the components from @rxova/astro-ui/components/*. There is no barrel: each component has its own path, so a page only loads the CSS of the components it imports. The package depends on @rxova/brand for the tokens, so a site installs both.

    Jonatan Kruszewski

  3. The brand moves into the website, and the landing gets a walkthrough

    WebsiteBrandfeatureinfra

    @rxova/brand has moved out of its private repository and into rxova-website, which is public. Its source, its history from here on and its release process sit next to the landing, the blog, the updates stream and the tooling that assembles rxova.org. One workspace, one gate: the pre-push verify list and CI run the same steps, and every app and package is held to a coverage floor. Playwright now checks the built site’s behaviour, and a local visual check catches layout drift.

    On the landing, each project has a before/after walkthrough: the code a problem usually ends up as, and the same thing written with the library, one note at a time. Pick a project from the rail and step through it on the stage.

    Jonatan Kruszewski

  4. ts-extended-errors 1.0.0

    ts-extended-errorsreleasebreakingts-extended-errors@1.0.0

    ts-extended-errors is 1.0. The API that shipped through 0.x is now stable: ExtendedError, defineError, the cause-chain helpers, serializeError, deserializeError, toError, isErrorLike, describeValue and the exported types. From here, a breaking change to any of them, to the serialized shape, or to the defaults of toJSON and serializeError means a new major.

    The one breaking change is the runtime floor: Node.js 22.12 or newer. The library uses no Node.js APIs, so browsers and other runtimes are unaffected.

    1.0 also types code as the literal a class declares, so a switch over a taxonomy’s codes can be exhaustive; accepts an interface as a context type; and keeps a numeric code through a JSON round trip.

    Jonatan Kruszewski

  5. A new rxova.org landing page

    WebsiteBrandfeature

    The rxova.org landing page now opens straight on the projects. The old hero and its stacked list are replaced by a rail beside a stage: pick a project from the rail, and the stage shows what it does, how to install it and where its docs live.

    Every project is tagged zero-dependency, and the page says what that means: each package declares no third-party dependency, which npm view <pkg> dependencies confirms. The page only links to docs that are live.

    Jonatan Kruszewski

  6. Leaner builds and stricter gates across the libraries

    overlockuse-everywherejourneyreact-inputsts-extended-errorsinfra

    overlock and use-everywhere now build with tsdown instead of tsup (overlock 0.10.1, use-everywhere 1.0.1). The published output keeps its shape: same entry points, formats, filenames and types. overlock’s tarball is smaller, because code shared by its two entries is no longer duplicated.

    Every library’s CI now gates on knip and sherif. knip fails on unused files, exports and dependencies. sherif fails when two packages in a workspace disagree on a dependency’s version. The first pass removed internal exports nothing imported (overlock 0.10.2, @use-everywhere/core 1.0.2), none of them part of the public API.

    Jonatan Kruszewski

  7. overlock 0.10.0

    overlockreleasefeatureoverlock@0.10.0

    overlock check --staged --stage-record stages the record of a run into the commit that run judged. Without it, the evidence for a change always trailed one commit behind the change, and a repository collecting records never had a clean working tree.

    It is a flag, not a config key, because only a pre-commit hook’s run has a commit to join. Staging is the one write overlock makes, and it happens only when you pass this flag. If .overlock is ignored, the record stays on disk and overlock says so on stderr.

    Jonatan Kruszewski

  8. ts-extended-errors is on npm and rxova.org

    ts-extended-errorsWebsiteBrandreleasedocsts-extended-errors@0.4.4

    ts-extended-errors is public. It is a zero-dependency error model for TypeScript applications that throw native exceptions but need more from them:

    • typed context: defineError, with a message option that writes the message from the context;
    • cause-chain inspection;
    • JSON round trips: serializeError and deserializeError rebuild errors as their own classes, including AggregateError.

    Install it with npm install ts-extended-errors. Every version is on npm under that name, so no .npmrc line is needed. Its docs are at /packages/ts-extended-errors/, and it appears on the rxova.org landing page.

    0.4.4 hardens the part that matters most in an error handler: a getter, proxy trap or prototype check that throws while inspecting an unknown value no longer replaces the original failure.

    Jonatan Kruszewski

  9. journey 1.0 takes shape on main

    journeyfeaturebreaking

    The API journey will ship as 1.0 is on main. It replaces the single journey machine with two builders:

    • createLinearJourney for step-by-step flows;
    • createGraphJourney for flows that branch.

    Around them:

    • analyzeStructure checks a definition without starting a runtime.
    • The analytics, replay and execution-paths plugins move under @rxova/journey-core/plugins.
    • An immer connector sits under ./connectors/immer.
    • There are Vite examples for both builders and every plugin.

    It is not on npm yet: the rc tag is still 1.0.0-rc.3, on the old API. Expect a breaking release candidate next.

    Jonatan Kruszewski

  10. overlock 0.9.2

    overlockreleasefixoverlock@0.9.2

    The Claude Code hook now stops only once for a suppression the patch wrote about itself. Before, every later turn on the branch stopped again on the same claim.

    The hook now remembers what it has already put to a person, per repository and branch. Changing the reason, or adding another suppression, still stops once. Findings nobody suppressed still stop every turn until they are fixed.

    Jonatan Kruszewski

  11. File input 1.1.0 and an OTP font fix

    react-inputsreleasefeaturefix@rxova/react-file-input@1.1.0

    The file input is easier to use from a keyboard and a screen reader:

    • One tab stop: Tab goes straight to the drop zone, not first to a hidden input.
    • The drop zone is named after its field, so three file fields no longer all read “Choose a file or drop it here”.
    • Picking the same file again after it was refused or removed now runs the rules again.
    • onReject always carries a reason. attempt() returns FileAccepted | FileRejected, which narrows on accepted.

    @rxova/react-inputs 1.1.0 re-exports the two new types. Separately, OTP input 1.0.3 applies --rx-otp-font to the visible slots and separators, not only to the hidden input.

    Jonatan Kruszewski

  12. use-everywhere 1.0.0

    use-everywherereleasebreakinguse-everywhere@1.0.0

    use-everywhere is 1.0, and so are @use-everywhere/core, @use-everywhere/test-utils and eslint-plugin-use-everywhere. The stability policy applies from this release.

    The only breaking change is the one 0.13 announced: the 0.x names deprecated there are removed. Behaviour does not change, and the wire protocol stays at version 1, so tabs on 0.13 and 1.0 still talk to each other.

    Coming from 0.x, run npx use-everywhere-codemod rename-1.0 src/, review the diff, and take the major.

    Jonatan Kruszewski

  13. overlock 0.9.1

    overlockreleasefixoverlock@0.9.1

    Crafted input can no longer stall a run. Four parsers backtracked quadratically on text the patch author controls, and each now reads in linear time:

    • an Overlock-Allow: trailer;
    • a diff --git header;
    • an unterminated string literal;
    • an exclude entry.

    Every result is the same as before.

    Jonatan Kruszewski

  14. overlock 0.9.0

    overlockreleasefeatureoverlock@0.9.0

    A new exclude setting leaves other tools’ evidence directories out of the patch, such as .basting or .saidso. They are treated the way .overlock already was. Without it, those directories changed the patch fingerprint on every turn and grew captured diffs recursively.

    Entries are literal paths from the repository root. Globs, pathspec magic, .. and filesystem paths are refused, so the setting can only ever narrow what overlock looks at.

    Jonatan Kruszewski

  15. overlock 0.8.0

    overlockreleasefeatureoverlock@0.8.0

    overlock can now keep a record of what it judged. Evaluation records are opt-in and stored in the repository under .overlock. Each one holds:

    • the exact patch fingerprint and a snapshot of it;
    • the final hook decision;
    • the evidence as it was before any suppression applied.

    Those records feed new tooling: import artifacts from other runs, review outcomes independently, report across deduplicated evaluations, and replay a labelled corpus. The replay checks detection and blocking as two separate questions.

    Repeated detections in the old ledger no longer count as verified catches, so the deprecated meetsBar now always returns false.

    Jonatan Kruszewski

  16. overlock 0.7.1

    overlockreleasefixoverlock@0.7.1

    SUITE_SCOPE_NARROWED no longer blocks a pull request that adds a package. A new vitest.config.ts read as an exclude list that grew, so every new package was reported as a narrowed suite, at high.

    A config file that already existed still fires, including when it gains an exclude key it never had.

    Jonatan Kruszewski

  17. use-everywhere 0.13.0

    use-everywherereleasedeprecationfeatureuse-everywhere@0.13.0

    The 1.0 names from RFC 0001 arrive here, alongside the old ones: useOnMessage, useWindowResult, createStoreHooks, useSharedSelector and StoreHooks.store(). The 0.x spellings keep working and warn once per name in development (UE2005).

    Two tools make the move mechanical:

    • use-everywhere-codemod 0.1.0, its first release: npx use-everywhere-codemod rename-1.0 src/ rewrites a 0.x codebase, and --dry-run lists the changes first.
    • eslint-plugin-use-everywhere 0.2.0 checks createStoreHooks as well as defineStore, so a codebase part-way through the rename is linted either way.

    Nothing breaks on 0.13. It is the release to take before 1.0.

    Jonatan Kruszewski

  18. overlock is open source

    overlockrelease

    I open-sourced overlock, a CLI that catches changes that make tests pass by weakening them: an it.skip, an assertion loosened to toBeDefined(), a coverage threshold lowered. It was built for coding agents, which will happily take that route to a green suite.

    Run it with npx overlock, or as a Claude Code Stop hook, an MCP server or a GitHub Action. Thirteen rules, no network calls, zero runtime dependencies.

    Jonatan Kruszewski

  19. overlock 0.7.0

    overlockreleasefeatureoverlock@0.7.0

    overlock now catches tests switched off in CI config, not just in test files. Two new rules:

    • TEST_GATE_DISABLED: a failing suite stops failing the build — continue-on-error: true, || true, --passWithNoTests, or the test step deleted.
    • SUITE_SCOPE_NARROWED: the runner collects fewer tests — an include pattern removed, an exclude pattern added, a filter flag on the test command.

    A rule can also be turned off per repository with "severity": { "RULE_ID": "off" }. What it would have reported is still counted in the verdict line, so switching a rule off never hides silently.

    Backwards compatible: existing configs and flags behave as they did.

    Jonatan Kruszewski

  20. use-everywhere 0.12.0

    use-everywherereleasefeatureuse-everywhere@0.12.0

    A React app now gets every type it needs from use-everywhere alone. LockManagerLike, SharedWorkerLike and MessagePortLike are re-exported, so writing a SharedWorker factory or a fake lock manager for a test no longer means installing @use-everywhere/core as well.

    The docs are more accurate too: bundle sizes are re-measured (core at 8.56 kB brotlied, useSharedState at 4.1 kB), and stability.md now documents useClientId’s server snapshot as '', which is what it actually returns.

    Additive, so nothing on 0.11.x has to change.

    Jonatan Kruszewski

  21. The inputs suite is 1.0.0

    react-inputsrelease@rxova/react-inputs@1.0.1

    All nine components and @rxova/codemod are now on 1.0.0, and the meta-package sits one patch ahead at 1.0.1 because the alignment moved its dependencies.

    The numbers had drifted for no reason worth keeping: currency had taken three minor bumps where a later-landing component took a single major, so 0.3.0 sat next to 1.0.0 implying a difference in maturity that did not exist. Merge order is not a maturity signal, and a codemod claiming 0.2.0 while every package it rewrites claims 1.0.0 reads as the older half of a pair it is actually in step with.

    For six of these — date, time, phone, password, tags and file — 1.0.0 is simply the first version anyone can install. 0.1.0 was versioned and its notes written, but the release run failed before it reached npm, so the number never existed as far as consumers are concerned. The alignment itself changes no API and needs no migration; the one break in 1.0 is the token namespacing, which landed with the nine-input standard and has a codemod.

    Jonatan Kruszewski

  22. One standard across all nine inputs

    react-inputsbreakingfeature@rxova/react-inputs@1.0.0

    Going from three components to nine broke the conventions that worked for three. Every styling hook in the suite is now namespaced --rx-<name>-* / data-rx-<name>-*: --rx-otp-slot-size, --rx-rating-size, --rx-date-segment-radius. This is the breaking part of 1.0 — the two components whose prefixes actually change are OTP (--otp-*) and rating (--rfs-*), and npx @rxova/codemod rx-token-prefixes plus one sed line over your stylesheets is the whole migration.

    The old scheme was each package’s initials, which does not survive nine components: password and phone both reduce to rpi. Custom properties inherit, so setting the wrong one is silently inert rather than an error — the knob just does nothing, on a component that looks like it should have it. pnpm check:tokens now fails any hook that leaves its component’s namespace. The shared state hooks — data-state, data-filled, data-active, data-disabled, data-readonly, data-invalid — are deliberately untouched, because they mean the same thing on every input and one selector should reach all of them.

    The rest of the standard is the same exercise applied elsewhere. The meta-package now re-exports everything its components export rather than a hand-picked subset — 37 names were missing, which is how you get DateInputProps.onPartsChange re-exported without DateParts, a prop you can see and cannot name the argument of; a test fails on any export that is neither re-exported nor one of the six that genuinely collide. Development warnings say the package you installed rather than a name it had two renames ago. The date and time fields paint a focus ring on the focused segment, which a <span role="spinbutton"> gets from nobody. Password and phone always cap their length now. And the whole suite is exercised on every PR in real Vite, Next and Remix apps, because “renders in a test renderer” and “hydrates in your framework” are different claims.

    Jonatan Kruszewski

  23. Six new inputs, and the suite is nine

    react-inputsfeaturerelease

    The suite was currency, rating and OTP. It is now nine: date, time, phone, password, tags and file landed together, each one headless, typed, with no stylesheet to import and zero runtime dependencies.

    The through-line is that the platform already ships most of what these components are usually sold with. Date and time are segmented, keyboard-first fields with no calendar, no clock popup and no date library — segment order, separators, month names and the AM/PM words come from Intl, and the value is a YYYY-MM-DD or HH:mm[:ss] string end to end with no Date ever constructed, because a calendar date is not an instant. Phone drops the metadata blob: country names from Intl.DisplayNames, flags from Unicode regional indicators, and a ~4 kB dial-code table, which is ~6 kB brotli against 10.2 MB unpacked and five dependencies for the category leader.

    The other three are about the details you only meet in use. Tags is not the smallest option in its category and says so — the case is six accessibility failures common to the popular alternatives, each with a test here, starting with focus never landing on <body> after a removal. File validates, deduplicates and revokes its own preview URLs, and never uploads anything. Password has a reveal toggle that keeps focus and the caret, a Caps Lock warning read off the real modifier state, and a 1.2 kB entropy estimator you can swap for zxcvbn if you want its wordlists.

    Jonatan Kruszewski

  24. The use-everywhere demo is linked from rxova.org

    Websiteuse-everywheredocs

    Every explanation of this library ends with “open a second tab and watch”, and until now there was nothing to open unless you cloned the repo. There is now, and rxova.org links it — so a reader meets the thing before the install line rather than after.

    Ten pages behind a sidebar, each one a live thing you operate with the code that is running printed above it. The order is how someone meets the library rather than how the API is organised: shared state first, then the two questions it immediately provokes — what happens when two tabs write the same key, and what happens when the value is a count rather than a fact — then persistence and the coordination primitives, then the parts that matter once it is load-bearing: namespaces, transports, devtools. The demonstrations are the claims that are hard to believe without seeing them. Two counters side by side running the same race, one losing concurrent increments and one that cannot. A version clock table showing which write won and who made it. A sign-out that reaches four tabs in the same millisecond.

    It sits on its own origin rather than under /packages/use-everywhere/ with the docs, which is deliberate. The docs are composed into rxova.org under a base path; this has to be opened in two tabs, so it wants an origin, and GitHub Pages gives it one. Routing is hash-based for the same reason — on Pages a path route is a 404 until you add a 404.html that impersonates the app, and a deep link that works on first load is the whole point of a page you are asked to open twice.

    Jonatan Kruszewski

  25. use-everywhere reads well to an agent

    use-everywheredocsfeature

    The treatment the inputs docs got in July, ported here — five surfaces rather than six. The shadcn registry does not port: this library ships hooks, not components you copy into your own tree.

    Every docs page is also served as raw markdown at <route>.md, all 257 of them, generated from the same page enumeration the site is built from so a twin cannot disagree with its page. llms.txt indexes the site in 11 kB and llms-full.txt inlines the lot in 517 kB, under an 800 kB budget the build enforces rather than an agent silently truncating. Its ## Optional section links the three TypeDoc index pages, not the 209 generated reference pages behind them — listing those individually buries the 46 prose links the index exists to present, and a 24 kB budget keeps it that way. Every symbol page still has its own twin, one hop further on.

    Each of the four tarballs carries a hand-written llms.txt as well, which is what an agent reads out of node_modules after an install rather than fetching anything: what the package is, how to install it, a working example, the public surface, and the mistakes that are silent at runtime. check-llms.ts checks its ## API table against the package’s real entry points, so a renamed export fails the build instead of leaving the file describing an API that no longer exists, and pack:smoke proves the file actually ships. AGENTS.md covers the other case — working in the repo rather than reading the site.

    Writing it found five things wrong with the docs, which is the argument for writing it. 235 doc-relative links left over from the Docusaurus migration — ../core/transports.md and its like — were being emitted into the HTML verbatim as dead links, and nothing checked them because starlightLinksValidator runs with errorOnRelativeLinks: false. Twelve of those pointed nowhere at all. Astro lowercases ids, so TypeDoc’s README.md is served at /api/core/readme/ and a link written with the true filename resolved to a page that does not exist. index.md has the id index rather than '', which had put a source: line naming a route the site does not serve into all 257 twins. And the description extractor dropped a description whenever the opening sentence ran past 200 characters, and welded link URLs into the prose — two pages had none and one was corrupted.

    The site’s files are live. The packaged ones ship with each package’s next patch.

    Jonatan Kruszewski

  26. use-everywhere 0.11.0

    use-everywherereleasefeatureuse-everywhere@0.11.0

    0.10.0 put a bus in a SharedWorker. This one lets the worker speak on it.

    startRelay returns a Relay now, and @use-everywhere/core/shared-worker exports the one it installs on import — mirrored at use-everywhere/shared-worker, so a React app whose worker owns the WebSocket still needs a single dependency to publish from it.

    Until now the shipped relay could only forward between ports. A worker hosting it had no way to originate a message, so “the worker owns the socket” — the entire reason to put a bus somewhere that is not a tab — needed a second bus over BroadcastChannel and a separate handle to keep the worker alive. One port does both now.

    relay.connect() hands back a Transport, which means worker-side code calls createSharedStore exactly as a tab does, late-joiner handshake included, and never hand-assembles an envelope the wire protocol is free to redefine. relay.broadcast(data) is the raw escape hatch, and relay.size counts the attached ports — which is how a worker knows to idle while no tab is looking.

    Additive, so nothing on 0.10.x has to move: startRelay keeps its signature and the import side effect is unchanged.

    The transport also has end-to-end coverage now, in three real browsers. Whether two tabs reach one worker is not a question unit tests can settle. The fixture’s worker mints a socket id once at startup and never re-broadcasts it, so two tabs agreeing on it can only mean they found the same worker, a late tab learning it can only mean the handshake travelled over the relay, and the id surviving a tab closing can only mean the connection did too. The first assertion is that the transport really is shared-worker — every other one would pass just as happily over BroadcastChannel, so without it a silent fallback would look like a green suite.

    Jonatan Kruszewski

  27. use-everywhere 0.10.0

    use-everywherereleasefeaturedocsuse-everywhere@0.10.0

    A bus can run through one SharedWorker instead of a channel between N tabs. SharedWorkerTransport and the relay it talks to (@use-everywhere/core/shared-worker, mirrored at use-everywhere/shared-worker so a React app still needs one dependency) are opt-in — BroadcastChannel stays the default.

    The point is a place that is not a tab. Leadership exists because something had to own the socket and only one tab could; a relay can own it outright. isSharedWorkerAvailable() reports the contexts where the constructor would throw — dedicated workers, Chrome for Android — so the fallback is a check rather than a caught exception.

    The Inspector renders inside a shadow root, so the host page’s CSS cannot reach it. Its own styles already could not leak out, which was only half the problem. It also gains per-scope views over the wire log, and a timeline: every state wire records a frame, and restore writes one back through the store, so every tab converges rather than one tab quietly disagreeing with the rest.

    Two behaviour changes worth knowing. The panel is no longer in document — reach it through host.shadowRoot if your tests assert on it. And it renders nothing during server rendering, where it previously emitted markup no one could use. react-dom is an optional peer dependency now, used for the portal.

    And the groundwork for 1.0, written before it is needed rather than after: an RFC process for changes that cannot be undone cheaply, RFC 0001 covering the naming sweep, a migration guide written before the migration exists, and a code of conduct.

    Jonatan Kruszewski

  28. eslint-plugin-use-everywhere 0.1.0

    use-everywherereleasefeatureeslint-plugin-use-everywhere@0.1.0

    Four rules, for the four mistakes this library cannot warn you about at runtime.

    Each one has the same shape: the code runs, nothing throws, and the bug shows up as behaviour that is merely wrong — in another tab, or on somebody else’s machine, or only after a deploy. That is the case worth spending a lint rule on.

    define-at-module-scope catches a definer called inside a component, where only the first registration takes effect and every later one is silently discarded.

    no-dynamic-name catches a bus name computed at runtime. A BroadcastChannel name is an identity, so a name that varies forks the bus in two, and both halves keep working perfectly on their own.

    structured-clone-safe catches functions, symbols and class instances in shared state. These either throw on write or, worse, arrive with their prototype dropped — a plain object wearing the shape of the thing you sent.

    leader-effect-captures warns when a useLeaderEffect closes over a value that changes between renders. The effect re-runs when leadership moves and not when the value does, so the stale capture can outlive several renders without anything looking wrong.

    Flat config, ESLint 9+, and no type information required — so it costs nothing to turn on:

    import useEverywhere from "eslint-plugin-use-everywhere";
    export default [useEverywhere.configs.recommended];

    Jonatan Kruszewski

  29. @use-everywhere/test-utils 0.1.0

    use-everywherereleasefeature@use-everywhere/test-utils@0.1.0

    A browser full of tabs, in one test process, with no browser and no globals.

    Multi-tab code is hard for one reason, and it is not the messaging. It is that a tab which closes says goodbye, and a tab which crashes leaves its peers to work out that it is gone. Testing the first case was always easy. Testing the second meant a real browser, real timers, and a test that was slow when it passed and unreadable when it failed.

    createScenario() is one simulated browser: a hub every tab shares, a navigator.locks stand-in every tab queues on, and tabs that can be closed or crashed.

    const browser = createScenario();
    const a = browser.tab();
    const survivor = browser.tab().leader("app");
    a.leader("app");
    await browser.settle();
    a.crash(); // no goodbye, and the lock the dead tab held is reclaimed
    expect(survivor.getSnapshot().isLeader).toBe(true);

    crash() is the interesting half. The tab stops without unsubscribing, without releasing its lock and without telling anybody, and the rest of the scenario has to notice on its own — which is exactly the failure the leader election and the presence roster exist to survive, and exactly the one that used to go untested.

    It hangs off a seam rather than a mock: LeaderOptions.locks is documented and LockManagerLike is exported from core as of 0.9.0, so FakeLockManager is a supported implementation of a public type and not a monkey-patch that breaks on the next refactor.

    Also published: FakeWindow and fakeWindowPair for the window-channel seams, tick and snapshotWindow, and MemoryHub/MemoryTransport re-exported from @use-everywhere/core/testing — so a test needs one import rather than two.

    Jonatan Kruszewski

  30. use-everywhere 0.9.0

    use-everywherereleasefeatureuse-everywhere@0.9.0

    Every diagnostic has a code now, and a page that explains it.

    [use-everywhere] UE1001: second shared store for "cart" in this tab — …
    → https://rxova.dev/packages/use-everywhere/errors/#ue1001

    The code is the durable part. A message can be reworded, mangled by a minifier or truncated by a log aggregator, and UE1001 survives all three — so codes are permanent and never reused, because an old build in somebody’s browser is still emitting them. The text is otherwise unchanged; anything that matched on it, a test asserting a console warning or a log filter, should match on the code. The React package re-exports the same codes, and warnings still cost nothing in production.

    The Inspector becomes usable while something is going wrong. Pause, clear, and a filter that matches on scope/type and on the sender. Pausing freezes the log and nothing else — the observer stays subscribed, since tearing it down would drop the traffic in between and leave you with a log that has a hole in it, and the crown keeps updating, because leadership is state rather than history. State is editable too: click a value, type JSON, press Enter, and the write goes through the store, so it takes a version and reaches every tab. A draft that is not JSON is refused rather than guessed at — light and "light" mean different things, and a panel that picks one for you starts disagreeing with the wire.

    Core also exports LockManagerLike, and documents LeaderOptions.locks as the supported test seam it had quietly become — which is what the new @use-everywhere/test-utils package, released alongside this one, passes a fake lock manager through.

    Jonatan Kruszewski

  31. Mutation testing on use-everywhere

    use-everywhereinfra

    Coverage says a line ran, not that a test would notice if it were wrong. The core is under mutation testing now — every module past 90%, gated in CI per module as well as overall, and two tests that turned out not to notice were fixed in the same pass.

    It runs Sunday, Wednesday and Friday rather than on every pull request, because rerunning the suite once per mutant is slow and paying that on every push buys very little. The e2e suite also grew coverage for throttling and transport degradation, the two failure modes 0.7.0 addressed and nothing was exercising end to end.

    Jonatan Kruszewski

  32. use-everywhere 0.8.0

    use-everywherereleasefeatureuse-everywhere@0.8.0

    The largest release so far, and the one that closes most of the gaps between “syncs state across tabs” and something you would put under a real app.

    Namespaces. A BroadcastChannel is global to the origin, so a bus name is an identity: two micro-frontends that each call createSharedStore('cart', …) are one cart with two teams writing to it. createNamespace('checkout') carries the whole surface, hooks included, onto its own bus.

    Payload validation. The payload was the one thing on the wire trusted without a check — cast to the receiving code’s type, which during a rolling deploy is the sender’s idea of the shape. schema takes anything implementing Standard Schema, without the library depending on any of them.

    Versioned persistence. Disk is where version skew has its longest fuse: a value written by last month’s build restores with a clock that beats every live tab. persist.version and migrate handle the older case, a newer one is refused, and store.hydrated makes the restore observable — which is what the new indexedDbAdapter needs, since it buys room and structured-clone fidelity at the cost of a synchronous flush.

    New primitives. useSharedReducer for writes relative to what is already there, where last-writer-wins would drop one of two simultaneous increments. useSharedStore(selector) for derived reads without re-rendering on every write. Presence metadata, so peers can carry a name or a cursor. And the channel is finally complete: echo, once, and ask/answer.

    Development warnings are stripped from production bundles now, so most size budgets ended up below where they stood before this stack started. One consequence: loading the ESM directly with no bundler to define process throws.

    Jonatan Kruszewski

  33. The inputs docs now read well to an agent

    react-inputsdocsfeature

    An agent asked to use one of these components fetched a whole Starlight page — nav, sidebar, search index, live-example islands — to read a prop table. Every docs page is now also served as raw markdown at <route>.md, which is the same content at a fraction of the bytes and needs no HTML parsing.

    llms.txt indexes the site for agents and llms-full.txt inlines the lot, under a size budget so the build complains rather than an agent silently truncating it. AGENTS.md covers the other case: working in the repo rather than reading the site.

    Jonatan Kruszewski

  34. A new landing page for Journey

    journeydocs

    The Docusaurus migration transcribed the old homepage rather than redesigning it, so it still opened as a release announcement — “We did it!”, a confetti modal, a link to reopen the 1.0.0rc celebration — and four sections carried the old teal-and-navy palette as some fifty hardcoded colour literals, on a site whose accent has been violet since it adopted @rxova/brand. The carousel’s cards filled with the token the brand maps to the page background, so only a hairline of them showed.

    Those four components are gone, along with every hardcoded colour and the confetti dependency, replaced by sections built the way the react-inputs landing is built: brand tokens only, prefers-reduced-motion on every animation, and server-rendered content so the page reads with JS off. The demo runs a real @rxova/journey-core machine from a plain script through a branching checkout — no React, no bindings — which demonstrates the framework-agnostic claim rather than asserting it.

    The proof band measures itself: size-limit runs against each package’s built output at build time and the coverage floor is parsed out of the vitest config, so no figure is restated from prose. It immediately caught one — the bridge measures 3.13 kB, not the 3.2 kB the README claims.

    Jonatan Kruszewski

  35. A new landing page for react-inputs

    react-inputsdocs

    The landing page read as thin documentation — the h1 was the org name, the body was a component catalogue, and none of the CTAs a visitor needs were on it. It runs hero → quick start → why → proof → one live demo → budgets → headless now, and no longer enumerates components, so a fourth input changes nothing here.

    The numbers are derived from the same configs CI enforces — size budgets, coverage thresholds, Playwright projects, axe tags — so the page renders the pipeline’s own inputs and cannot drift from it. Budgets are quoted Brotli rather than gzip, and the coverage floor is the minimum across packages, not the best case.

    Jonatan Kruszewski

  36. use-everywhere 0.7.0

    use-everywherereleasefeaturefixuse-everywhere@0.7.0

    A missing BroadcastChannel used to mean silence: every hook kept working, every write appeared to succeed, and nothing reached anybody. The chain is BroadcastChannel → StorageTransport → NoopTransport now, and getTransportKind(name) reports what is actually carrying traffic.

    Leader election uses Web Locks where the platform has it, so a throttled background tab keeps its seat and failover after a crash is immediate rather than lease-length. Several copies of the library on one page now find each other and behave as one client instead of contending with themselves. And a quiet peer gets probed before it gets pruned — browsers clamp hidden tabs to about one timer tick a minute, which was enough to make the roster oscillate for a tab that never went anywhere.

    Jonatan Kruszewski

  37. Input fixes across OTP, currency and rating

    react-inputsfix

    Small bugs, all of them the kind you only meet by using the thing rather than testing it.

    • OTP — a pointer press no longer flashes a stale slot active before the pressed one.
    • OTP — typing over a full code replaces the character under the caret instead of being swallowed by maxLength.
    • OTP — keyboard focus parks the caret deterministically: first empty slot, or the last character when the code is full.
    • OTP — clicks land the caret in the slot actually pressed, including edges, separators and a scrolled field.
    • Currency — a keystroke that cannot contribute to the amount is rejected before it moves the value or the caret.
    • Currency — controlled hosts that echo onValueChange asynchronously no longer clobber the field with stale text.
    • Rating — the cursor no longer flickers back to the default in the gaps between icons.

    Also: the docs sidebar mark is keyed per component, GFM tables render in Storybook, and the autodocs prop tables are back with a test guarding them.

    Jonatan Kruszewski

  38. use-everywhere 0.5.0

    use-everywherereleasefixuse-everywhere@0.5.0

    The hardening pass before 1.0, aimed at the failure modes real tabs hit. A tab restored from the back/forward cache rejoins presence and the leader election instead of holding a phantom seat; a value that cannot survive structured clone throws before touching local state, where it used to silently diverge that tab from every peer; client ids come from Web Crypto now, so the tie-breaker can no longer collide.

    The hooks are also safe on a server at last — rendering in Next.js used to open transports and run elections on timers nothing cleared. SSR now gets inert doubles, useClientId hydrates cleanly, and the API’s silent conflicts warn out loud in dev.

    No React API changed, but anything asserting on client id shape sees the new 64-bit hex format.

    Jonatan Kruszewski

  39. Two new contracts, and Storybook on rxova.org

    WebsiteBrandreact-inputsuse-everywhereinfrafeature@rxova/website-schemas@0.6.0

    @rxova/website-schemas grew two contracts. Page-component bundles: a producer’s artifact now carries only its page bodies, and the website supplies the header, footer, global head and analytics at deploy time. The docs sites moved onto it through one shared mode instead of each repo keeping a copy.

    And a storybook source kind: a project’s workshop ingests like any other source and nests under one /storybook/ tree. The react-inputs workshop is the first through — live at rxova.dev/storybook/react-inputs/.

    Jonatan Kruszewski

  40. use-everywhere 0.6.0

    use-everywherereleasebreakinguse-everywhere@0.6.0

    MemoryHub and MemoryTransport moved to a testing subpath — they are a multi-tab simulation harness, not runtime API, and on the package root they were a 1.0 promise and a line in every production bundle’s module graph.

    import { MemoryHub } from 'use-everywhere';
    import { MemoryHub } from 'use-everywhere/testing';

    The React package also dropped export * from '@use-everywhere/core', which had made its public API implicitly whatever core happened to export. The list is written out now.

    Jonatan Kruszewski

  41. A Storybook workshop for the inputs

    react-inputsfeaturedocs

    Every input has stories now — currency, OTP and rating each get an args-driven Playground plus one story per behaviour worth showing off, with a toolbar toggle that flips the whole workshop to RTL.

    The prop tables are generated from the annotated source interfaces, so documenting a prop in a package updates its table here with no extra work. The workshop aliases the packages’ source directly: no build step in between, and library edits hot-reload into the stories.

    Jonatan Kruszewski

  42. Shared chrome, two new sections, and analytics on rxova.org

    WebsiteBrandfeature@rxova/brand@0.8.2

    One header and one footer across every page now, both from @rxova/brand.

    The landing gains a “Why Rxova” section and a maintainer block, with a longer /about page behind them.

    Traffic is measured by Cloudflare Web Analytics, chosen because it sets no cookies and does not track the reader — page path and referrer, in aggregate, and nothing else.

    Jonatan Kruszewski

  43. Images in posts, validated before they merge

    Brandfeature@rxova/website-schemas@0.3.0

    Posts can carry a cover and embed images, optimised through sharp on the way out.

    Alt text is required rather than encouraged, and a missing one is a failed build, not a review comment. Every embedded path is checked to resolve on the pull request that writes it — a typo’d image should not survive until the deploy.

    Jonatan Kruszewski

  44. The blog and this feed go live

    WebsiteBrandfeature@rxova/brand@0.5.0

    /blog and /updates are built in the brand repo and mounted on rxova.org as two more sources, through the same ingest path the project docs use.

    Both now sit under one shared layout with a real menu, and the components rxova.org had duplicated were deleted in favour of the ones @rxova/brand already exports.

    Jonatan Kruszewski

  45. The docs ingest contract

    Websitejourneyuse-everywherereact-inputsinfra

    rxova.org used to check out three repos and build their docs itself, which meant one project’s broken build took the whole site down with it.

    Each repo now builds its own docs and ships them as a release asset; the site ingests the artifact. A project either publishes something valid or the site keeps serving the last good copy.

    Jonatan Kruszewski

  46. Rxova.org gains a blog and an updates feed

    WebsiteBrandfeatureinfra

    I want to document how Rxova gets built, not only ship it. Two pieces were missing.

    The blog is for reasoning — why a design went one way instead of the obvious other way, and what broke on the way there.

    This feed is for what actually moved. It filters by repo and by tag, so you can follow one project without reading about the rest.

    Jonatan Kruszewski

  47. use-everywhere 0.4.0

    use-everywherereleaseuse-everywhere@0.4.0

    The Turborepo migration, the Starlight docs and a single-branch release model, cut as one release.

    No API changed. It is the release that makes the next ones cheaper.

    Jonatan Kruszewski

  48. Clearing the audit backlog

    journeyreact-inputsuse-everywherefixinfra

    pnpm audit is only useful if it is ever green. svgo was pinned to a vulnerable 3.3.3 by an override, so it got unpinned; brace-expansion, js-yaml, webpack-dev-server and dompurify were resolved behind it.

    Dropping Docusaurus left sixteen overrides pointing at packages no longer in the tree, and those went too — a stale override is a silent way to hold a dependency back.

    Jonatan Kruszewski

  49. One Turborepo standard across every repo

    journeyuse-everywherereact-inputsBrandinfra

    Four repos had drifted into four ways of running the same six checks. They now share one Turborepo pipeline and one CI shape, so a fix to the build order is made once.

    Node 24, TypeScript 6 and pnpm 11 across all of them, which also got pnpm audit working again.

    Jonatan Kruszewski

  50. Docusaurus out, Astro Starlight in

    use-everywherereact-inputsjourneydocsfeature

    All three docs sites moved to Astro Starlight. The landing already was Astro, so this leaves one framework across the whole origin — and dropped a webpack build that had been the flakiest job in every pipeline.

    react-inputs took the chance to restructure: one component library with a flat sidebar and shared guides, instead of three manuals bolted together.

    Jonatan Kruszewski

  51. A design system for rxova.org

    Brandrelease@rxova/brand@0.1.0

    Four surfaces on one origin only feel like one site if they share their tokens. @rxova/brand holds the colours, type and spacing, a Starlight theme built on them, and the header, footer and project switcher every surface mounts.

    Social cards are generated from the same tokens rather than drawn by hand, so a palette change reaches the previews too.

    Jonatan Kruszewski

  52. Every docs site points at the aggregator

    journeyreact-inputsuse-everywhereinfra

    Each project used to deploy its own GitHub Pages site at its own URL. All three stopped, and now take their url and baseUrl from the environment instead of hardcoding one.

    The same build therefore works standalone at / and mounted at /packages/<name>/, which is what let rxova.org host them without forking anything.

    Jonatan Kruszewski

  53. Three input libraries become one

    react-inputsreleasebreaking@rxova/react-inputs@0.1.1

    react-intl-currency-input, react-feedback-stars and react-otp-slots were three repos with three CI setups, three docs sites and three release processes for what is really one problem: the tricky React inputs.

    They are now @rxova/react-intl-currency-input, @rxova/react-rating-input and @rxova/react-otp-input, with @rxova/react-inputs as the umbrella if you want all three. The old package names are deprecated, and @rxova/codemod rewrites the imports for you.

    Jonatan Kruszewski

  54. rxova.org goes up as an aggregator

    Websitefeatureinfra

    One origin, several projects. An Astro landing sits at /, and each project’s docs are built in its own repo and mounted as a static tree under /packages/<name>/.

    That keeps every project owning its own docs while readers only ever learn one domain. It also means every cross-project link has to be absolute, which is the first thing the shared chrome had to get right.

    Jonatan Kruszewski

  55. Publishing over OIDC, with size budgets to match

    use-everywhereinfra

    npm publishes now run through trusted publishing — OIDC, no long-lived token in the repo — and every package carries provenance.

    The rest of the gate went in at the same time: a changeset is required before a published package can change, each public export has its own size-limit budget, and per-file coverage has to clear 95%.

    Jonatan Kruszewski

  56. The use-everywhere docs, rewritten as walkthroughs

    use-everywheredocs

    The docs were a hook list, which answers “what does this do” and never “why would I reach for it”. They are now walkthroughs that build something, with a per-hook reference behind them for when you already know what you want.

    A concepts layer, recipes, a testing guide and a limitations FAQ fill the gaps — including the honest list of what the library cannot do.

    Jonatan Kruszewski

  57. An Inspector, and typed channels bound once

    use-everywherefeature

    <Inspector /> shows the live bus — channels, peers, who is leader — and ships on a use-everywhere/devtools subpath so it stays out of the bundle unless it is asked for.

    defineChannel binds a channel and its message type at module level, so the shape is declared once instead of being repeated at every call site.

    Jonatan Kruszewski

  58. Leader election, so exactly one tab does the work

    use-everywherefeatureuse-everywhere@0.2.0

    Shared state is the easy half. The hard half is that five open tabs will happily run the same poll five times. Leader election picks one, and hands the role over when that tab closes.

    Persistence landed alongside it, opt-in rather than automatic, plus an observable debug seam on the bus for anyone who needs to see what is actually crossing it.

    Jonatan Kruszewski

  59. use-everywhere is up

    use-everywherereleaseinfrause-everywhere@0.1.0

    State and messages that exist in every tab, window and worker — the first cut is on npm. @use-everywhere/core owns the bus, and use-everywhere wraps it in React hooks.

    It arrived with the parts that are painful to add later: CI on every push, CodeQL, coverage gates, and licenses.

    Jonatan Kruszewski