meta description -> locked (A3) auto-save string (history.html L6). page-sub -> locked (A3) string, the <strong>Save</strong> emphasis retired with the button (L106-109). empty row -> locked (A3) string; colspan=6, hidden, and row id untouched (L163). h1, the anonymous gate section, and history.js are byte-identical — state language verified accurate.
7.9 KiB
Phase 65 — Chat Action Cluster to the Pinned Bottom
Source: TODO.md L3 — "Move the new chat and share button to the tune/retry/save doc cluster area so it's always at the bottom of the screen and easily accessble. Make the button clusters look better, neater, more aligned"
Story: n/a (TODO-derived — owner roadmap confirmation 2026-09-01)
Context: Verified layout inventory (audited 2026-09-01, line numbers as of this writing):
frontend/index.html— the.chat-actionsrow (New chat + Share pills, ONE wrapper) sits at the TOP of the 46rem chat column: the comment block runs L126–148, the row div is L149, positioned between#steering-announcer(L129) and<section class="messages" id="messages">(L193). The pinned composer form follows the messages at L225. In any real conversation the row scrolls off-screen the moment the reader is at the bottom — the exact defect this phase removes.frontend/assets/styles.css—.chat-actions(L433: horizontal row,align-items: center,gap: 0.6rem, intrinsic pill widths);.new-chat-btn(L301) /.share-chat-btn(L335) — solid brand pill family,min-height: 44px,border-radius: 999px;.composer(L1226) —position: sticky; bottom: env(safe-area-inset-bottom, 0), no z-index (the phase-52 pin, owner-locked 2026-08-30);.messages—flex: 1 1 auto+min-height: 200px(the grow that rests the bottom at the screen on short chats); the per-answer meta cluster:.msg-meta(L641),.tune-btn(L694),.retry-btn(L723),.save-as-doc-btn(L756) — ghost pill family,min-height: 44px, 14px icons,margin(-inline-start): autopushes Tune/Retry/Save-as-doc to the row's right edge ("Save as doc" is the bottom-right action of every completed brain bubble — the app's action corner). The ≤640px block (L3049) flips.chat-actionsto a full-width vertical stack.frontend/assets/header.jsL336–339 — binds#new-chat-btnand dispatches thebor:new-chatwindow event;frontend/assets/app.jsL1700 listens and resets the conversation (it owns the in-flight-turn guard);app.jsL246/L2067 binds#share-chat-btntoshareCurrentChat(the empty-chat no-op writesNothing to share yet.to#send-status). All bindings are id-based and position-independent — the move is pure HTML/CSS.frontend/assets/app.jscopyShareLinkWithFallback(L1424) appends the clipboard-fallback field to the composer (not the row) — unaffected by the move.- Existing suites that constrain the design:
tests/e2e/test_pinned_composer.py(the#composercomputedpositionmust staysticky; the composer box flush with the viewport bottom at EVERY scroll position inside the sticky range; settles back into flow above the footer at the document bottom; NO z-index on the composer; the empty chat must not be scrollable,sh <= ch + 1);tests/e2e/test_save_share_ux.py::test_action_row_layout(ONE.chat-actions, DOM order New chat → Share, horizontal row at 1280px with intrinsic pill widths, full-width stack at 390px, no horizontal overflow at 360px — all RELATIVE geometry, nothing pins the row's vertical position);tests/e2e/test_chat_persistence.py(#new-chat-btn≥44px, click resets the conversation,New chat startedin#send-status). .agent/is tracked and committed (AGENTS.md rule 8) — the phase commit stages.agent/+frontend/+tests/.
Objective
The New chat + Share pills live in the bottom cluster of the chat column — the same screen zone as the tune/retry/save-doc meta actions — and are always at the bottom of the screen (pinned with the composer, visible even while scrolled up reading a long conversation). The two button clusters read as neat, aligned sets: the bottom row hugs the column's right edge, mirroring the right-aligned "Save as doc" corner above it.
Dependencies
64_sync_upload_progress(todo, preceding — no functional dependency; ordering by number)
Tasks
01_move_chat_actions_to_bottom.md— relocate the.chat-actionsrow (buttons + comments byte-identical) to the last child of.chat-shell, directly above the composer; in-flow at first.02_pin_bottom_cluster.md— wrap row + composer in a sticky.chat-bottomunit (A1): the cluster is pinned at the viewport bottom at every scroll position and settles into flow above the footer at the document bottom.03_align_button_clusters.md— right-align the bottom row to the column's right edge (A2); verify the five action pills share one geometry (44px targets, 999px radius, global focus-visible).04_e2e_bottom_chat_actions.md— the dedicated Playwright suitetests/e2e/test_bottom_chat_actions.py(run in isolation): pinned at every scroll position, resting at the screen bottom, settled above the footer, geometry + DOM order, mobile stack, both buttons still work.05_regressions_and_commit.md— regression E2E matrix, fullvalidate.sh, one atomic commit.
Testing & Quality
- E2E (mandatory, house rule — one file per phase, run in isolation):
tests/e2e/test_bottom_chat_actions.pyagainst the shared conftest server (DB up, mock LLM, seeded KB — thetest_pinned_composer.pypatterns for overflow conversations and scroll measurement). - Frontend-only phase:
app/is untouched, so the coverage gate stays satisfied at its current level —validate.sh(unit + integration >90% + ruff + pyright) must still pass end to end. - Regression E2E (each in isolation):
test_pinned_composer.py,test_save_share_ux.py,test_chat_persistence.py,test_share_chat.py,test_chat_history.py,test_smoke.py.
Completion Criteria
- The
.chat-actionsrow is the bottom cluster of.chat-shell— above the composer, inside the sticky.chat-bottom— with DOM order New chat → Share preserved and all ids/aria-labels intact. - Over-viewport conversation, scrolled to the top: the row is fully visible, pinned at the viewport bottom above the composer, at every scroll position inside the sticky range; at the document bottom the cluster settles into flow above the footer (no overlap).
- Empty chat at 1280×800: the row rests at the bottom of the screen, the document is not scrollable.
- Desktop: the row's right edge aligns with the column's right edge (A2); ≤640px: full-width vertical stack; no horizontal overflow at 360px.
#new-chat-btnclick resets the conversation (New chat startedin#send-status);#share-chat-btnon an empty chat is a no-op (Nothing to share yet.) — the bindings survived the move.uv run pytestgreen; coverage TOTAL >90%;uv run ruff check . && uv run pyrightclean.uv run pytest tests/e2e/test_bottom_chat_actions.py -v --no-covgreen in isolation (DB up); the six regression E2E suites green in isolation.- One
--no-gpg-signcommit; phase dir moved to.agent/phases/complete/.
Locked decisions
- Owner-locked (2026-09-01, roadmap confirmation, A1): pinned bottom cluster — a
.chat-bottomwrapper (position: sticky; bottom: env(safe-area-inset-bottom, 0), NO z-index) groups.chat-actions+#composeras one sticky unit, so the pills are literally always at the bottom of the screen — the rejected alternative (plain in-flow row) would scroll away while reading up a long conversation. The#composer's own sticky declaration STAYS (redundant inside the wrapper — it cannot shift within its containing block — buttest_pinned_composer.pypins the computed stylesticky). - Owner-locked (2026-09-01, roadmap confirmation, A2): right-aligned bottom row — the New chat + Share row hugs the column's RIGHT edge on desktop (so the bottom-right reads as one aligned action column with the right-aligned "Save as doc" buttons above); the ≤640px stack stays full-width (alignment is moot when stretched).
Commit
git add .agent/ frontend/ tests/ && git commit --no-gpg-sign -m "feat(web): move the chat action cluster to the pinned bottom and align the button sets"