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. overlock is on rxova.org

    overlockBrandWebsitedocs

    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.

    Its docs are now live at /packages/overlock/, and it leads the rxova.org landing page.

    Jonatan Kruszewski

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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