Rxova
← All repos

use-everywhere

Updates mentioning use-everywhere. See the repository ↗ for commits and releases.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. @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

  8. 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.org/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

  9. 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

  10. 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

  11. 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 BroadcastChannelStorageTransportNoopTransport 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

  12. 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

  13. 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.org/storybook/react-inputs/.

    Jonatan Kruszewski

  14. 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

  15. 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

  16. 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

  17. 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

  18. 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

  19. 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

  20. 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

  21. 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

  22. 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

  23. 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

  24. 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

  25. 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