Repository navigation
Conversation
|
Thanks Adam. This follows the shape agreed in #347 closely, and the isolation holds up:
A few things to change before merge, most important first. 1. Backend CI fails, from a timing race in a test. 2. A reinstalled plugin's counter can repeat an old value, and the ETag can't tell. New counters start from the boot What goes wrong on a display:
So the first write after a reset is lost on every display, which is the case this PR exists for. Suggested fix: one server-wide sequence for plugin counters, so a value is never reused within a boot: let pluginSeq = base;
// in bumpPlugin, instead of starting from base:
revisions[domain] = ++pluginSeq;(or put the keys in the ETag). It's also worth pruning in 3. Drop the Google Tasks route entry. 4. Reject 5. Plugin guide wording.
Smaller: the branch is two commits behind main (#362, #364). A rebase will re-run CI against them. 🤖 Review written with Claude Code |
…herforth#347) Per-plugin storage counters on the jherforth#339 path: a write moves only its own plugin's counter, pushed over the event stream within a second and caught up by the 15s GET /api/changes check, so plugins need polling only as the older-server fallback. The event carries the plugin id only, never a key; the frame reloads what it uses. Counters are created on a plugin's first storage write and dropped when its data is purged, so the set grows and shrinks with installed plugins; the ETag is built from the five fixed kinds plus the plugin kinds in sorted order. A frame receives only its own plugin's events: the dashboard subscribes per plugin id and forwards nothing else into the frame. Covered write paths: PUT, DELETE and POST increment through the request hook, server-side reactions, and uninstall with purge; reactions never fire on a storage change itself, which would self-trigger. The guide documents the no-key payload and the per-plugin scoping. Tests: per-path and isolation coverage in server/tests (unit + real server, incl. the stream carrying no key) and client tracker tests (a frame's subscription hears only its own plugin, baseline rules, prune on uninstall, reinstall reading as changed).
- Reaction stream test: wait out the coalescing window before opening the stream, and count only this test's own plugin's events, so an earlier test's burst can't be mistaken for this test's event. - Plugin counters now start from a server-wide sequence, never reused within a boot, so uninstall-with-purge plus reinstall can't repeat the old counter or ETag; the client also prunes vanished plugin keys on every 200, so a reinstall reads as changed even at an equal value. - Drop the dead /api/users/:id/google-tasks/sync route entry: HomeGlow has no such route. - Installing a manifest with a reaction on plugin.storage.changed now fails loudly instead of silently never running. - Guide: declaring the event requires a server with this support (no older-server fallback), and a frame hears its own writes too, so a write-on-load plus reload-on-event widget would loop.
11b5d9e to
a5f3879
Compare
|
Thanks for the review — all five points are addressed in the latest push, and the branch is rebased onto main (#362, #364):
Server tests (householdChanges, pluginStorageChanges, pluginPlatform, householdChangesRoutes: 58 pass) and client householdChanges tests (14 pass) are green locally; CI will re-run against the rebased branch. Generated with Muse AI 🤖 |
|
Thanks Adam. I re-checked a5f3879 on the rebased branch. Points 1, 3, 4 and 5 are done and look right:
The client pruning on every 200 is also good. One thing left on point 2: the counter can still repeat. My snippet was ambiguous here, sorry. The sequence is only used when a counter is created, and later writes still do The window is narrower now, thanks to the client prune. It only bites when the purge and the reinstall's first write both happen between two of a display's checks (15 s, or longer while the tab is hidden). In that case the display sends the old ETag and gets 304, and the stream event is dropped as "already known". The fix is to take the next sequence value on every plugin bump: function bumpPlugin(pluginId) {
if (typeof pluginId !== 'string' || pluginId.length === 0) return;
const domain = pluginDomain(pluginId);
revisions[domain] = ++pluginSeq;
pending.add(domain);
scheduleFlush();
}Then no plugin counter value is ever reused within a boot. Every write and every purge changes the set of values, so the ETag can't repeat either, even without keys in it. The new server test passes only because it creates With that change I'm happy with this. 🤖 Review written with Claude Code |
A create-only sequence still lets a plugin's own count climb back onto a value it had before a purge (two writes, purge, reinstall, one write repeats the old counter and ETag), so a display holding the old ETag reads a 304 and drops the stream event as already known between checks. Every bump now takes ++pluginSeq, so no plugin counter value is ever reused within a boot. The reinstall test no longer leans on an intervening write to another plugin: two writes, purge, reinstall, one write must give a fresh counter and ETag. The moves-only-that-counter test expectation shifts accordingly (other takes 103, since lists took 101 and 102). Generated with Muse AI
|
Thanks — good catch, and thanks for spelling out the remaining case. Fixed in the latest push:
Server tests (householdChanges, pluginStorageChanges, pluginPlatform, householdChangesRoutes: 58 pass) and client householdChanges tests (14 pass) are green locally; CI will re-run against the new push. Generated with Muse AI 🤖 |
|
Read through the latest push, not run. The fixes Jay asked for look right. Three things left, the first one being the one I'd settle before merge:
Smaller: declaring this event without |
1. Restart seeding: seedPlugins() at boot seeds a counter for every
plugin with stored data (distinct plugin_storage rows), so frames
read as moved after a restart instead of silently pruning a
vanished key and showing stale data until the next write.
2. Self-write detection: storage writes return their revision
(PUT/DELETE { success, revision }, increment adds revision),
bumped in-route so the response carries the exact revision the
event will carry; the tracker and wrapper pass the revision
through to the frame, which compares it against its own writes'
revisions. The event still carries no key and no value.
3. Visible-mode (phones) deafness: in visible mode the stream now
establishes the baseline instead of dropping events until a
first GET succeeds; interval-mode behavior is unchanged.
4. Install rejection: declaring plugin.storage.changed without
storage: true now fails at install, like reactions do.
5. Docs: backend-api.md describes the dynamic plugin:<pluginId>
counters alongside the fixed household kinds; the plugin guide
covers the restart promise, revision comparison, phone
behavior, and the install failure.
Tests: server unit + real-server revision/restart tests, client
tracker visible-mode tests, platform install-rejection test; full
client (891) and server (713) suites green.
|
Thanks @mrramam — all three points plus the two smaller fixes are in, pushed as bf48ce4.
Tests: a server unit test proving a restart makes plugin frames read as moved (seeded value differs, re-seeding is a no-op, the first write after a seed takes the next value, seeding sends no event), plus a real-server restart test (write, restart on the same database, counter present and changed); a real-server test that PUT/DELETE/increment return their revision and the stream event carries the same one; client tests for the visible-mode failed-first-check-then-stream case (plugin and household); a platform test for the install rejection; and updated client assertions for the revision argument. Full client suite 891/891, full server suite 713/713, vite build and i18n check green. The PR awaits re-review — happy to adjust if you or Jay see anything else. Generated with Muse AI 🤖 |
Implements #347: plugins can now hear about storage writes without polling.
plugin.storage.changedwith{ pluginId }only — no key, no value — and reloads what it uses. The plugin guide says so explicitly, so authors don't expect a key.GET /api/changescheck. Counters live underplugin:<pluginId>, are created on a plugin's first storage write, and drop on uninstall-with-purge (kept without purge, since the data is kept). The ETag stays deterministic (fixed kinds plus plugin kinds in sorted order);GET /api/changesreturns the full map;household.changedpayloads still carry household kinds only.plugin.storage.changeditself, since the reaction writes plugin storage and would otherwise re-trigger in a loop.Tests: new server unit tests (routing, flush partitioning, ETag determinism, prune), new real-server tests for each write path plus the no-key payload, purge behavior, and ETag movement, and extended client tracker tests including frame isolation (a subscription hears only its own plugin from both the check and the stream). Full client and server suites pass, as do the build and i18n checks.
Generated with Muse AI 🤖