fix: web UI review findings (step 2b)

- Saving the security settings threw: the form's values were named
  security, hiding the Security object the new token goes to. A save from
  None to None no longer reconnects, as the device closes nobody then.
- Each sync redraws the lists and reloads the settings, so a reconnect
  shows what changed meanwhile; closing the config panel drops its feeds
  even while the socket is down.
- A 401 on backup or an upload opens the login, as a refused socket does
  (Security.tokenRefused); a failed firmware upload says so.
- The general and security panels fill in what the device leaves out at
  its default, so a save never writes back what the form showed before.
  The hostname holds 31 characters, as the device does.
- The views (settings with defaults, a group's shades, frames, scans, the
  network and radio panels, op errors) move to data-src/js/views.js, with
  tests. runOp is the one op path with the busy retry; repeatOf,
  commandArgs, gone(); dead update*List and editedRepeaters removed.
- Tests for ProtoClient.off and a listener removing itself mid-event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-25 20:43:09 +01:00
parent 0df740df6a
commit 3212230f01
5 changed files with 357 additions and 208 deletions

View file

@ -315,6 +315,21 @@ test('waitFor gives null when no event comes, and stops listening', async () =>
assert.equal((c.listeners.room || []).length, 0);
});
test('off stops a listener, and one that removes itself mid-event leaves the others called', () => {
const { c, ws } = connected();
const seen = [];
const once = (d) => { seen.push('once'); c.off('memory', once); };
const always = () => seen.push('always');
const dropped = () => seen.push('dropped');
c.on('memory', once);
c.on('memory', always);
c.on('memory', dropped);
c.off('memory', dropped);
ws.deliver({ v: 2, t: T.memory, d: { fr: 1 } });
ws.deliver({ v: 2, t: T.memory, d: { fr: 2 } });
assert.deepEqual(seen, ['once', 'always', 'always']);
});
test('subscribe sends the feed number', () => {
const { c, ws } = connected();
c.subscribe('frames');