fix(web): history tab copy — every chat saves automatically, there is no Save button
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.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# 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-actions` row (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): auto` pushes 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-actions` to a full-width vertical stack.
|
||||
- `frontend/assets/header.js` L336–339 — binds `#new-chat-btn` and dispatches the `bor:new-chat` window event; `frontend/assets/app.js` L1700 listens and resets the conversation (it owns the in-flight-turn guard); `app.js` L246/L2067 binds `#share-chat-btn` to `shareCurrentChat` (the empty-chat no-op writes `Nothing to share yet.` to `#send-status`). All bindings are id-based and **position-independent** — the move is pure HTML/CSS.
|
||||
- `frontend/assets/app.js` `copyShareLinkWithFallback` (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 `#composer` computed `position` must stay `sticky`; 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 started` in `#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
|
||||
1. `01_move_chat_actions_to_bottom.md` — relocate the `.chat-actions` row (buttons + comments byte-identical) to the last child of `.chat-shell`, directly above the composer; in-flow at first.
|
||||
2. `02_pin_bottom_cluster.md` — wrap row + composer in a sticky `.chat-bottom` unit (A1): the cluster is pinned at the viewport bottom at every scroll position and settles into flow above the footer at the document bottom.
|
||||
3. `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).
|
||||
4. `04_e2e_bottom_chat_actions.md` — the dedicated Playwright suite `tests/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.
|
||||
5. `05_regressions_and_commit.md` — regression E2E matrix, full `validate.sh`, one atomic commit.
|
||||
|
||||
## Testing & Quality
|
||||
- E2E (mandatory, house rule — one file per phase, run in isolation): `tests/e2e/test_bottom_chat_actions.py` against the shared conftest server (DB up, mock LLM, seeded KB — the `test_pinned_composer.py` patterns 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-actions` row 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-btn` click resets the conversation (`New chat started` in `#send-status`); `#share-chat-btn` on an empty chat is a no-op (`Nothing to share yet.`) — the bindings survived the move.
|
||||
- [ ] `uv run pytest` green; coverage TOTAL >90%; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] `uv run pytest tests/e2e/test_bottom_chat_actions.py -v --no-cov` green in isolation (DB up); the six regression E2E suites green in isolation.
|
||||
- [ ] One `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/`.
|
||||
|
||||
## Locked decisions
|
||||
- **Owner-locked (2026-09-01, roadmap confirmation, A1):** *pinned bottom cluster* — a `.chat-bottom` wrapper (`position: sticky; bottom: env(safe-area-inset-bottom, 0)`, NO z-index) groups `.chat-actions` + `#composer` as 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 — but `test_pinned_composer.py` pins the computed style `sticky`).
|
||||
- **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
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
# Task 05 — Regression matrix, full gate, atomic commit
|
||||
|
||||
**Phase:** `65_bottom_chat_actions` · **Source:** `TODO.md:3` — "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)
|
||||
|
||||
## Objective
|
||||
Prove the move + pin + alignment broke nothing in the adjacent contracts (the composer pin, the phase-55 action row, chat persistence, sharing, history) and land ONE atomic commit.
|
||||
|
||||
## Work
|
||||
1. Regression E2E — each in isolation, DB up (run in this order; stop and fix the FIRST failure, do not batch-patch):
|
||||
- `uv run pytest tests/e2e/test_pinned_composer.py -v --no-cov` (the composer pin — the most entangled contract with the new wrapper);
|
||||
- `uv run pytest tests/e2e/test_save_share_ux.py -v --no-cov` (the phase-55 row geometry + auto-save + share flows);
|
||||
- `uv run pytest tests/e2e/test_chat_persistence.py -v --no-cov` (the New chat reset contract, incl. the ≥44px pin);
|
||||
- `uv run pytest tests/e2e/test_share_chat.py -v --no-cov` (the full share lifecycle — the button moved under it);
|
||||
- `uv run pytest tests/e2e/test_chat_history.py -v --no-cov` (history restore + the Share button on the chat page);
|
||||
- `uv run pytest tests/e2e/test_smoke.py -v --no-cov` (front-door sanity).
|
||||
2. The dedicated suite one final time: `uv run pytest tests/e2e/test_bottom_chat_actions.py -v --no-cov` green in isolation.
|
||||
3. Full gate: `uv run pytest` green; coverage TOTAL **>90%**; `uv run ruff check . && uv run pyright` clean (equivalently `.agent/validate.sh` exit 0).
|
||||
4. One atomic commit (`.agent/` is tracked — AGENTS.md rule 8):
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
Commit body: one line per task — the move (task 01), the sticky `.chat-bottom` unit (task 02, A1), the right-edge alignment (task 03, A2), the dedicated suite (task 04), the regression outcome (task 05).
|
||||
|
||||
## Testing & Quality
|
||||
- The six regression suites + the dedicated suite green in isolation (DB up).
|
||||
- `uv run pytest` green; coverage TOTAL **>90%**; lint + types clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] All six regression suites green in isolation; `test_bottom_chat_actions.py` green in isolation.
|
||||
- [ ] `uv run pytest` green; coverage TOTAL >90%; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] Exactly one new commit on `main` staging `.agent/ frontend/ tests/` only, `--no-gpg-sign`, Conventional Commits message as specified.
|
||||
- [ ] Phase dir moved to `.agent/phases/complete/65_bottom_chat_actions/` (pipeline step — `.agent/` stays tracked and committed).
|
||||
@@ -0,0 +1,30 @@
|
||||
# Task 01 — Rewrite the History-tab copy to the auto-save model
|
||||
|
||||
**Phase:** `66_history_auto_save_copy` · **Source:** `TODO.md:4` — "Update the history tab - the text still reads like there's a save button. There isn't anymore, every chat is saved."
|
||||
**Story:** n/a (TODO-derived)
|
||||
|
||||
## Objective
|
||||
The three offending strings in `frontend/history.html` describe how BOR used to work (a Save button the visitor pressed). They become the locked (A3) auto-save copy — accurate, same house voice, no new markup.
|
||||
|
||||
## Work
|
||||
1. `frontend/history.html` — three exact replacements (markup otherwise byte-identical):
|
||||
- L6 — the meta description content becomes exactly (A3):
|
||||
`Saved chats — every conversation is saved automatically, one click back.`
|
||||
- L106–109 — the `.page-sub` becomes exactly (A3) — the `<strong>Save</strong>` emphasis is retired with the button; keep the `<p class="page-sub">` element, the indentation, and the single paragraph:
|
||||
`Every conversation is saved automatically — newest activity first. Click a title to return to that chat.`
|
||||
- L163 — the `#history-empty-row` cell text becomes exactly (A3):
|
||||
`No saved chats yet — start a conversation and it will be saved automatically.`
|
||||
(the `colspan="6"`, the `hidden` attribute, and the row's id are untouched — `history.js` reveals this exact row on a 0-row fetch).
|
||||
2. Leave UNTOUCHED (verified accurate — "saved" as a state, not a button): the `<h1>Saved chats</h1>` (L105), the anonymous gate copy (L123–128), the table caption, every other row/cell in `history.html`, and everything in `frontend/assets/history.js` (its "saved chat" aria-labels are state language).
|
||||
3. No CSS, no JS, no other page — the TODO scopes this to the history tab; any OTHER page found to name a Save button is noted in the commit body as follow-up material, not fixed here.
|
||||
|
||||
## Testing & Quality
|
||||
- `uv run pytest` green (unit + integration — no Python changed); `uv run ruff check . && uv run pyright` clean.
|
||||
- Coverage TOTAL **>90%** (validate.sh gate — unchanged, no `app/` code).
|
||||
- Quick manual sanity (optional, before the pins land in task 02): `grep -n "Save" frontend/history.html` — the only remaining case-sensitive "Save" hits are the retired-comment-free state strings ("Saved chats" h1, "Saved conversations are admin-only", "saved chats" table aria-label/caption).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `grep -c "pressed <strong>Save</strong>\|press <strong>Save</strong>\|every conversation you saved" frontend/history.html` → 0.
|
||||
- [ ] The three locked (A3) strings are present in `frontend/history.html` (meta, page-sub, empty row), each exactly once.
|
||||
- [ ] The h1, the gate section (L119–129), and `history.js` are byte-identical to before this task.
|
||||
- [ ] `uv run pytest` green; lint + types clean; coverage >90%.
|
||||
Reference in New Issue
Block a user