GitShow/developit/mixed-signals
developit

mixed-signals

Use Preact Models + Signals from a server as if they lived on the client.

by developit
preactsignals
Star on GitHubForkWebsitenpm

TypeScript

43 stars8 forks6 contributorsActive · 1d agoSince 2026v0.3.0MIT

Meet the team

See all 6 on GitHub →
developit
developit27 contributions
CopilotBot
Copilot3 contributions
github-actions[bot]Bot
github-actions[bot]2 contributions
airhorns
airhorns1 contribution
jed
jed1 contribution
infiton
infiton1 contribution

Languages

View on GitHub →
TypeScript95.9%
JavaScript4.1%

Commit activity

Last 12 weeks · 9 commits

Full graph →

Community health

2 of 6 standards met

Community profile →
42
✓README✓License○Contributing○Code of Conduct○Issue Template○PR Template

Recent PRs & issues

Active · Last activity 1d ago
See all on GitHub →
JoviDeCroock
Add process-aware reconnect supportOpenPR

Problem Reconnects currently reset or stale out reflected client state, which breaks held roots, signals, model facades, and subscriptions when a transport reconnects or lands on a different backend process. Solution Preserve client reflection state across transport replacement, use server connection metadata to detect process changes, refresh/rebind roots and active held reflected models in place, replay active subscriptions, and forward held-model refreshes through upstream broker chains. Reconnect reconciliation only preserves explicit protocol identities ( signals and model facades); unbranded nested arrays/plain objects are replaced rather than reconciled by index or shape. For backwards compatibility with legacy servers that do not emit connection metadata yet, a reconnect without is treated as an unknown/new process. Reconnect flows Same process unchanged signal ids usually still match client keeps the root shell, signals, and model facades unbranded nested arrays/plain objects are replaced root snapshot refreshes values in place for preserved signal/model identities subscriptions replay active held model facades not already present in the reconnect root are refreshed via one batched call is true only if the old connection state was still active New or unknown process changes, or the frame omits connection metadata client drops raw signal-id mappings before hydration all known model facades are marked stale until refreshed by the root snapshot or old signal objects are rebound to new server ids during root/model refresh when they appear through preserved root/model identities active held models not present in the root are refreshed via one batched call inactive held models are not eagerly refreshed; if one of their signal props becomes watched later, the client lazily refreshes that stale model and then replays the newly rebound signal id subscriptions replay once immediately and once after model refresh works best when model ids are stable and resolvable on the new process

JoviDeCroock · 16h ago
developit
Drop per-signal watch/unwatch timers, fix watch/unwatch raceOpenPR

getOrCreateSignal() was creating and clearing a setTimeout on every watched()/unwatched() call just to debounce rapid subscribe/unsubscribe cycles, on top of the global 1ms watch/unwatch batch timers. That's a lot of timer churn for something the batch timers can already do. scheduleWatch()/scheduleUnwatch() now use a 10ms window (up from 1ms) and each removes the signal id from the other pending batch before adding it to its own. That means a quick unmount/remount cancels a pending unwatch (or a quick remount/unmount cancels a pending watch) for free, so the per-signal debounce timer in getOrCreateSignal() can be deleted entirely: watched() and unwatched() now call scheduleWatch()/scheduleUnwatch() directly. This also fixes a real bug: watchBatch and unwatchBatch were tracked completely independently, so it was possible for the same signal id to be present in both batches at once and get flushed as a batched watch and a batched unwatch in the same tick. Deleting the id from the opposite batch in scheduleWatch()/scheduleUnwatch() makes the two sets mutually exclusive. Net effect: two shared timers total instead of N+2, and no more possibility of a same-tick watch/unwatch race for one id.

developit · 1d ago
rafbcampos
Optimistic UI for reflected signalsOpenPR

