phase: 159_edit_and_resend_message
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:
@@ -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). */
|
||||
|
||||
Reference in New Issue
Block a user