diff --git a/CHANGELOG.md b/CHANGELOG.md index d282ead..ed63bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog — ChannelGate +- Keep sidebar update messages inside the rail, wrapping long details and showing a short commit + revision with the full hash on hover. + - Add System health to the admin UI with real CPU/RAM/load and storage metrics, historical charts and peaks, storage warnings and capacity estimates, and refreshed hardware inventory. diff --git a/FEATURES.md b/FEATURES.md index 97cd244..1639da2 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2491,6 +2491,9 @@ are retired, bullet by bullet; everything else stands. `refused`, or `failed`. Slack/MCP thread markers are transaction-bound and survive intermediate boots; only the matching terminal result is posted, with a deterministic Slack message id, then the marker is removed. → TEST-PLAN: Transactional self-update. +- **Sidebar update text stays inside the rail**: completion, rollback, errors, progress and + sign-in links wrap as normal text, including long unbroken paths. Result revisions show seven + characters with the full hash on hover. → TEST-PLAN: Sidebar update layout (engine-independent). - **npm advisory policy**: candidate production dependencies are audited after `npm ci`. Critical/high findings block and roll back the update; moderate findings are reported and reviewed in the same development cycle. Weekly Dependabot discovery, exception policy, and the diff --git a/TEST-PLAN.md b/TEST-PLAN.md index 3a4482c..29a0687 100644 --- a/TEST-PLAN.md +++ b/TEST-PLAN.md @@ -4948,6 +4948,22 @@ Manual checks for the daemon-level behavior: connected Airtable account. The cases above are ready to transfer using the operator's personal connection; this is not a claim of live engine acceptance or stable-release readiness. +### Sidebar update layout (engine-independent) + +- [x] Chromium acceptance: `CG_BROWSER_MODULE=/absolute/path/to/playwright/index.mjs node --test test/update-ui.test.js`. + Uses the shipped sidebar markup, stylesheet and update renderer in a disposable browser; + no daemon state changes or engine calls. At 1440, 800 and 390 px widths, render updated, + unchanged, rolled-back, refused, failed, image-warning, running preflight and sign-in states. + Fixtures use a 40-character revision and a `/tmp/` error path containing twelve repeated + `long-path-segment` strings. Pass: every rendered text/element rectangle stays within the + sidebar, dots remain 7 px wide, and the sign-in link remains visible. The revision unit case + requires a seven-character label and the complete hash in its tooltip. +- Manual reproduction: open the Admin UI after a completed update, resize to desktop and mobile, + and hover the revision. Pass: no update text crosses into page content; the full revision + remains available on hover. Claude/Codex share this browser-only presentation; neither + engine participates in layout. Automated fixture passed; a real updater run is unnecessary + for this presentation-only change. + ### Transactional self-update - [x] Unit: exclusive reservation, live-owner refusal, dead/abandoned-owner recovery, ownership diff --git a/public/app.js b/public/app.js index 8cb17b9..2648343 100644 --- a/public/app.js +++ b/public/app.js @@ -3741,7 +3741,9 @@ function updateResultHtml(transaction) { const cause = transaction.candidateError ? ` Candidate error: ${transaction.candidateError}` : ""; return `${escapeHtml(transaction.reason + cause)}`; } - const revision = transaction.runningRevision ? ` ${escapeHtml(transaction.runningRevision)}` : ""; + const revision = transaction.runningRevision + ? ` ${escapeHtml(transaction.runningRevision.slice(0, 7))}` + : ""; if (transaction.result === "updated" && transaction.imageWarning) { return `container image needs attention — ${escapeHtml(transaction.imageWarning)}`; } diff --git a/public/styles.css b/public/styles.css index 7e780a5..80864b1 100644 --- a/public/styles.css +++ b/public/styles.css @@ -837,7 +837,8 @@ button.clear-tok.armed { background: rgba(229, 96, 77, .14); border-color: rgba( /* Gateway version chip + one-click update (rail footer) */ #update { margin-top: 4px; display: flex; flex-direction: column; gap: 4px; } -#update .statuschip { padding: 3px 10px; } +#update .statuschip { display: block; overflow-wrap: anywhere; padding: 3px 10px; } +#update .statuschip .dot { display: inline-block; margin-right: 8px; vertical-align: middle; } .update-btn { font-size: 11.5px; padding: 6px 10px; margin: 2px 10px 0; } /* Per-model Codex rates table (Settings → Behavior) */ diff --git a/test/update-ui.test.js b/test/update-ui.test.js index 5bebd78..60ef115 100644 --- a/test/update-ui.test.js +++ b/test/update-ui.test.js @@ -80,3 +80,63 @@ test("session loss after restart asks for login without leaking or inventing upd assert.match(el.innerHTML, /completion is not yet verified/); assert.equal(timers.length, 0); }); + +test("completed update shows a short revision with the full hash available on hover", () => { + const { context } = fixture(); + const revision = "79ee4e6de60c3ea7be98699deea56922a6f36170"; + for (const transaction of [{ result: "updated" }, { result: "updated", changed: false }, { result: "rolled_back" }]) { + const html = context.updateResultHtml({ ...transaction, runningRevision: revision }); + assert.ok(html.includes(`79ee4e6`)); + } +}); + +// Use the real sidebar markup, CSS and update renderer: DOM-only tests cannot detect overflow. +test("sidebar update results, progress and login links stay within the rail", { skip: !process.env.CG_BROWSER_MODULE }, async (t) => { + const { chromium } = await import(process.env.CG_BROWSER_MODULE); + const browser = await chromium.launch({ headless: true, args: ["--no-sandbox"] }); + t.after(() => browser.close()); + const page = await browser.newPage(); + const index = readFileSync(new URL("../public/index.html", import.meta.url), "utf8"); + const sidebar = index.match(/