phase: 170_edit_bubble_sizing
Build and Push Containers / build-and-push-app (push) Successful in 1m42s
Build and Push Containers / build-and-push-db (push) Successful in 13s

All verification complete. Final report:

**Phase 170 — final verification pass (all 3 tasks were in `complete/`):**

**Verified implemented:**
- Editor renders inside the bubble: `bubble.replaceChildren(form)`, `display:none` rule deleted, `form.style.minWidth` seeded with pre-edit width, `restoreBubble()` (markdown + image re-prepend) before `closeEditor()` on Cancel/Escape, `closeEditor()` reordered before submit re-render
- Label: `editSaveBtn.textContent = "Send"` (app.js L1241)

**Gates (all 100% green):**
- `uv run pytest` → 4066 passed
- `uv run pytest --cov=app --cov-report=term-missing` → 99% on `app/` (gate >90%)
- `uv run ruff check . && uv run pyright` → clean (0 errors, 0 warnings)
- `uv run pytest tests/e2e/test_edit_bubble_sizing.py -v --no-cov` (in isolation) → 6/6 passed
- Regression set in isolation: `test_edit_and_resend.py` 7✓, `test_retry_answer.py` 4✓, `test_failed_turn_retry.py` 4✓, `test_chat_persistence.py` 4✓

**Completion criteria:** in-bubble editor + width pin ✅ · "Send" label + cancel restore + trim re-ask + "Question too long" driver case ✅ · suite/coverage/lint ✅ · E2E + regression isolation ✅ · phase dir moved to `complete/` ✅ (atomic commit is the harness's to make — changes left in the working tree per protocol)

**Documentation:** README.md reviewed in full — edit section already aligned to phase 170 (in-bubble editor + "Send"), pages table matches actual routes, all 29 `.env.example` vars documented, setup commands accurate; AGENTS.md reviewed — all referenced files/commands exist. Both verified consistent — no changes needed.

**Notable decisions:** none — verification pass only, no code changes required.

**Next pending phase:** `171_admin_settings_layout`
This commit is contained in:
2026-10-10 03:25:40 -04:00
parent 1200437bf8
commit c4f098d0c0
27 changed files with 1607 additions and 120 deletions
+27 -16
View File
@@ -1401,27 +1401,34 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
}
/* Phase 159 (task 01, TODO L3): the inline EDIT form in the newest
* user bubble — the .tune-form card family (labeled textarea +
* Save/Cancel, both ≥44px; the textarea's maxlength = the 4000 cap,
* single-sourced from MAX_QUESTION_CHARS in app.js). While open the
* rendered .bubble is hidden (.is-editing) so the question appears
* ONCE; Cancel / Escape restore it and return focus to the toggle.
* Save rejection (role=alert) keeps the form open (the note is never
* lost — the openTuneForm failure way). */
.msg.user .bubble.is-editing { display: none; }
* user bubble. Phase 170 (A1, owner-locked 2026-10-10): the form
* renders INSIDE the .bubble itself — it inherits the bubble's own
* --brand fill, border, and padding, so it keeps the bubble's
* dimensions by construction (no shrunken sibling box, no
* display:none swap — .is-editing is the state class, the bubble
* stays VISIBLE). Label + action text are the bubble's --bg ink on
* the --brand fill: 5.2:1 (the .msg.user .bubble text pairing,
* documented in :root — B5: the state is text + color, never color
* alone). The textarea keeps its own --surface fill + --ink text
* (a readable edit field on the --brand bubble, 15.3:1) at
* width:100% — the "not tiny" fix. Both buttons keep the ≥44px
* floor; the save pill edges at --bg (5.2:1 on the --brand bubble,
* ≥3:1 UI boundary — a --brand pill on the --brand bubble would be
* invisible); the textarea's maxlength = the 4000 cap,
* single-sourced from MAX_QUESTION_CHARS in app.js. Cancel / Escape
* restore the rendered markdown + the attachment and return focus
* to the toggle (the openTuneForm cancel rule). Save rejection
* (role=alert) keeps the form open (the note is never lost — the
* openTuneForm failure way). */
.edit-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
background: var(--surface);
border: 1px solid var(--brand-soft);
border-radius: var(--radius-sm);
padding: 0.75rem 0.85rem;
}
.edit-form label {
font-size: 0.85rem;
font-weight: 600;
color: var(--ink-soft);
color: var(--bg); /* --bg ink on the --brand fill: 5.2:1 (the user-bubble pairing) */
}
.edit-form textarea {
font: inherit;
@@ -1431,6 +1438,7 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 0.5rem 0.6rem;
width: 100%;
resize: vertical;
min-height: 2.6rem;
}
@@ -1441,7 +1449,7 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
justify-content: center;
min-height: 44px;
padding: 0.4rem 1.1rem;
border: 0;
border: 1px solid var(--bg);
border-radius: var(--radius-sm);
background: var(--brand);
color: var(--bg);
@@ -1460,13 +1468,16 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
border-radius: var(--radius-sm);
border: 1px solid var(--line);
background: transparent;
color: var(--ink-soft);
color: var(--bg); /* was --ink-soft: 1.6:1 on --brand (fails AA); --bg ink: 5.2:1 */
font: inherit;
font-weight: 600;
cursor: pointer;
}
.edit-cancel:hover { background: var(--brand-soft); color: var(--brand-ink); }
/* Inline save rejection (role=alert): err pair ≈9.1:1 — form is kept. */
/* Inline save rejection (role=alert): the --err-* pair is
* self-contained — text vs its own --err-bg is 9.3:1 (≥4.5:1), so
* it holds on the --brand bubble without a scoped variant; the box
* edge on --brand holds 4.9:1 (≥3:1 UI boundary). Form is kept. */
.edit-error {
margin: 0 0 0 0.25rem;
background: var(--err-bg);