This adds optimistic UI for reflected signals and hardens it for a unilateral server data flow, where the server pushes authoritative deltas independent of RPC replies. What you get lets you mutate the very reflected signal the UI renders, bound to the action that will make the change real. The change is layered over the live signal, incoming server deltas keep updating a hidden base, and the overlay reconciles to the server value once the action settles or rolls back if it rejects. Array changes reconcile by a primitive key, so inserts, removes, replaces, and moves settle by element identity rather than by position. Reconciling by mutation identity The two hard cases in a unilateral flow are an optimistic edit racing another user's delta, and a reconnect to a different node while a mutation is in flight. To handle them the server echoes the wire call id on the deltas a call produces, to that call's originating client only, and tags its patches with the same id. A change confirms when its id comes back rather than when the base value happens to match. A server-side normalization of the caller's own edit still confirms and displays, and a concurrent write from another client no longer masquerades as confirmation. It is reported through instead, and the optimistic value keeps shadowing until the change is confirmed, so an unconfirmed edit is never overwritten on screen. Value comparison stays as the fallback when no id is present, so an existing server keeps working unchanged. On , in-flight calls reject with . The outcome is unknown rather than failed, since the mutation may have committed on the previous connection, and optimistic overlays roll back to the last server value. Observability and safety takes an optional third argument with , , and , and the returned handle exposes as , , , or . Applying a change to a concurrently moving server value can never wedge the read loop: a transform or predicate that throws is dropped and reported through , and un-keyable server rows are skipped. User callbacks run after each update commits, so a callback that rolls back cannot diverge the handle state from what is rendered. Behavior changes Signal writes made inside one server method call are coalesced into a single delta, since each call body runs in a batch. A signal update that removes an object key is sent as a full replacement, because a merge delta cannot express deletion. Notes is non-distributive and exposes a nested reflected signal as , both of which can surface as new compile errors in code that relied on the looser types. A smaller client-only version of this work is available as a separate fallback PR if you prefer to land optimistic UI without the server-side echo.

rafbcampos · 1d ago

Recent fixes

View closed PRs →
rafbcampos
feat: optimistic UI primitive for reflected signalsMergedPR

Closes #17. Supersedes #18 (generalizes the list-only overlay into a shape-agnostic primitive, per the discussion there). Summary Adds a client-side optimistic-UI primitive for reflected signals. Reflected signals are server-owned read-only computeds, so optimism is layered on top as a derived overlay rather than written into the source. Each optimistic change is a pure patch . The overlay value is the source folded through the still-pending patches, and patches are pruned automatically once the server reflects them. Dedup, realignment, and rollback are handled for the user. API — / / — / (key-safe via ) — — the core for arbitrary changes (e.g. compaction that splices the list) Render the overlay's , never the source. Lifecycle Pass the action promise as the last argument: a rejection rolls the change back, a resolution marks it confirmed. Reconciliation is driven by the source, not the promise, so the optimistic value stays on screen until the confirming delta arrives — a single update, no flicker. Without an action, drive it manually with / . Reconciliation is by exact match against the source ( by default; pass on / when the server normalizes a write, e.g. trims a string). Confirmation never settles a change against a value it does not match, so a stale or unrelated delta cannot drop a newer optimistic write. Supersession Within a shape, the latest change to a target supersedes earlier pending ones: writing a value back to what the source holds cancels cleanly (no leftover overlay), and cancels a still-pending / of the same item rather than stacking beneath it. The core treats each patch independently against the bare server value, so cancellation there is via , not an opposing patch. Why only lists require a matcher A confirmed list item is a fresh server object with a new id, so a client-generated correlation (echoed by the server) is the only robust way to recognize it. A list reconciles purely on that key — resolving its action does not settle it, so a server that never echoes the key (with no ) leaves the optimistic item beside the server copy. is an escape hatch for servers that echo it under a different field. Objects reconcile on the property name and values on identity (or a custom ), so neither needs a matcher. Note: reflected objects use deltas that cannot remove a key, so an only reconciles when the server sends a full replacement (or the action confirms a delete of an optimistic-only key).

rafbcampos · 2d ago
rafbcampos
feat: add optimistic list overlayMergedPR

fix #17 Summary Adds a client-side optimistic list overlay for reflected list signals. Changes Adds to the client API. Supports optimistic inserts without mutating the reflected source signal. Tracks pending optimistic items separately from server-confirmed source data. Reconciles optimistic entries when matching server data appears. Supports rollback, remove, clear, and dispose behavior. Adds test coverage for optimistic overlay behavior, reconciliation, rollback, pruning, and ownership checks. Updates README generation so optional API members render with ?.

rafbcampos · 2d ago
Structured data for AI agents

Repository: developit/mixed-signals. Description: Use Preact Models + Signals from a server as if they lived on the client. Stars: 43, Forks: 8. Primary language: TypeScript. Languages: TypeScript (95.9%), JavaScript (4.1%). License: MIT. Homepage: https://npm.im/mixed-signals Topics: preact, signals. Latest release: v0.3.0 (1mo ago). Open PRs: 12, open issues: 1. Last activity: 1d ago. Community health: 42%. Top contributors: developit, Copilot, github-actions[bot], airhorns, jed, infiton.

·@ofershap

Replace github.com with gitshow.dev