diff --git a/docs/firewall-tunnel-followup-20261008.md b/docs/firewall-tunnel-followup-20261008.md index dc54066b..1a40c23e 100644 --- a/docs/firewall-tunnel-followup-20261008.md +++ b/docs/firewall-tunnel-followup-20261008.md @@ -27,9 +27,29 @@ local JSON. They are not exposed as controls that claim to open or close real po The existing OpenWrt management screen remains linked. Actual node firewall rule inspection/editing and the complete app exposure workflow are still separate work. -Validation checkpoint: 21 focused UI tests passed before the final background -resolver and pending-operation display additions. Those additions, the complete -app typecheck, responsive rendered-route/background checks and production build -remain queued behind IndeeHub recovery qualification. No live acceptance is claimed. -The backend peer-safety changes are isolated separately and also require tests and -paired helper deployment before these new mutation controls can be enabled. +Validation: current focused tests pass 25/25 (22 component cases plus three +background resolver cases), and the full app typecheck passes. Typecheck exposed +an unsupported replaceAll call and a test-wrapper assertion; both were corrected +and the changed device test file was rerun successfully (16/16). + +Source browser fixtures pass at 320, 390, 768 and 1440 pixels: full content width, +right-aligned values, no horizontal overflow, QR containment and unavailable +mutation controls against the old-backend fixture. A routed source fixture using +the same background resolver preserved the rendered Network background when +entering Firewalls & tunnels. This is not a full production Dashboard acceptance +or a live-node test. All RPC replies were synthetic; no node mutation occurred. + +Local receipts: +- `/tmp/archy-firewall-focused-current-20261008.log` — three background tests. +- `/tmp/archy-firewall-focused-components-20261008.log` — 22 component tests. +- `/tmp/archy-firewall-device-final-20261008.log` — corrected device tests. +- `/tmp/archy-firewall-typecheck-20261008.log` — successful exit 0, no diagnostics. +- `/tmp/archy-firewall-responsive-20261008.log` — all four rendered viewport cases. +- `/tmp/archy-firewall-fixture-server.mjs` and + `/tmp/archy-firewall-responsive.cjs` — the source fixture harnesses. + +Production build and live acceptance remain pending. The backend peer-safety +changes are isolated separately; actual ephemeral-kernel helper qualification +passed, but Rust compilation/tests and paired helper deployment remain required +before the new mutation controls can be enabled on a node. Task 18 remains open +for broader host firewall management, persistence and rollback. diff --git a/neode-ui/src/views/server/VpnDeviceSettings.test.ts b/neode-ui/src/views/server/VpnDeviceSettings.test.ts index 028d1522..5398f05f 100644 --- a/neode-ui/src/views/server/VpnDeviceSettings.test.ts +++ b/neode-ui/src/views/server/VpnDeviceSettings.test.ts @@ -62,7 +62,7 @@ describe('Private device management', () => { const w = view(); await flushPromises(); await w.get('[aria-label="Remove Phone"]').trigger('click') expect(vi.mocked(rpcClient.call).mock.calls.filter(([a]) => a.method === 'vpn.remove-peer')).toHaveLength(0) await button(w, 'Remove device').trigger('click'); await flushPromises() - expect(w.text()).toContain('Could not confirm removal'); expect(w.get('[aria-label="Remove Phone"]').exists()).toBe(true); w.unmount() + expect(w.text()).toContain('Could not confirm removal'); expect(w.find('[aria-label="Remove Phone"]').exists()).toBe(true); w.unmount() }) it('keeps a failed list distinct from no devices and disables creation', async () => { vi.mocked(rpcClient.call).mockRejectedValue(new Error('offline')) diff --git a/neode-ui/src/views/server/VpnDeviceSettings.vue b/neode-ui/src/views/server/VpnDeviceSettings.vue index 6b3dfe28..23a4a59d 100644 --- a/neode-ui/src/views/server/VpnDeviceSettings.vue +++ b/neode-ui/src/views/server/VpnDeviceSettings.vue @@ -18,7 +18,7 @@ const removeName = ref('') const copied = ref(false) const needsRefresh = ref(false) const safeName = (value: string) => /^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$/.test(value) -const filename = (value: string) => value.toLowerCase().replaceAll(' ', '-') +const filename = (value: string) => value.toLowerCase().replace(/ /g, '-') const canCreate = computed(() => props.statusKnown && props.configured && props.managementVerified && devices.value !== null && !needsRefresh.value && safeName(name.value.trim()) && !devices.value.some(d => filename(d.name) === filename(name.value.trim()))) let active = true let request = 0