refactor: modernize frontend with hoover framework components and docs

- Add quick modal, table, service status, and confirmation dialog components
- Refactor all pages (certs, dhcp, proxy, etc.) to use new component patterns
- Introduce refactor load utility and render guard for consistent UX
- Add hoover documentation and update AGENTS.md, architecture, overview
This commit is contained in:
2026-06-21 04:29:27 +00:00
parent b8f20e99d9
commit 633505e7dc
29 changed files with 2558 additions and 1414 deletions
+36 -12
View File
@@ -5,12 +5,12 @@
* batched re-render loop integration with reactivity.js.
*/
import { requestUpdate, setCommitFn } from './reactivity.js';
import { requestUpdate, setCommitFn } from './reactivity.js?v=6';
import {
_vnodeDom, createDom, getDom, patchNode,
_vnodeDom, createDom, getDom, patchNode, sweepDom,
setMountFn, setUnmountFn,
} from './vdom.js';
import { mountComponent, unmountComponent } from './component.js';
} from './vdom.js?v=6';
import { mountComponent, unmountComponent } from './component.js?v=6';
/** Container → previous root vnodes */
export const _renderSlots = new Map();
@@ -21,6 +21,9 @@ export const _renderFns = new Map();
/** Component key → last normalized #comp output (for _vnodeDom preservation) */
export const _compExpandedCache = new Map();
/** Component key → renderer function (survives normalization that expands #comp) */
const _compRegistry = new Map();
/**
* Set up lifecycle callback hooks from vdom.js.
* Called once during render initialization.
@@ -87,7 +90,7 @@ function commit(container) {
* and manage component lifecycle based on key changes.
*/
function normalizeVNodesWithLifecycle(result, prevVnodes) {
const oldEntries = prevVnodes ? collectCompEntries(prevVnodes, []) : [];
const oldEntries = [..._compRegistry.entries()].map(([key, renderer]) => ({ key, renderer }));
const oldKeyMap = new Map(oldEntries.map(e => [e.key, e]));
const newEntries = [];
@@ -104,6 +107,16 @@ function normalizeVNodesWithLifecycle(result, prevVnodes) {
}
}
// Sync registry with current render (prevVnodes are normalized and lack #comp tags,
// so collectCompEntries always returns [] after the first render)
const newKeySet = new Set(newEntries.map(e => e.key));
for (const [key] of _compRegistry) {
if (!newKeySet.has(key)) _compRegistry.delete(key);
}
for (const entry of newEntries) {
_compRegistry.set(entry.key, entry.renderer);
}
return normalized;
}
@@ -199,6 +212,7 @@ function diffContainer(container, prev, vnodes) {
if (d?.parentNode) {
if (d.nodeType === Node.ELEMENT_NODE) sweepDom(d);
d.parentNode.removeChild(d);
lastDom = i > 0 ? getDom(prev[i - 1]) : null;
}
continue;
}
@@ -214,15 +228,25 @@ function diffContainer(container, prev, vnodes) {
if (oldDom && oldV.tag === newV.tag) {
patchNode(oldDom.nodeType === 1 ? oldDom.parentNode : container, oldV, newV, null);
lastDom = getDom(newV);
} else {
if (oldDom?.nodeType === Node.ELEMENT_NODE) sweepDom(oldDom);
} else if (oldDom && !oldDom.parentNode) {
// oldDom exists in _vnodeDom but detached from the tree
if (oldDom.nodeType === Node.ELEMENT_NODE) sweepDom(oldDom);
const nd = createDom(newV);
_vnodeDom.set(newV, nd);
if (oldDom?.parentNode) {
oldDom.parentNode.replaceChild(nd, oldDom);
} else if (nd.parentNode !== container) {
container.insertBefore(nd, lastDom ? lastDom.nextSibling : null);
}
container.insertBefore(nd, lastDom ? lastDom.nextSibling : null);
lastDom = nd;
} else if (oldDom && oldV.tag !== newV.tag) {
// tag mismatch — replace old DOM with new
if (oldDom.nodeType === Node.ELEMENT_NODE) sweepDom(oldDom);
const nd = createDom(newV);
_vnodeDom.set(newV, nd);
oldDom.parentNode.replaceChild(nd, oldDom);
lastDom = nd;
} else {
// oldDom is null — create and insert new DOM
const nd = createDom(newV);
_vnodeDom.set(newV, nd);
container.insertBefore(nd, lastDom ? lastDom.nextSibling : null);
lastDom = nd;
}
}