Skip to content

fix(meetings): guard unsaved-minutes loss and polish editor flow (#7116) - #10445

Open
NicholasRBowers wants to merge 1 commit into
mainfrom
fix/meetings-minutes-edit-followups-7116
Open

fix(meetings): guard unsaved-minutes loss and polish editor flow (#7116)#10445
NicholasRBowers wants to merge 1 commit into
mainfrom
fix/meetings-minutes-edit-followups-7116

Conversation

@NicholasRBowers

Copy link
Copy Markdown
Contributor

Problem / Motivation

The minutes editor could throw away the user's work. Clicking Cancel with unsaved edits discarded the text silently, and the revert dialog never said the deletion is permanent. Five smaller gaps shipped with it: the editor opened without focus, the save button froze on "Save" while a write ran, an empty saved edit showed "output will appear here", the delete-output response carried a reverted field no client reads, and store.delete_meeting spelled the edits-path derivation by hand next to a helper that already does it. All seven were deferred from the minutes-editing feature's advisory reviews and tracked in #7116.

Why it matters

The editor exists so a user can correct minutes during a live meeting. A silent discard loses exactly that correction, with no way back — and the dialog that does exist understated the same loss. The rest is small friction on every edit, plus two pieces of dead weight in the backend.

What changed (motivation → approach → change)

Cancelling a dirty draft now asks first, with the same dialog the revert path already uses. The dirty check compares the draft against the seed the editor opened with — never the live output prop, which the 5-second outputs poll refreshes — so an untouched draft stays clean even while the agent writes more underneath. The revert dialog now says "Permanently delete my edits and show what {name} wrote", in all 14 locale catalogs.

case Before After
Cancel with unsaved changes 🟥 draft silently discarded 🟩 confirm dialog; declining keeps the draft
Cancel with nothing typed 🟦 closes 🟦 closes, no dialog
Agent writes more under an untouched open draft 🟥 would have read as dirty 🟩 still counts as clean

🟩 added · 🟥 removed · 🟦 unchanged

Discarding typed text now takes the same explicit yes as reverting a saved edit; a draft you never touched still closes with one click.

The editor textarea is focused when edit mode opens (the clicked Edit button unmounts, so focus used to drop to the page body). The save button reads "Saving…" while this panel's own save is in flight — the label is driven by a panel-local flag, because the editSaving prop is session-wide and would mislabel an idle sibling panel; that prop still disables the controls as before. A saved-but-empty edit no longer renders the "{name} output will appear here" placeholder: the blank is the user's own version, not agent silence, and the Edited badge already says whose copy is shown.

In the backend, handle_delete_output drops the reverted response field (its only mention anywhere was the client's response type, now updated) and _drop_edit stops returning the flag nothing consumed. store.delete_meeting now derives the edits path through the existing agent_edits_dir() helper, which applies the same contain + refuse-linked barrier at both the edits root and the per-meeting entry — one spelling instead of two.

Tests

Frontend (MeetingsAgentPanel.test.tsx, 43 pass; 6 fail without the component change):

  • dirty Cancel opens the confirm dialog, confirming discards and never calls the server, declining keeps the draft
  • untouched Cancel closes with no dialog, and a draft stays clean when the output prop refreshes underneath
  • the textarea is focused on entering edit mode
  • an empty saved edit suppresses the placeholder; a truly unwritten output still shows it
  • "Saving…" appears only for this panel's own in-flight save, returns to "Save" after a failed save, and an idle panel with the session-wide flag set still reads "Save" (disabled)

Backend (test_meetings_minutes.py, 311 meetings tests pass): the delete-output response is asserted exactly as {"ok", "agent_id"} on both the reverted and nothing-to-revert paths, locking the field removal; the revert-restores-generated-text behavior is unchanged. prove.py: PROVEN (the changed tests fail with the production hunks reverted).

Manual verification

Drove the three states in a real browser (scripted Playwright against a Vite dev server rendering AgentPanel) and captured them below. Full local gates: isort / flake8 / mypy / diff-scoped black / full pytest (reds match pristine main), tsc -b, full vitest (2042 files), i18n gate chain.

Screenshots / video

Revert confirm with the permanent-loss wording:

Revert confirm dialog stating the edits will be permanently deleted

Dirty-cancel confirm (new dialog; the focused editor behind it):

Discard unsaved changes dialog over the minutes editor

Saving… pending label while a save is in flight:

Save button showing Saving… while the write is pending

Related Issues

Closes #7116

Pattern harvest

Rule candidate: review-prompt
Pattern: UI dirty/pending state derived from a live shared prop instead of a captured local seed — a background poll or a sibling's in-flight write then misreports this element's state.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

Cancelling a dirty minutes draft now asks before discarding, the revert
dialog says the loss is permanent (all 14 locale catalogs), the editor
textarea is focused on entry, the save button reads Saving… while the
write is in flight, and a saved-but-empty edit no longer shows the
'output will appear here' placeholder.

Backend: the delete-output response drops the consumer-less 'reverted'
field, and store.delete_meeting derives the edits path through the
existing agent_edits_dir helper (same contain + refuse-linked barrier,
spelled once).
@NicholasRBowers
NicholasRBowers requested a review from a team September 12, 2026 23:13
@NicholasRBowers
NicholasRBowers requested a review from a team as a code owner September 12, 2026 23:13
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 12, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor Author

Intent: Close the seven advisory follow-ups from the minutes-editing feature (issue #7116): stop the editor losing unsaved work silently, state permanent loss plainly, polish the edit flow (focus, pending label, empty-edit placeholder), and remove two pieces of backend dead weight (the consumer-less reverted field, the hand-spelled edits-path derivation).
Not a goal: any new editing capability, redesign of the minutes editor, or refactors beyond the seven listed items.

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 1fe5981dc385730f4fa6e1a9c27b314ffa002e50 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Real data-loss harm, fixed at the right seam — a seed-anchored dirty check that survives the outputs poll, not a naive prop comparison.

Suggestions

  • The Cancel button is now guarded, but a dirty draft still dies silently when the panel unmounts (navigation, closing the meeting view); the same text !== seed check on a route-leave guard is a natural follow-up PR.

[DESIGN-REVIEWED] 1fe5981

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of 1fe5981dc385730f4fa6e1a9c27b314ffa002e50 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All claims verified: agent_edits_dir() applies contain+refuse-linked at both levels (store.py:844-853), reverted has zero consumers outside the removed type, savePending/seed reasoning holds in the component, and every item is declared and tested. Producing the review.

First-Principles-Verdict: PASS

Verify #7116 actually enumerates all seven deferred items — every item's provenance hangs on that one linked issue.

What this change ships

Inventory (9 items) — 9 justified

Intent: stop the minutes editor from silently losing or misrepresenting the user's corrections — a FIX (batch of seven review deferrals tracked in #7116).

  1. Cancelling a dirty draft now asks first; declining keeps the draft — justified
  2. An untouched draft still closes with one click, even while the agent writes more underneath — justified
  3. Revert dialog now says the edits are permanently deleted — justified
  4. The editor textarea is focused when edit mode opens — justified
  5. Save button reads "Saving…" only during this panel's own in-flight save — justified
  6. A saved-but-empty edit no longer shows the "output will appear here" placeholder — justified
  7. Delete-output response drops the reverted flag (0 consumers: grepped reverted in website/src/apps/meetings, only the type now updated) — justified
  8. Meeting deletion derives the edits path via existing agent_edits_dir() (store.py:849), deleting the hand-spelled twin; refused-link audit label shifts to meetings.agent_edits — justified
  9. Two dialog strings and the pending label added to all 14 locale catalogs (i18n gate mandates every catalog) — justified

Items 7 and 8 are subtractions; the rewritten reverted test pin is retired with counted evidence (zero consumers), not recast as a gap.

[FIRST-PRINCIPLES-REVIEWED] 1fe5981

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 1fe5981dc385730f4fa6e1a9c27b314ffa002e50 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 1fe5981

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 1fe5981dc385730f4fa6e1a9c27b314ffa002e50: <one-sentence reason>

@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of 1fe5981dc385730f4fa6e1a9c27b314ffa002e50 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

I have everything I need: the blind read, all three screenshots, the diff, the PR intent, and the sibling wording conventions (discard_unsaved_changes already exists elsewhere in the product, so the new dialog's vocabulary is consistent). Reconciliation: the dirty-cancel dialog, the strengthened revert wording, and the "Saving…" label are all shown and were all read correctly and with confidence by the blind reader; their refusal to click the red confirm buttons is the deterrent working as designed ("sure about what it does"), not a comprehension failure — the guard's value is delivered by the dialog plus the safe Cancel, which the reader would use. Two introduced states have no evidence: the edited-but-blank output (placeholder suppression) and the focus-on-open behavior. The reader's "Could not tell" section flags one real risk: the two look-alike red "Discard" dialogs.

UX-Verdict: CONCERNS

The guard reads correctly on first sight, but two look-alike red "Discard" dialogs invite skim-confusion, and two introduced states shipped unshown.

Watch

  • The two confirm dialogs are visually identical and both lead with "Discard": the blind reader worked them apart but said "at a glance they are easy to mistake for one another… If I only skimmed, I could confirm the wrong one" — confirming revert while meaning dirty-cancel permanently deletes saved edits. Moderate frequency × permanent-loss impact. Smallest fix: retitle the revert dialog to name its object, e.g. "Delete my saved edits" (apps.meetings.agentPanel.revert dialog title stays the button label; differentiate the confirm label).
  • The revert confirm asks for permanent deletion of edits the overlay is actively blurring (shot-01); the reader refused it: "I cannot see what I would lose." Fix: quote the first line of the edit in revertHint, or leave the panel legible behind the dialog.

Evidence gaps

  • Edited-but-empty output (the suppressed "awaiting output" placeholder branch in AgentPanel.tsx) appears in no screenshot — one shot of an "Edited" card with a blank body closes it.
  • Editor focus on entering edit mode (autoFocus) is invisible in static shots — a short recording of clicking "Edit this output" closes it.

[UX-REVIEWED] 1fe5981

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 1fe5981dc385730f4fa6e1a9c27b314ffa002e50 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 1fe5981

Verdict parsed from the review's SHA-scoped output markers for commit 1fe5981dc385730f4fa6e1a9c27b314ffa002e50.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 1fe5981dc385730f4fa6e1a9c27b314ffa002e50: <one-sentence reason>

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 13, 2026
@NicholasRBowers
NicholasRBowers enabled auto-merge (squash) September 13, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: passed Eligible automated validation passed for the current revision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

meetings: follow-ups from PR #5740 advisory reviews (minutes editing)

1 participant