| 18 | "Field loses focus after every keystroke" in a sidebar panel | The panel rebuilds its whole form on every command signal, destroying the focused editor. Root cause fixed for the properties panel by identity-gated incremental refresh (#206) — a new field regresses it if it doesn't register a refresher, or a new panel copies the old rebuild-everything pattern | Instrument set_selected_items entry with /debug-verbose — does it run per keystroke? | New editable fields must _register_refresh(...); relationship-dependent fields must join _compute_identity. Free-text fields commit on debounce + focus-out (never per keystroke, never focus-out-only) and must flush via _pending_text_commits before any rebuild | #200 → #206 → #210; chronicle: rebuild only on a genuine and debounce, not per keystroke |
| 19 | Panel shows stale data after undo/redo (correct after re-selecting) | Panel wired only to selectionChanged (or the legacy command_executed, which does not fire on undo/redo) | Undo → check panel; re-select same item → panel corrects itself. That asymmetry is the signature | Wire the panel refresh to cmd_mgr.stack_changed (fires once per execute/register_applied/undo/redo, never on clear()). Test pattern: tests/integration/test_panel_refresh_wiring.py | #222/#223/#225; chronicle: stack_changed |
| 20 | After a save, undo then close — no "unsaved changes" prompt, edit silently lost | Dirtiness not wired to the undo stack. Contract: exactly two ways onto the undo stack — CommandManager.execute() and register_applied() — both emit the full signal set incl. stack_changed → mark_dirty. Hand-rolled _undo_stack.append(...) breaks it | Save → Ctrl+Z → is the title-bar * present? | Route through register_applied; never append to _undo_stack directly. Test: tests/integration/test_undo_redo_dirty.py | #209; chronicle: two* ways onto the undo stack |
| 21 | Sidebar list selection "works only sometimes" / list scroll jumps to top after clicking | (a) Rows cache live QGraphicsItem refs that go stale after undo/delete → setSelected no-ops; (b) scene.changed fires on every repaint and drives a destructive list rebuild racing the click | Click, then check whether a rebuild ran between press and the deferred selection (instrument with /debug-verbose + stack traces) | Store item_id only, resolve at click time via scene.find_item_by_id; defer scene mutation with QTimer.singleShot(0, ...); debounce and signature-guard the rebuild (diff before rebuild — a debounce limits how often, not whether) | #212; chronicle: store item *ids |
| 22 | Warning borders (soil mismatch etc.) go stale after dragging a plant in/out of a bed — but update after any other edit | QGraphicsScene.changed fires only on visual changes; Python attribute writes (parent_bed_id, _child_item_ids) emit nothing, so the debounced refresh never triggers | The stale-until-unrelated-edit pattern is itself the discriminator | Funnel reparenting through SetParentBedCommand (it calls trigger_soil_mismatch_refresh itself); any new reparent path must call it explicitly (src/open_garden_planner/core/commands.py) | #173; chronicle: only fires on geometry/visibility; debug-verbose case study |
| 23 | Marking a task done on one surface (calendar vs Tasks tab) invisible on the other | Two surfaces deriving a shared store key differently | Print both surfaces' computed task_id for the same task | Both key and format must come from one shared function: canonical species_key() (src/open_garden_planner/models/plant_data.py) + make_calendar_task_id() (services/task_generator.py). A sync test must build ids the way production does (realistic species with source_id), not hand-written strings | #227; chronicle: derive the key identically |
| 24 | A startup notification "never appears" | statusBar().showMessage() written while a modal dialog (Welcome) covers the window — painted behind it, then expires | Reproduce via the Welcome→recent-project path specifically | Persistent dismissible bar (TaskReminderBar pattern) + defer the check with QTimer.singleShot(0, ...) past the modal exec() | #227 fix #16; chronicle: modal dialog is invisible |
| 25 | New bed feature (menu entry, badge) works on rectangle beds but missing on polygon/ellipse/circle beds | Four independently-implemented bed shape classes; hand-wiring one misses the others | Right-click each of the four shapes | Never hand-code per shape: GardenItemMixin.build_bed_context_menu + dispatch_bed_action central dispatch; extend the parametrised tests/integration/test_bed_context_menu.py. Read §8.14 + ADR-017 first (CLAUDE.md marks this READ FIRST) | US-12.7/12.8 recurrences; chronicle: all bed-shape item types |