feat(docs): save the whole chat session as a doc
Build and Push Containers / build-and-push-app (push) Successful in 1m46s
Build and Push Containers / build-and-push-db (push) Successful in 12s

Phase 75 (TODO.md L4): "Save as doc" now drafts a document from the
ENTIRE chat session — every question and answer up to the click, in
order — instead of only the clicked bubble's answer; the existing
doc-edit screen's free-form body editing is how the user edits out
anything they don't want to keep from previous replies (no new UI
surface).

Task 01 (frontend):
- app.js buildSessionTranscript(): walks the bor.chat.v1 conversation
  record in order — a numbered section per user turn ("## N.
  <question, raw>" + blank line + the raw answer text; more answers
  join under the same heading), sections blank-line separated, all
  trailing whitespace collapsed to one final newline. Only the raw
  persisted text travels (m.who + m.text — no thinking blocks, no
  source chips, no tune metadata); a brain record before the first
  user record is skipped; a heading-only section marks a user turn
  whose answer never landed (A6, owner-confirmed 2026-09-08).
- saveAsDoc(btn): the draft body is buildSessionTranscript(); the
  dead single-bubble markdown parameter is dropped (the button's
  appendSaveAsDocButton signature is unchanged — one button per
  bubble). Title/path/double-click guard/hand-off are unchanged
  (defaultDocTitle: the last question, whitespace-collapsed,
  <=120 chars; docs/<slug>.md).
- Unit: the app.js source pins move to the transcript shape (whole
  session, no thinking, no dead parameter).

Task 02 (E2E):
- tests/e2e/test_save_doc_session.py (bare-repo fixture, the
  phase-59 convention — git as source of truth): three DISTINCT
  on-topic turns in one session (turn 1 carries the phase-17
  "think out loud" trigger so its record has a thinking block the
  transcript must exclude) -> save on the LAST bubble -> the
  prefilled body is ## 1./## 2./## 3. in order, byte-exact against
  the deterministic mock, thinking-free -> edit the whole
  section-2 block out of the body -> push -> git show
  bor-docs:<path> equals the EDITED body byte-for-byte (section 2's
  question and answer provably absent; sections 1 and 3 byte-exact;
  the UI's sha prefix is git rev-parse bor-docs). Second test:
  the button on the FIRST bubble still drafts the whole session
  (A6 — the transcript is the session at click time, title stays
  the last question); canceling leaves the branch tip untouched.
- tests/e2e/test_response_to_docs.py: the phase-59 single-turn body
  expectation moves to the transcript shape ("## 1. <question>" +
  the answer's markdown) — the rest of the suite unchanged.

Also lands the phase-74 file moves (00_phase.md /
03_mock_marker_e2e.md -> complete/) and the phase reports — the
house convention of committing .agents/ with the phase.
This commit is contained in:
2026-09-05 16:59:31 -04:00
parent 055c0b5d85
commit 0e4651c779
16 changed files with 974 additions and 34 deletions
@@ -0,0 +1,37 @@
# Task 01 — `saveAsDoc` posts a full-session transcript; single-turn E2E expectation updated
**Phase:** `75_save_doc_full_session` · **Source:** `TODO.md:4` — "Then, update the \"save as doc\" process to include the output from the entire chat session rather than the last response. The user can edit out anything they don't want to keep from previous replies."
**Story:** n/a (TODO-derived)
## Objective
The "Save as doc" draft's body becomes the whole conversation — every user question and brain answer up to the click, in order — instead of only the clicked bubble's answer; the user then edits out unwanted turns in the existing doc-edit body field before pushing.
## Work
1. `frontend/assets/app.js` — add `buildSessionTranscript()` next to `defaultDocTitle()`/`saveAsDoc()` (~L562–L615):
- Walks the `conversation` record in order. Each `who === "user"` entry opens a section; each following brain entry (up to the next user entry) is appended under it:
```markdown
## 1. <user question, raw text>
<brain answer, raw text>
## 2. <user question, raw text>
<brain answer, raw text>
```
- Numbering: 1-based per USER turn (a section per question; normally one answer per section).
- Trailing whitespace collapsed to a single final newline.
- - ASSUMPTION A6 (owner-confirmed 2026-09-08): the transcript format is the numbered `## N. <question>` + raw answer markdown shown above; ALL turns at click time are included (even when the button is on an earlier bubble); NO thinking blocks, NO source chips, NO tune metadata travel into the document; the default TITLE is unchanged (`defaultDocTitle()` — last user question, ≤120 chars).
- - ASSUMPTION A7 (owner-confirmed 2026-09-08): "edit out anything they don't want to keep" = free-form editing in the EXISTING doc-edit body field (`frontend/doc-edit.html` / `frontend/assets/doc-edit.js` already expose the body as an editable textarea) — no new per-turn selection UI in this phase.
- Stopped/partial brain turns appear as-is (their `text` is what the user saw); the user edits them out if unwanted (A7).
2. `frontend/assets/app.js` — `saveAsDoc`: the draft's `body` becomes `buildSessionTranscript()` instead of the bubble's `markdown` (the POST to `/api/doc-drafts` at ~L623 otherwise unchanged: same `title` from `defaultDocTitle()`, same `docs/<slug>.md` path, same 201→`/doc-edit.html?draft=<token>` hand-off, same double-click guard). The `markdown` parameter of `saveAsDoc` is then unused — drop the parameter and update its single call site (`appendSaveAsDocButton`'s click binding ~L607); keep `appendSaveAsDocButton(wrap, markdown)`'s signature (the button is still appended per bubble).
3. `tests/e2e/test_response_to_docs.py` — `test_save_edit_push`'s body expectation: today "body == the rendered answer's markdown source" → the SINGLE-turn transcript `## 1. <the question>\n\n<the mock answer>` (byte-stable: the mock's default composed answer embeds the question's first 80 chars). Everything else in that suite (push → git verification, guest/unconfigured pins) is unchanged.
4. No backend change: `app/api/doc_drafts.py` accepts any body string; `frontend/doc-edit.html` renders it into the editable body as before.
## Testing & Quality
- No `app/` code — coverage floor unaffected; the wire-level proof is the E2E (this task updates the existing phase-59 suite; task 02 adds the multi-turn suite).
- Manual smoke: 2-turn conversation → Save as doc → the edit screen's body shows both Q/A sections before any editing.
## Completion Criteria
- [ ] `buildSessionTranscript()` produces the exact shape above for multi-turn records (section per user turn, raw texts, single trailing newline).
- [ ] `saveAsDoc` posts the transcript; the parameter cleanup is done (no dead `markdown` argument); the double-click guard and hand-off are unchanged.
- [ ] `tests/e2e/test_response_to_docs.py` (updated) green in isolation; `uv run pytest` green; ruff + pyright clean.