/* Brain of Reese — Git sources admin page (phase 35, task 04; * archive uploads, phase 49 task 03). * * The page module for /git-sources.html: the admin-only manager for the * stored source list (git-sources table, phase 35 tasks 01/02) — git * repo URLs (kind "git") and uploaded archives unpacked under * BOR_UPLOAD_DIR (kind "local", phase 49; the phase-38 * local-directory form is gone — the kind=local API POST is * unchanged, the page just no longer offers it). * This module is the single owner of the page's behaviour: * * • boot — initSharedHeader() (one cached whoami, shared with the * header toggling): anonymous → the sign-in gate shows and the * manager stays hidden (the exact Sources page gate pattern, and * NO /api/git-sources call is made); admin → gate hidden, * #git-sources-content revealed, loadSources(). * • loadSources() — GET /api/git-sources → the table rows * (#git-sources-tbody), the env-fallback note's visibility * (from_env), and the empty state. Each row leads with its kind * badge (Git/Local — text + color, never color alone) plus the * location in a mono : the git URL, or the full local path * for kind "local" rows (phase 38). Values are ALWAYS rendered * with textContent — never innerHTML (URLs may embed user:pass@ * credentials; phase 32's masking discipline). Non-2xx or a * network failure renders the role="alert" load error with a * retry button — never a stuck page. * • add — #git-source-form submit → POST /api/git-sources {url}. * The §7.4 never-stale lifecycle (wireAddForm): the button * disables + relabels "Adding…" while the request is out, * re-enables on success AND failure. 201 clears the input, * reloads the list, and focuses the new row's Remove button * (a11y); a failure (409 duplicate, 422 validation) shows the * server detail inline under the form (role="alert", 422 * shape-aware like the tuning forms) and keeps the input — the * instruction survives. 409/422 details are fixed generic strings * (credential safety — the URL is never echoed). * • upload — #archive-upload-form submit (phase 49, reworked to the * phase-64 202 contract in task 05 — the phase-49 synchronous * 200 paragraph is superseded): POST * /api/git-sources/upload with a FormData file (NO manual * Content-Type — the browser sets the multipart boundary). The * §7.4 never-stale lifecycle keeps its shape — the button * disables + relabels "Uploading…" while the request is out — * but the transfer is now short: the 202 arrives the moment the * archive is safely on disk (A1). 202 → the page-local * "Successfully uploaded — " toast fires (showUploadToast, * the phase-55 share-toast pattern; A2: safe to navigate away), * the file input clears, and the button hands over to the scan — * the processing state ("Processing…", disabled, title cleared) * plus startUploadPolling(): a 2 s poll of * GET /api/git-sources/upload/status renders the live * "Processing… (n/m)" label (A4 — bare during unpack; the * full path rides the button title) and settles it: success → * the sync-style count line (fmtUploadResult, the role=status * result line) + the "Archive uploaded: …" announce + * loadSources (the new/updated row lands with the Local badge; * a re-upload refreshes the row — no duplicate; NO second toast * — A2); failure → the sanitized server error in the role=alert * banner + loadSources, the file selection KEPT for a one-click * re-upload. 409 (an upload is already in progress) raises NO * error banner — it re-attaches to the in-flight run (processing * state + poll, never stale). Other non-2xx (422 format/name, * 413 cap, 5xx) keep the phase-49 error banner + the kept file * selection. The submit finally restores the button ONLY when no * poll is active (§7.4). Boot re-attach (initUploadStatus, admin * branch): a running scan re-enters the processing state + poll * (a reload mid-scan re-attaches — no second upload), a terminal * run re-renders its result line / error banner. * • remove — a row's Remove button asks window.confirm first * (removal prunes the documents only on the NEXT sync — the * confirm says so). Cancel → nothing; ok → the row button * disables, DELETE /api/git-sources/{id}, loadSources(). A * failure shows a per-row role="alert" error and re-enables the * button. Env-fallback rows (id null — the list comes from * BOR_GIT_SOURCES, not the table) carry no Remove: nothing is * stored to remove — they show a "from .env" tag instead. * • announce(msg) — #git-sources-announcer (role=status, * aria-live=polite): the screen-reader confirmation for loads, * adds, and removals. * * Scope boundary (phase locked decisions): adding a git repo or * removing a source does NOT clone, import, or prune — the sync * service (server-side) performs that; the page's hint box says so. * The phase-49 upload is the exception: it unpacks and scans the * single source in place (the phase-64 background task — 202 + * status endpoint), and its counts render as the result line. * * The shared header module loads through this script's own relative * import ("./header.js") — a hoisted import evaluated before this body * runs (single-evaluation design: no direct