/* * init.js * Application bootstrap: full render, partial detail re-render, config and * tree loading, batch-status polling, and the initial Promise.all boot call. * * render() is the single source of truth for full repaints — it replaces * #app innerHTML, re-attaches editables, reinitialises Sortable instances, * and (on desktop) schedules the boundary canvas setup. * * renderDetail() does a cheaper in-place update of the right panel only, * used during plugin runs and field edits to avoid re-rendering the sidebar. * * Depends on: S, _plugins, _batchState, _batchPollTimer (state.js); * req, toast (api.js / helpers.js); isDesktop (helpers.js); * vApp, vDetailBody, mainTitle, mainHeaderBtns, vBatchBtn * (tree-render.js / detail-render.js); * attachEditables, initSortables (editing.js); * setupDetailCanvas (canvas-boundary.js) * Provides: render(), renderDetail(), loadConfig(), startBatchPolling(), * loadTree() */ // ── Full re-render ──────────────────────────────────────────────────────────── function render() { if (document.activeElement?.contentEditable === 'true') return; const sy = window.scrollY; document.getElementById('app').innerHTML = vApp(); window.scrollTo(0, sy); attachEditables(); initSortables(); if (isDesktop()) requestAnimationFrame(setupDetailCanvas); } // ── Right-panel partial re-render ───────────────────────────────────────────── // Used during plugin runs and field edits to avoid re-rendering the sidebar. function renderDetail() { const body = document.getElementById('main-body'); if (body) body.innerHTML = vDetailBody(); const t = document.getElementById('main-title'); if (t) t.innerHTML = mainTitle(); // innerHTML: mainTitle() returns an HTML span const hb = document.getElementById('main-hdr-btns'); if (hb) hb.innerHTML = mainHeaderBtns(); const bb = document.getElementById('main-hdr-batch'); if (bb) bb.innerHTML = vBatchBtn(); attachEditables(); // pick up the new editable span in the header requestAnimationFrame(setupDetailCanvas); } // ── Data loading ────────────────────────────────────────────────────────────── async function loadConfig() { try { const cfg = await req('GET','/api/config'); window._grabPx = cfg.boundary_grab_px ?? 14; window._confidenceThreshold = cfg.confidence_threshold ?? 0.8; _plugins = cfg.plugins || []; } catch { window._grabPx = 14; window._confidenceThreshold = 0.8; } } function startBatchPolling() { if (_batchPollTimer) clearInterval(_batchPollTimer); _batchPollTimer = setInterval(async () => { try { const st = await req('GET', '/api/batch/status'); _batchState = st; const bb = document.getElementById('main-hdr-batch'); if (bb) bb.innerHTML = vBatchBtn(); if (!st.running) { clearInterval(_batchPollTimer); _batchPollTimer = null; toast(`Batch: ${st.done} done, ${st.errors} errors`); await loadTree(); } } catch { /* ignore poll errors */ } }, 2000); } async function loadTree() { S.tree = await req('GET','/api/tree'); render(); } // ── Init ────────────────────────────────────────────────────────────────────── Promise.all([loadConfig(), loadTree()]);