phase: 170_edit_bubble_sizing
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:
+27
-16
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user