# Web UX refinement — 2026-10-04 Scope: web only. Native iOS remains unchanged until later parity work. ## Implemented - Conversation history has per-row, accessible, viewport-clamped native popover actions for pin/unpin, rename and archive/unarchive without opening the conversation. Revision/account/selection guards preserve active runs and navigation drafts during delayed responses. Deletion remains the opened conversation's top-right menu. - Library explains reference files versus Saved drafts. Files open on entry, with prominent upload and reuse actions. Choosing files closes Library and exposes composer progress, retry/removal and errors. A saved file is revalidated before queuing; nothing sends until explicit message submission. Long filenames appear once with compact, named actions. Unsupported capabilities are explained rather than simulated. - Scheduled task and memory forms use reusable styles outside the original Library mount. Labels, controls, hints, actions and checkbox alignment remain stacked and readable. Activity closes mobile navigation before opening and keeps a reachable close header during scroll. History scrolls independently so account/Library/Activity remain reachable. - Memory & data explains account context versus chat history/files, gives examples, and exposes revision-aware save/clear. Compare-latest preserves local edits and requires explicitly accepting a reviewed current revision before separately saving; no automatic conflict overwrite. - Security shows the actual connected host, HTTPS transport status, current account and session scope, with a real sign-out action. It explains administrator-managed resets and does not fabricate session/device lists, self-service reset, or two-factor controls. - Successful model changes requalify file/image support; pending/failed refreshes block images, latest-response generation wins, and Retry checks support again. ## Verification Full real-asset screenshot review used offline mocked same-origin API fixtures, not NAS/account data. 71 baseline captures from frozen committed assets; final 85 screenshots at desktop 1440×1000, mobile 390×844 and 320×740, including internal dialog/panel scrolling. Covers conversation/sidebar, Files/Saved drafts and editor, tasks list/create/edit, Memory & data, Security, Account and Administration. Artifacts: `/tmp/hope-web-ux-before/`, `/tmp/hope-web-ux-qualified/`; reusable harness `/tmp/hope_web_screenshots.py`. Final capture receipt reports zero browser errors, unknown requests or external requests at all widths. Screenshots were inspected, including task collision baseline and final narrow forms, Library filename/actions, memory editor/navigation and mobile history/footer. Final integrated offline browser selection: **43 passed** in 70.91s: ```bash .venv-tests/bin/python -m pytest --noconftest -p no:cacheprovider \ tests/unit/test_settings_web.py tests/unit/test_chat_features_web.py \ tests/unit/test_sidebar_actions_web.py tests/unit/test_chat_web.py \ tests/unit/test_voice_settings_ui.py -q ``` JavaScript syntax and diff whitespace checks passed. Independent read-only source/contract review initially found asynchronous ownership, hidden feedback, capability refresh and revision recovery issues; these were corrected with regressions and final review approved the scope. Earlier retry test failed because it asserted before the awaited retry finished; corrected to wait for completed state, not to remove behavior coverage. The initial refinement was qualified offline only. Production publication was subsequently authorized; see the deployment receipt below. No speech/device qualification or iOS parity implementation is claimed. Existing untracked design artifacts were left untouched. ## Production release follow-up Published web commit `41ca590`, then corrected two real CI/shutdown defects discovered by production qualification: - Run 242 failed before build: a socket-backed runner checkout cannot be bound through the host Docker daemon. Commit `5a02b8f` streams `git archive HEAD` into the throwaway qualification container instead, excluding untracked runtime/secrets and retaining all tests. - Run 243 revealed SQLite close racing a cancelled `asyncio.to_thread` worker. Commit `ea4e191` serializes connection close with the shared database lock; deterministic regression safely rejects the original implementation and proves the terminal event persists. Packaging assertions follow the safe source-transfer mechanism. - Run 244's exact Python 3.12 offline qualification passed **148 tests** in 64.69s, with one unrelated Starlette/httpx deprecation warning. Theme artifacts verified. The [production run 244](https://git.srmr.xyz/snowspeeder/hope-voice-api/actions/runs/244) completed **successfully** at 2026-10-04 12:36 UTC, rebuilding speech/web, control and Hermes WebUI images and deploying through the protected rollback-preserving script. Production checks at `https://192.168.86.2:9178` verified all seven entrypoint assets (`index.html`, `style.css`, `app.js`, `chat.js`, `features.js`, `settings.js`, `hope-themes.css`) HTTP200 and byte-for-byte equal to release `ea4e191`. Anonymous `/api/auth/me` and POST `/token` both returned401. Fresh mobile Chromium at390px rendered the live login gate without browser errors or horizontal overflow. Qualification used an exception for the installation's existing self-signed certificate only; no production TLS policy changed. Actual Docker `voice` and `hope-control` were running with new images `9475f397d2e2…` and `db756e976829…`, started at12:36:15/12:36:10 UTC respectively. Voice health was `healthy`; control `/health` returned `{"status":"ok"}`. Protected account homes/runtime/certificates and timestamped rollback containers retained. No account credentials, chats, uploads or memory were read or changed for these live checks. No authenticated CRUD/voice/device success is inferred from static assets and health checks. Neither failed qualification run reached deployment; the original production containers stayed healthy.