phase: 159_edit_and_resend_message
Build and Push Containers / build-and-push-app (push) Successful in 1m39s
Build and Push Containers / build-and-push-db (push) Successful in 15s

All completion criteria verified. Final report:

**Phase 159 — Edit the last question and re-ask it: VERIFIED COMPLETE** (all 4 tasks already in `complete/`; this pass verified end-to-end)

**Verified (implementation landed + tests green):**
- `app.js`: `EDIT_ICON`, `lastUserWrap`, `appendEditButton`/`markLastEditable` (single-target discipline, in-flight guard), `openEditor` (labelled textarea, `maxlength=4000`, Escape/Ctrl+⌘+Enter, empty guard), save path: replace → splice tail → DOM removal → `lastBrainWrap=null` → `saveConversation`+`persistConversation` → `runTurn(text,{reask:true})` — matches README exactly
- `styles.css`: `.edit-btn`/`.edit-form`/`.edit-error`/`.is-editing` + mobile 44px; unit pins 40, E2E 7, integration `test_chats_api.py` extended

**Test/lint results (all green):**
- `uv run pytest` — 3,661 tests, exit 0
- `uv run pytest --cov=app --cov-report=term-missing` — TOTAL **99%** (>90%)
- `uv run ruff check . && uv run pyright` — all checks passed; 0 errors
- `uv run pytest tests/e2e/test_edit_and_resend.py -v --no-cov` (DB up) — **7/7 passed**
- Regressions in isolation: `test_retry_answer.py` 4/4, `test_failed_turn_retry.py` 4/4, `test_chat_persistence.py` 4/4

**Completion criteria:** all 7 ✓ — Edit on last bubble + prefill + Escape/Cancel restore (E2E #3); replace+truncate+re-ask with no duplicated question (E2E #1); refresh survival (E2E #4); failed/“question too long” turn editable + trim re-asks (E2E #5); earlier bubbles un-editable + byte-identical Retry/Stop/persistence (E2E #2 + regressions + unit pins); suite/coverage/lint ✓; E2E + regressions isolated ✓. Commit + phase-dir move: harness's job (no git ops by me, per protocol).

**Documentation:** README.md — full review; phase 159 section cross-checked against `app.js` source (cap, keys, truncate, image survival) — consistent, no changes needed; all 30 `.env.example` vars documented. AGENTS.md — full review; all rules/quick-ref commands valid — verified consistent, no changes needed.

**Notable:** none — no defects found in prior work; no live-infrastructure changes required.

**Next pending phase:** `160_tool_budget_telemetry`
This commit is contained in:
2026-10-05 19:07:33 -04:00
parent 4d7d0f6297
commit cb8767512a
36 changed files with 4009 additions and 22 deletions
+110
View File
@@ -1027,6 +1027,37 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
.retry-btn svg { width: 14px; height: 14px; display: block; }
.retry-btn:hover { background: var(--brand-soft); color: var(--ink); }
/* Phase 159 (task 01, TODO L3): the "Edit" button — the
* trim-and-re-ask action in the .msg-meta.msg-actions row (phase 130)
* of the NEWEST user bubble (markLastEditable — the phase-49
* single-target discipline, one `who` over). The exact visual family
* of .retry-btn (same pill size/spacing, the global :focus-visible
* ring, >=44px via min-height) so the meta actions read as a set —
* but neutral: ink-soft -> ink on hover (Tune keeps the brand pair).
* The pencil glyph rides currentColor. No per-button auto margin
* (phase 130 — the row's justify right-aligns the cluster). Contrast:
* ink-soft on --bg ~8.6:1, ink ~15:1, hover ink on --brand-soft
* ~15.7:1 — all AA. */
.edit-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
min-height: 44px;
padding: 0.35rem 0.8rem;
border-radius: 999px;
border: 1px solid var(--line);
background: transparent;
color: var(--ink-soft);
font: inherit;
font-weight: 600;
font-size: 0.82rem;
white-space: nowrap;
cursor: pointer;
}
.edit-btn svg { width: 14px; height: 14px; display: block; }
.edit-btn:hover { background: var(--brand-soft); color: var(--ink); }
/* Phase 59 (owner-locked 2026-08-31, TODO.md L3): the "Save as doc"
button — the bottom-right action of every completed brain bubble's
meta row (the JS gates: admin + a configured docs repo; this rule
@@ -1193,6 +1224,84 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
font-weight: 600;
}
/* 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; }
.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);
}
.edit-form textarea {
font: inherit;
font-size: 0.9rem;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 0.5rem 0.6rem;
resize: vertical;
min-height: 2.6rem;
}
.edit-form-actions { display: flex; gap: 0.5rem; }
.edit-save {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 0.4rem 1.1rem;
border: 0;
border-radius: var(--radius-sm);
background: var(--brand);
color: var(--bg);
font: inherit;
font-weight: 700;
cursor: pointer;
}
.edit-save:hover:not(:disabled) { background: var(--brand-hover); }
.edit-save:disabled { opacity: 0.6; cursor: wait; }
.edit-cancel {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 0.4rem 1rem;
border-radius: var(--radius-sm);
border: 1px solid var(--line);
background: transparent;
color: var(--ink-soft);
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. */
.edit-error {
margin: 0 0 0 0.25rem;
background: var(--err-bg);
color: var(--err-ink);
border: 1px solid var(--err-line);
border-radius: var(--radius-sm);
padding: 0.45rem 0.8rem;
font-size: 0.85rem;
font-weight: 600;
}
/* Header panel above the messages: notes newest-first, per-note delete,
designed empty state. */
.steering-panel {
@@ -5707,6 +5816,7 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
.app-main > .steering-panel { width: calc(100% - 1.8rem); }
.tune-btn { min-height: 44px; }
.retry-btn { min-height: 44px; }
.edit-btn { min-height: 44px; }
/* Phase 27: the tuning page squeezes like the other cards — the row
padding tightens; the icon+label actions keep their 44px floor and
the note text ellipsizes (min-width: 0). */