phase: 132_image_upload_feedback
Build and Push Containers / build-and-push-app (push) Successful in 2m25s
Build and Push Containers / build-and-push-db (push) Successful in 17s

All verification complete. Final report:

**Phase 132 (image upload feedback) — final verification pass: 100% green**

- **Verified** all 3 tasks' implementations in the working tree: `index.html` status part (hidden-by-default, arc + "Uploading image…", pinned order), `app.js` `enterUploadState`/`exitUploadState` (entered after `attachUpload` guard pre-await, exited on BOTH resolution paths, `setUiState` untouched, idle "Send" restore byte-identical), `styles.css` `.is-uploading` rules (house `spin` keyframes, reduced-motion block, existing `:disabled` Send language), unit pins, E2E suite with route-delayed upload window.
- **Completion criteria** — (1) mid-flight spinner + "Uploading image…", remove + Send disabled with "Uploading…": ✅ E2E-asserted; (2) second Send press = no second upload (request count exactly 1): ✅; (3) success clears + turn proceeds (bubble carries image); failure clears + banner + text/attachment remain: ✅; (4) `uv run pytest tests/e2e/test_image_upload_feedback.py -v --no-cov`: ✅ 3 passed (18.5 s, in isolation); (5) no behavior change in completed phases: ✅ (full suite green, A5 path-never-bytes re-pinned in E2E).
- **Full suite**: `uv run pytest --cov=app --cov-report=term-missing` → **3108 passed**, `app/` coverage **99%** (>90% gate); `uv run ruff check .` → clean; `uv run pyright` → 0 errors.
- **Documentation**: README.md fully reviewed (setup, pages, all `.env.example` vars + every default vs `app/config.py`, tools incl. phase-131 `read(offset,limit)`, image/PDF/sync/deploy sections) and AGENTS.md fully reviewed (rules, quick-reference commands, gitignore/gpg claims) — **verified consistent; no changes needed**. Phase-132 README bullet already in the working tree; CSS `:root` contrast-note corrections (15.3:1 / 8.2:1) re-verified mathematically correct (comments only).
- **Decisions**: none — all task work was already implemented and correct; no fixes required.
- **Next pending phase**: `133_pdf_screenshot_fallback`.
This commit is contained in:
2026-09-28 13:45:31 -04:00
parent 101156bada
commit 4cb85e5baa
22 changed files with 1696 additions and 2 deletions
+52 -2
View File
@@ -7,9 +7,9 @@
:root {
/* Palette — all text/background pairs meet WCAG 2.1 AA (>= 4.5:1) */
--bg: #0f0a0a; /* page: ink on bg 16.7:1 */
--surface: #1a0f0f; /* ink on surface 13.8:1 */
--surface: #1a0f0f; /* ink on surface 15.3:1 */
--ink: #f0e6e6;
--ink-soft: #b8a8a8; /* 5.1:1 on --surface */
--ink-soft: #b8a8a8; /* 8.2:1 on --surface */
--line: #2d1a1a; /* decorative 1px borders */
--grid-line: #4a2626; /* background grid texture — decorative, no
contrast duty (like --line) */
@@ -1745,6 +1745,56 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
}
.attach-preview-remove svg { width: 18px; height: 18px; }
.attach-preview-remove:hover { background: var(--err-bg); color: var(--err-ink); }
/* Phase 132 (task 02, TODO L8): the strip's UPLOADING look — the
visual half of the owner-confirmed double-spam block (task 01's JS
state proves the image is moving while POST /api/chat-images is in
flight; before this the strip looked frozen and the user spammed
Send). The thumbnail DIMS to 0.5 (it is decorative — no contrast
duty — and 0.5 is the house dim value the disabled remove button
below); the filename HIDES with visibility (the flex: 1 slot keeps
its measured width — the status takes the row's right hand WITHOUT
reflowing the pinned 44px remove button); the house loading arc
(the status part's svg — task 01's markup, currentColor = the
status's --ink-soft) SPINS at the sync spinner's pace (the house
`spin` keyframes at 1s linear — .sync-icon.is-spinning's in-flight
pace; the .spinner's 0.8s is the busy-turn arc's). The status text
is --ink-soft on the strip's --surface — the :root-pinned pair,
8.2:1 computed against WCAG 2.1 (AA ≥ 4.5:1), at the strip's
small text size (the name's 0.85rem). The remove button (task 01
disables it mid-flight) dims with its disabled attribute — the
attribute is the state, the 0.5 is its visual half; the Send rides
the same house :disabled language (.send-btn:disabled's
--brand-busy + not-allowed below — which reads as disabled already,
so NO new color or weight was added, B4). prefers-reduced-motion stills the arc (the house block): the
"Uploading image…" TEXT + the dimmed thumbnail carry the state
without motion (B5 — never motion alone). Every value is a theme
variable or a neutral number — the block ships no raw color. */
.attach-preview.is-uploading .attach-preview-img {
opacity: 0.5;
}
.attach-preview.is-uploading .attach-preview-name {
visibility: hidden; /* keep the flex: 1 slot; the status takes its right hand */
}
.attach-preview-status {
display: inline-flex;
align-items: center;
gap: 0.35rem;
color: var(--ink-soft);
font-size: 0.85rem; /* the strip's small text — the name's size */
min-width: 0; /* the row's clamp: at 320 px the text WRAPS (no clip) */
}
.attach-preview-status-icon {
width: 14px;
height: 14px;
flex: 0 0 auto;
animation: spin 1s linear infinite; /* the house spin keyframes (the sync pace) */
}
@media (prefers-reduced-motion: reduce) {
.attach-preview-status-icon { animation: none; }
}
.attach-preview.is-uploading .attach-preview-remove {
opacity: 0.5; /* the disabled attribute's visual half (task 01 sets it) */
}
.composer {
display: flex;
align-items: flex-end;