feat(web): move the chat action cluster to the pinned bottom and align the button sets
- task 01: relocate the .chat-actions row (New chat + Share, comments byte-identical with a Phase 65 note) from the top of the column to the bottom of .chat-shell, directly above the composer - task 02 (owner-locked A1): wrap the row + #composer in ONE sticky .chat-bottom unit (position: sticky; bottom: env(safe-area-inset-bottom, 0), no z-index) — the pills stay at the bottom of the screen at every scroll position and settle into flow above the footer - task 03 (owner-locked A2): right-align the bottom row to the column's right edge (justify-content: flex-end), mirroring the right-aligned Save-as-doc corner; the five action pills share one 44px / 999px-pill geometry - task 04: dedicated Playwright suite tests/e2e/test_bottom_chat_actions.py (resting geometry, the A1 pin across the sticky range, A2 alignment + DOM order + mobile stack + 360px overflow bound + 44px touch targets, New chat / Share click-through) — green in isolation - task 05: regression matrix green in isolation (pinned_composer 4, save_share_ux 5, chat_persistence 4, share_chat 4, chat_history 5, smoke 3); full gate green — unit + integration pass, app/ coverage 99% (>90%), ruff + pyright clean
This commit is contained in:
@@ -423,17 +423,31 @@ html::after {
|
||||
(a normal .chat-shell column child). As a flex ITEM of the column it
|
||||
spans the column width, but as a flex ROW with align-items: center
|
||||
(not the column default stretch) each pill keeps its INTRINSIC
|
||||
content width — two pills side by side, left-aligned in the column,
|
||||
on desktop. The ≤640px block flips this to a vertical stack
|
||||
(flex-direction: column + align-items: stretch — full-width pills,
|
||||
New chat above Share; the existing ≤640px pill rules apply to the
|
||||
stacked pills unchanged). The reading-column contract is untouched
|
||||
(--chat-column: 46rem base, 92rem at >=1500px — PLAN §7 lineage).
|
||||
*/
|
||||
content width — two pills side by side on desktop. The ≤640px block
|
||||
flips this to a vertical stack (flex-direction: column +
|
||||
align-items: stretch — full-width pills, New chat above Share; the
|
||||
existing ≤640px pill rules apply to the stacked pills unchanged).
|
||||
The reading-column contract is untouched (--chat-column: 46rem base,
|
||||
92rem at >=1500px — PLAN §7 lineage).
|
||||
|
||||
Phase 65 (task 03, 2026-09-01, `TODO.md` L3, owner-locked A2): the
|
||||
row is the TOP member of the pinned .chat-bottom cluster (task
|
||||
01/02) and hugs the column's RIGHT edge on desktop
|
||||
(justify-content: flex-end) — mirroring the right-aligned
|
||||
"Save as doc" action of every brain bubble above it, so the
|
||||
bottom-right of the chat reads as ONE aligned action column. The
|
||||
pills keep their intrinsic widths (align-items: center already
|
||||
beats the column default stretch; justify-content only shifts the
|
||||
group). The vertical row↔composer spacing is the .chat-bottom
|
||||
wrapper's 0.5rem gap (task 02). The ≤640px stack rule below is
|
||||
UNCHANGED — with stretched full-width pills the horizontal
|
||||
alignment is moot (the stacked pills already edge-to-edge the
|
||||
column). */
|
||||
.chat-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
@@ -1222,7 +1236,37 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
so messages scrolling behind the pinned box never show through it,
|
||||
and no z-index is added: DOM order already paints the composer over
|
||||
`.messages`, it never overlaps the sticky header (z 20) and stays
|
||||
under the z-1000 document modal. */
|
||||
under the z-1000 document modal.
|
||||
Phase 65 (2026-09-01, `TODO.md` L3, owner-locked A1): the pin now
|
||||
belongs to the .chat-bottom wrapper (rule below) — the row + this
|
||||
form are ONE sticky unit; this composer's own sticky declaration is
|
||||
kept, redundant inside the wrapper but pinned as a computed style by
|
||||
tests/e2e/test_pinned_composer.py. */
|
||||
/* Phase 65 (owner-locked A1, 2026-09-01, `TODO.md` L3): the pinned
|
||||
bottom cluster — the .chat-actions row + the #composer form as ONE
|
||||
sticky unit (A1): the pills are literally always at the bottom of
|
||||
the screen, even while the reader is scrolled up through a long
|
||||
conversation (the rejected in-flow alternative would scroll away).
|
||||
The sticky range is the .chat-shell containing block (the wrapper's
|
||||
parent) — the same range the composer used to have alone — and the
|
||||
.messages flex-grow still rests the WHOLE unit at the screen bottom
|
||||
on short chats (the wrapper is the last shell child, so the grow
|
||||
pushes it down). The shell's 1rem gap applies only ABOVE the unit,
|
||||
from .messages — the 0.5rem gap here is the row↔composer spacing
|
||||
INSIDE it. No z-index: the phase-12 sticky header (z 20) and the
|
||||
phase-46 dropdown always stay on top, and at the document bottom the
|
||||
unit settles back into flow above the footer (the composer's opaque
|
||||
surface keeps messages hidden where the box overlaps them). The
|
||||
composer's own sticky rule below stays — redundant here (its
|
||||
containing block is the wrapper, so it cannot shift within the
|
||||
unit), pinned by tests/e2e/test_pinned_composer.py. */
|
||||
.chat-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
position: sticky;
|
||||
bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
.composer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user