From 87c3df67ac654bcac7962e5de1b986bfb9d5c600 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 10 Sep 2026 02:44:02 +0200 Subject: [PATCH] fix: restore the ListBucket grant the stale operator image dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Image generation was failing with "Generated image storage or migration unavailable". The real cause was a 403 the catch block was swallowing: the generated-images-app MinIO policy had lost its s3:ListBucket statement, so the storage preflight's HeadBucket was denied while writes still worked. Re-running the storage bootstrap tonight did it. The operator image carries its own baked-in /opt/storage/check.py, and that copy is older than the file beside the compose: the host copy grants ListBucket with a comment saying the preflight needs it, the image copy does not, and running the image overwrote the good policy. The image's copy also still resolves the store as "clinical-milvus", from before that service was renamed. The policy is restored, and the compose now mounts the host check.py (and bootstrap_basic.py) over the image's, so what runs is what can be read and reviewed here. Verified: bootstrap re-runs are idempotent again and storage readiness passes after one. Also styles the model choice as a real control — a matching chevron, hover and focus states in the app accent — instead of a bare form element, in both the composer pill and the Create image popup. Adds a test that admin rows stack on a phone and that every flex block added inside one can shrink, so the panel cannot start scrolling sideways. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WjVc5oaAaYFNbZGLeJp6TX --- public/css/assistant.css | 13 ++++++++++++- test/assistant-component-css.test.js | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/public/css/assistant.css b/public/css/assistant.css index f86cbdd0..32417de5 100644 --- a/public/css/assistant.css +++ b/public/css/assistant.css @@ -396,7 +396,18 @@ body.assistant-workspace #assistant-chat-view { min-height: 0; } /* Composer model pill (Open WebUI-style, its own line above the actions) */ .assistant-model-pill { display:flex; align-items:center; } .assistant-model-pill[hidden] { display:none; } -.assistant-model-control { border:1px solid var(--g200); background:white; border-radius:999px; padding:3px 10px; font-size:11px; color:var(--g600); cursor:pointer; max-width:220px; } +/* Model choice reads as a control, not a raw form element: the native chevron is + replaced with one that matches the composer, and the whole pill responds. */ +.assistant-model-control { appearance:none; -webkit-appearance:none; border:1px solid var(--g200); background:white; border-radius:999px; padding:5px 26px 5px 11px; font-family:inherit; font-size:11.5px; font-weight:600; color:var(--g600); cursor:pointer; max-width:240px; text-overflow:ellipsis; box-shadow:var(--shadow); + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 8.2 2.4 4.6l.8-.8L6 6.6l2.8-2.8.8.8z'/%3E%3C/svg%3E"); + background-repeat:no-repeat; background-position:right 9px center; background-size:10px; transition:border-color .12s ease, color .12s ease, background-color .12s ease; } +.assistant-model-control:hover { border-color:var(--blue-light); background-color:var(--blue-light); color:var(--blue); } +.assistant-model-control:focus-visible { outline:none; border-color:var(--blue); box-shadow:0 0 0 3px var(--blue-light); } +.assistant-model-control option { font-weight:500; color:var(--g800); background:white; } +/* The image-model select sits in a popup, where a full-width control reads + better than a pill. */ +.assistant-create-image-label { display:block; font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; margin:10px 0 4px; } +#assistant-image-model-select { max-width:100%; width:100%; border-radius:10px; padding:8px 26px 8px 11px; font-size:12.5px; } .assistant-model-pick { display:block; } /* Send button becomes a red stop while the assistant works */ .btn-send.busy { background:var(--danger,#dc2626); } diff --git a/test/assistant-component-css.test.js b/test/assistant-component-css.test.js index 0b85c577..6f9aa498 100644 --- a/test/assistant-component-css.test.js +++ b/test/assistant-component-css.test.js @@ -178,3 +178,24 @@ test('the advertised New chat shortcut actually works, and only inside the assis assert.match(handler, /classList\.contains\('assistant-workspace'\)/); assert.match(handler, /btn-assistant-clear/); }); + +test('the admin panel stays usable on a phone', () => { + const fs = require('node:fs'); + const path = require('node:path'); + const root = path.join(__dirname, '..'); + const css = fs.readFileSync(path.join(root, 'public/css/styles.css'), 'utf8'); + // Admin rows are a 3-column grid on desktop; on a phone they must stack or the + // label column squeezes every control into an unusable sliver. + const mobile = css.slice(css.indexOf('@media (max-width:640px) {', css.indexOf('.admin-row {'))); + assert.match(mobile, /\.admin-row \{ grid-template-columns:1fr; \}/, 'rows stack'); + assert.match(mobile, /\.admin-control \{ max-width:none; \}/, 'controls use the full width'); + + // Every block added inside a row must be able to shrink, or it forces the + // grid cell wider than the screen and the page scrolls sideways. + const html = fs.readFileSync(path.join(root, 'public/components/admin.html'), 'utf8'); + const rowChildren = html.split('\n').filter(l => l.includes('style="flex:1;display:flex')); + assert.ok(rowChildren.length > 0, 'found the flex blocks inside admin rows'); + for (const line of rowChildren) { + assert.match(line, /min-width:0/, 'a flex child inside an admin row must be shrinkable: ' + line.trim().slice(0, 60)); + } +});