diff --git a/AGENTS.md b/AGENTS.md index 79c3643..5c5d4ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ Conventions: - `h()` builds VNodes with `on:click` prefix. `html` tag (htm) templates use camelCase `onClick` (adapter translates). - State always has `loading`, `refreshing`, `error` plus data. `load()` receives `(state, abortController, entry)`. - `openModal` + `formModal` for dialogs; `apiSubmit()` for form submission. -- No build step — ES modules served raw. Assets versioned via `?v=N` query string. +- No build step — ES modules served raw. Cache controlled via HTTP headers. ### Daemon Endpoints diff --git a/docs/architecture.md b/docs/architecture.md index 59e0b5d..6ab9948 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -348,7 +348,7 @@ Each route is a `definePage()` component with reactive state, async data loading ### No Build Step -All JavaScript is served as ES modules. The `?v=N` query string param version-pins asset imports for cache invalidation. Dev mode (`VACUUM_WALL_DEV`) disables aggressive static asset caching. +All JavaScript is served as ES modules. Cache invalidation is handled via HTTP cache-control headers. Dev mode (`VACUUM_WALL_DEV`) disables aggressive static asset caching. ### WebSocket Broadcast diff --git a/docs/hoover.md b/docs/hoover.md index 3cb0cce..5cd42be 100644 --- a/docs/hoover.md +++ b/docs/hoover.md @@ -55,7 +55,7 @@ The app starts from `webui/static/app.js`: ```javascript import { h, render, Link, hComp, ToastContainer, connect, apiFetch, - modelRegister, modelFetch, reactive } from '/static/hoover/index.js?v=8'; + modelRegister, modelFetch, reactive } from '/static/hoover/index.js'; // 1. Register subsystem models modelRegister('firewall', { @@ -1151,9 +1151,10 @@ Render the toast notification container. Include in the main render root. See AP | `parseZones(data)` | Parse zone data from API responses into a flat string array | | `downloadBlob(blob, filename)` | Trigger a browser file download from a Blob | -## Versioned Imports +## Static Asset Caching -Static assets in `app.js` are imported with querystring version pins (e.g., `?v=8`) to invalidate browser cache when the framework changes. Page imports also include version pins (e.g., `?v=9`). The server handles caching headers; the version query string ensures browser cache invalidation. +The server handles caching headers for static assets. Browser cache invalidation is managed +through server-side cache-control headers rather than query string version pins. Dev mode (`VACUUM_WALL_DEV` set) disables aggressive static asset caching. diff --git a/webui/static/app.js b/webui/static/app.js index 2a18bbe..ce3921d 100644 --- a/webui/static/app.js +++ b/webui/static/app.js @@ -1,17 +1,17 @@ -import { h, render, Link, hComp, ToastContainer, connect, apiFetch, modelRegister, modelFetch, reactive, initAuth, getAuthToken, checkSession } from '/static/hoover/index.js?v=11'; +import { h, render, Link, hComp, ToastContainer, connect, apiFetch, modelRegister, modelFetch, reactive, initAuth, getAuthToken, checkSession } from '/static/hoover/index.js'; -import DashboardPage from '/static/pages/dashboard.js?v=11'; -import InterfacesPage from '/static/pages/interfaces.js?v=9'; -import ZonesPage from '/static/pages/zones.js?v=9'; -import RulesPage from '/static/pages/rules.js?v=9'; -import NatPage from '/static/pages/nat.js?v=9'; -import DhcpPage from '/static/pages/dhcp.js?v=9'; -import ProxyPage from '/static/pages/proxy.js?v=10'; -import BackendsPage from '/static/pages/backends.js?v=10'; -import CertsPage from '/static/pages/certs.js?v=9'; -import WireguardPage from '/static/pages/wireguard.js?v=9'; -import LogsPage from '/static/pages/logs.js?v=9'; -import NotFoundPage from '/static/pages/notfound.js?v=9'; +import DashboardPage from '/static/pages/dashboard.js'; +import InterfacesPage from '/static/pages/interfaces.js'; +import ZonesPage from '/static/pages/zones.js'; +import RulesPage from '/static/pages/rules.js'; +import NatPage from '/static/pages/nat.js'; +import DhcpPage from '/static/pages/dhcp.js'; +import ProxyPage from '/static/pages/proxy.js'; +import BackendsPage from '/static/pages/backends.js'; +import CertsPage from '/static/pages/certs.js'; +import WireguardPage from '/static/pages/wireguard.js'; +import LogsPage from '/static/pages/logs.js'; +import NotFoundPage from '/static/pages/notfound.js'; import LoginPage from '/static/pages/login.js'; import PasskeysPage from '/static/pages/passkeys.js'; import UsersPage from '/static/pages/users.js'; diff --git a/webui/static/hoover/api.js b/webui/static/hoover/api.js index 2bf8cf9..4bae2f8 100644 --- a/webui/static/hoover/api.js +++ b/webui/static/hoover/api.js @@ -6,9 +6,9 @@ * Modal processing guard for async form submissions. */ -import { modelFetch } from './model.js?v=10'; -import { requestUpdate } from './reactivity.js?v=9'; -import { isModalProcessing, setModalProcessing, refreshModals } from './components/modal.js?v=9'; +import { modelFetch } from './model.js'; +import { requestUpdate } from './reactivity.js'; +import { isModalProcessing, setModalProcessing, refreshModals } from './components/modal.js'; /** * Global state — shared with auth.js component. diff --git a/webui/static/hoover/component.js b/webui/static/hoover/component.js index 954eadf..11b876f 100644 --- a/webui/static/hoover/component.js +++ b/webui/static/hoover/component.js @@ -15,9 +15,9 @@ * }); */ -import { reactive } from './reactivity.js?v=9'; -import { h } from './vdom.js?v=9'; -import { _compExpandedCache } from './render.js?v=9'; +import { reactive } from './reactivity.js'; +import { h } from './vdom.js'; +import { _compExpandedCache } from './render.js'; /** Registry of mounted components: key → { state } */ const _mounted = new Map(); diff --git a/webui/static/hoover/components/applyconfirm.js b/webui/static/hoover/components/applyconfirm.js index e0d6e70..7301114 100644 --- a/webui/static/hoover/components/applyconfirm.js +++ b/webui/static/hoover/components/applyconfirm.js @@ -6,12 +6,12 @@ * expandable modal, then applies all via /api/status/apply-all. */ -import { h } from '../vdom.js?v=9'; -import { html } from '../html.js?v=9'; -import { reactive } from '../reactivity.js?v=9'; -import { apiFetch, toast } from '../api.js?v=9'; -import { modelFetch } from '../model.js?v=9'; -import { openModal, closeModal, modalVNodes, isModalProcessing, setModalProcessing, refreshModals } from './modal.js?v=9'; +import { h } from '../vdom.js'; +import { html } from '../html.js'; +import { reactive } from '../reactivity.js'; +import { apiFetch, toast } from '../api.js'; +import { modelFetch } from '../model.js'; +import { openModal, closeModal, modalVNodes, isModalProcessing, setModalProcessing, refreshModals } from './modal.js'; export const SUBSYSTEM_LIST = [ { key: 'firewall', label: 'Firewall' }, diff --git a/webui/static/hoover/components/auth.js b/webui/static/hoover/components/auth.js index 786f838..dae6654 100644 --- a/webui/static/hoover/components/auth.js +++ b/webui/static/hoover/components/auth.js @@ -4,7 +4,7 @@ * Token refresh scheduler, session check, logout. */ -import { apiFetch, setAuthToken, clearAuthTokens, redirectLogin, getAuthToken, tryRefreshToken, toast } from '../api.js?v=12'; +import { apiFetch, setAuthToken, clearAuthTokens, redirectLogin, getAuthToken, tryRefreshToken, toast } from '../api.js'; /** * Schedule a token refresh based on the access token TTL stored in sessionStorage. diff --git a/webui/static/hoover/components/data.js b/webui/static/hoover/components/data.js index 5756b4c..6605bcd 100644 --- a/webui/static/hoover/components/data.js +++ b/webui/static/hoover/components/data.js @@ -4,11 +4,11 @@ * Data display components: Badge, StatusDot, Empty, Card. */ -import { h } from '../vdom.js?v=9'; -import { esc } from '../helpers.js?v=9'; -import { apiFetch, toast } from '../api.js?v=9'; -import { modelFetch } from '../model.js?v=9'; -import { requestUpdate } from '../reactivity.js?v=9'; +import { h } from '../vdom.js'; +import { esc } from '../helpers.js'; +import { apiFetch, toast } from '../api.js'; +import { modelFetch } from '../model.js'; +import { requestUpdate } from '../reactivity.js'; const _actionPending = new Map(); const _confirmPending = new Map(); diff --git a/webui/static/hoover/components/layout.js b/webui/static/hoover/components/layout.js index 8c89191..16cf573 100644 --- a/webui/static/hoover/components/layout.js +++ b/webui/static/hoover/components/layout.js @@ -4,9 +4,9 @@ * Layout components: PageHeader, Tabs, SectionTitle, DataTableSection, ActionGroup. */ -import { h } from '../vdom.js?v=9'; -import { Table } from './data.js?v=9'; -import { collectLoadingModels } from '../model.js?v=9'; +import { h } from '../vdom.js'; +import { Table } from './data.js'; +import { collectLoadingModels } from '../model.js'; /** * Page header with title, optional subtitle, and action buttons. diff --git a/webui/static/hoover/components/modal.js b/webui/static/hoover/components/modal.js index 2703106..ea2ec06 100644 --- a/webui/static/hoover/components/modal.js +++ b/webui/static/hoover/components/modal.js @@ -6,9 +6,9 @@ * avoid fighting with the main render cycle. */ -import { esc, att_esc } from '../helpers.js?v=9'; -import { apiSubmit } from '../api.js?v=9'; -import { createDom } from '../vdom.js?v=9'; +import { esc, att_esc } from '../helpers.js'; +import { apiSubmit } from '../api.js'; +import { createDom } from '../vdom.js'; /** * Render Hoover VNodes into a modal content element. diff --git a/webui/static/hoover/components/qr.js b/webui/static/hoover/components/qr.js index 6160cf6..a148445 100644 --- a/webui/static/hoover/components/qr.js +++ b/webui/static/hoover/components/qr.js @@ -5,8 +5,8 @@ * Uses qrcode-svg library for generation. */ -import { h } from '../vdom.js?v=9'; -import { esc } from '../helpers.js?v=9'; +import { h } from '../vdom.js'; +import { esc } from '../helpers.js'; import QRCode from '../../../vendor/qrcode-svg-1.1.0.js'; /** diff --git a/webui/static/hoover/components/toast.js b/webui/static/hoover/components/toast.js index dad0bc1..a251094 100644 --- a/webui/static/hoover/components/toast.js +++ b/webui/static/hoover/components/toast.js @@ -5,8 +5,8 @@ * Uses the toast/dismissToast state from api.js. */ -import { h } from '../vdom.js?v=9'; -import { _toasts, dismissToast } from '../api.js?v=9'; +import { h } from '../vdom.js'; +import { _toasts, dismissToast } from '../api.js'; /** * Render all pending toast notifications. diff --git a/webui/static/hoover/html.js b/webui/static/hoover/html.js index 6469d27..8b31d65 100644 --- a/webui/static/hoover/html.js +++ b/webui/static/hoover/html.js @@ -1,4 +1,4 @@ import htm from '../../vendor/htm.js'; -import { htmAdapter } from './vdom.js?v=9'; +import { htmAdapter } from './vdom.js'; export const html = htm.bind(htmAdapter); diff --git a/webui/static/hoover/index.js b/webui/static/hoover/index.js index 58243fa..e164926 100644 --- a/webui/static/hoover/index.js +++ b/webui/static/hoover/index.js @@ -5,52 +5,52 @@ */ /* ── Reactivity ──────────────────────────────────────────────── */ -export { reactive, requestUpdate } from './reactivity.js?v=9'; +export { reactive, requestUpdate } from './reactivity.js'; /* ── VDOM ────────────────────────────────────────────────────── */ -export { h } from './vdom.js?v=9'; +export { h } from './vdom.js'; /* ── HTM ──────────────────────────────────────────────────────── */ -export { html } from './html.js?v=9'; +export { html } from './html.js'; /* ── Render ──────────────────────────────────────────────────── */ -export { render } from './render.js?v=9'; +export { render } from './render.js'; /* ── Component ───────────────────────────────────────────────── */ -export { definePage, hComp } from './component.js?v=10'; +export { definePage, hComp } from './component.js'; /* ── Router ──────────────────────────────────────────────────── */ -export { createRouter, Link } from './router.js?v=9'; +export { createRouter, Link } from './router.js'; /* ── WebSocket ───────────────────────────────────────────────── */ -export { connect, onMessage } from './websocket.js?v=10'; +export { connect, onMessage } from './websocket.js'; /* ── API & Toast ─────────────────────────────────────────────── */ -export { apiFetch, toast, dismissToast, apiSubmit, checkAbort, poll, refactorLoad, formAction, setAuthToken, clearAuthTokens, getAuthToken } from './api.js?v=12'; +export { apiFetch, toast, dismissToast, apiSubmit, checkAbort, poll, refactorLoad, formAction, setAuthToken, clearAuthTokens, getAuthToken } from './api.js'; /* ── UI Components: Auth ──────────────────────────────────────── */ -export { scheduleTokenRefresh, cancelTokenRefresh, checkSession, logout, initAuth, handleLoginSuccess, webauthnSupported, startRegistration, startAuthentication } from './components/auth.js?v=2'; +export { scheduleTokenRefresh, cancelTokenRefresh, checkSession, logout, initAuth, handleLoginSuccess, webauthnSupported, startRegistration, startAuthentication } from './components/auth.js'; /* ── Model ───────────────────────────────────────────────────── */ -export { modelRegister, getModel, modelFetch, collectLoadingModels } from './model.js?v=9'; +export { modelRegister, getModel, modelFetch, collectLoadingModels } from './model.js'; /* ── Helpers ─────────────────────────────────────────────────── */ -export { esc, att_esc, enc, $val, parseZones, downloadBlob } from './helpers.js?v=9'; +export { esc, att_esc, enc, $val, parseZones, downloadBlob } from './helpers.js'; /* ── UI Components: Layout ───────────────────────────────────── */ -export { PageHeader, renderGuard, renderGuardMulti, Tabs, SectionTitle, ActionGroup, DataTableSection } from './components/layout.js?v=9'; +export { PageHeader, renderGuard, renderGuardMulti, Tabs, SectionTitle, ActionGroup, DataTableSection } from './components/layout.js'; /* ── UI Components: Data ─────────────────────────────────────── */ -export { Badge, StatusDot, Empty, Card, ConfirmDelete, Table, ActionButton, certStatusBadge, serviceStatusBadge, StatCard, StatusText, ServiceStatus, ActionCell, MonoText, ZoneSelect } from './components/data.js?v=9'; +export { Badge, StatusDot, Empty, Card, ConfirmDelete, Table, ActionButton, certStatusBadge, serviceStatusBadge, StatCard, StatusText, ServiceStatus, ActionCell, MonoText, ZoneSelect } from './components/data.js'; /* ── UI Components: Modal ────────────────────────────────────── */ -export { openModal, closeModal, closeAllModals, modalVNodes, refreshModals, formModal, MultiSelectModal, QuickModal, isModalProcessing, setModalProcessing } from './components/modal.js?v=10'; +export { openModal, closeModal, closeAllModals, modalVNodes, refreshModals, formModal, MultiSelectModal, QuickModal, isModalProcessing, setModalProcessing } from './components/modal.js'; /* ── UI Components: Apply ────────────────────────────────────── */ -export { ApplyConfirm } from './components/applyconfirm.js?v=9'; +export { ApplyConfirm } from './components/applyconfirm.js'; /* ── UI Components: Toast ────────────────────────────────────── */ -export { ToastContainer } from './components/toast.js?v=9'; +export { ToastContainer } from './components/toast.js'; /* ── UI Components: QR Code ──────────────────────────────────── */ -export { qrSVG, QRCodeVNode, LogoUpload } from './components/qr.js?v=9'; +export { qrSVG, QRCodeVNode, LogoUpload } from './components/qr.js'; diff --git a/webui/static/hoover/model.js b/webui/static/hoover/model.js index f7a7f69..a057257 100644 --- a/webui/static/hoover/model.js +++ b/webui/static/hoover/model.js @@ -13,7 +13,7 @@ * collectLoadingModels(...models) — combine loading/refreshing/error */ -import { reactive } from './reactivity.js?v=9'; +import { reactive } from './reactivity.js'; /** Registered models: name → { model, subsystem, fetch } */ const _models = new Map(); diff --git a/webui/static/hoover/render.js b/webui/static/hoover/render.js index 2368792..1b62010 100644 --- a/webui/static/hoover/render.js +++ b/webui/static/hoover/render.js @@ -5,12 +5,12 @@ * batched re-render loop integration with reactivity.js. */ -import { requestUpdate, setCommitFn } from './reactivity.js?v=9'; +import { requestUpdate, setCommitFn } from './reactivity.js'; import { _vnodeDom, createDom, getDom, patchNode, sweepDom, setMountFn, setUnmountFn, -} from './vdom.js?v=9'; -import { mountComponent, unmountComponent } from './component.js?v=10'; +} from './vdom.js'; +import { mountComponent, unmountComponent } from './component.js'; /** Container → previous root vnodes */ export const _renderSlots = new Map(); diff --git a/webui/static/hoover/router.js b/webui/static/hoover/router.js index 3d1dcf4..e34321a 100644 --- a/webui/static/hoover/router.js +++ b/webui/static/hoover/router.js @@ -5,8 +5,8 @@ * navigation). Link component for client-side navigation. */ -import { reactive } from './reactivity.js?v=9'; -import { h } from './vdom.js?v=9'; +import { reactive } from './reactivity.js'; +import { h } from './vdom.js'; /** * Hash-based router. diff --git a/webui/static/hoover/websocket.js b/webui/static/hoover/websocket.js index a1403f2..a73890a 100644 --- a/webui/static/hoover/websocket.js +++ b/webui/static/hoover/websocket.js @@ -6,8 +6,8 @@ * Page-level subscribe/unsubscribe is replaced by the model layer. */ -import { refreshByTopic } from './model.js?v=9'; -import { tryRefreshToken, redirectLogin } from './api.js?v=12'; +import { refreshByTopic } from './model.js'; +import { tryRefreshToken, redirectLogin } from './api.js'; let _wsConn = null; let _wsReconnectMs = 0; diff --git a/webui/static/index.html b/webui/static/index.html index c69eb0e..80e7904 100644 --- a/webui/static/index.html +++ b/webui/static/index.html @@ -4,7 +4,7 @@