Skip to content

Commit 4ba56d5

Browse files
committed
fix: continue manual merge workflow after commit
1 parent add8d7e commit 4ba56d5

10 files changed

Lines changed: 283 additions & 47 deletions

design/merge-view-architecture.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,12 @@ The refactor should establish the following invariants:
152152
before and after states.
153153
8. Cancellation restores the exact entry snapshot.
154154
9. Undoing a Merge-mode merge restores the exact pre-commit workspace.
155-
10. Workspace edits do not enter the curation undo stack.
156-
11. Public selection and plugin APIs remain compatible.
157-
12. Selection transitions operate on cluster IDs and do no work proportional to
155+
10. A successful manual merge remains in Merge mode with its result as the sole
156+
blue reference; its cancellation snapshot is the corresponding settled
157+
Normal-mode result selection.
158+
11. Workspace edits do not enter the curation undo stack.
159+
12. Public selection and plugin APIs remain compatible.
160+
13. Selection transitions operate on cluster IDs and do no work proportional to
158161
every spike.
159162

160163
## 4. Proposed domain model
@@ -210,6 +213,8 @@ class MergeSession:
210213
reference_id: int
211214
ordered_ids: tuple[int, ...]
212215
entry_snapshot: NormalWorkflowSnapshot
216+
proposition_id: str | None = None
217+
is_post_merge: bool = False
213218
```
214219

215220
`ordered_ids[0]` is always `reference_id`. The reference cannot be removed or
@@ -221,6 +226,13 @@ not arbitrary application state. It includes selections, reference,
221226
presentation order, and any table filter, sort, scroll, or navigation state that
222227
entering or editing Merge mode changes.
223228

229+
`is_post_merge` distinguishes the automatically retained singleton workspace
230+
from a manually entered, uncommitted workspace. This allows `Undo` to target the
231+
commit directly without allowing a fresh temporary workspace to undo an older
232+
curation action. Workspace edits preserve this marker; exiting and manually
233+
re-entering Merge mode clears it. Proposition workspaces use their own provenance
234+
and automatic-advancement contract and are never post-merge continuations.
235+
224236
### 4.4 Selection change
225237

226238
```python
@@ -402,11 +414,17 @@ For a Merge-mode merge:
402414

403415
1. capture the complete pre-commit Merge state;
404416
2. execute the clustering merge;
405-
3. capture the resulting Normal-mode state;
417+
3. capture the resulting Normal-mode state and wrap it as the entry snapshot of
418+
a singleton post-merge continuation workspace;
406419
4. store both on the global action entry;
407420
5. on undo, undo controllers and restore `selection_before` transactionally; and
408421
6. on redo, redo controllers and restore `selection_after` transactionally.
409422

423+
The continuation keeps Merge View visible, makes the result the sole staged blue
424+
reference, clears Similarity selection, and recomputes Similarity rows. Exiting
425+
with `V` restores its settled Normal-mode result snapshot. Group and metadata
426+
actions remain disabled until that exit.
427+
410428
The existing `request_undo_state` mechanism may be used as a compatibility step,
411429
but the final ownership of Merge workflow context belongs to the global curation
412430
action, not the `Clustering` model.
@@ -529,6 +547,8 @@ The initial Merge-mode action policy is:
529547
- reject unsafe direct or plugin calls explicitly without partially mutating the
530548
workspace;
531549
- do not let an uncommitted Merge session undo an earlier curation action;
550+
- keep a successful manual merge in a marked singleton continuation workspace
551+
whose Undo action targets that commit directly;
532552
- after undoing a Merge-mode merge, allow redo to reapply it; and
533553
- truncate that redo branch normally if the restored workspace is edited and a
534554
different curation action is committed.

design/merge-view-dock-stability.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ Automatic advancement after a successful proposition merge is slightly
104104
different because the clustering has changed. It should construct the settled
105105
post-merge Normal state, use that as the next proposition's entry snapshot, and
106106
then project the next Merge workspace without hiding or recreating the dock.
107-
Failed and manual merges retain their existing no-advancement behavior.
107+
Failed merges retain their workspace unchanged. Successful manual merges do not
108+
advance proposition review; they reuse the dock for a singleton continuation
109+
workspace containing the merged result as its blue reference.
108110

109111
Reject-and-advance and shortcut navigation use the same in-place replacement
110112
path. Selecting a nonactionable proposition still cancels to Normal mode and
@@ -201,8 +203,9 @@ intermittent Qt shutdown crash.
201203
- `P1 -> P2` preserves `id(merge_view)` and `id(merge_view.dock)`.
202204
- Manual Merge to proposition review preserves those identities.
203205
- Shortcut navigation and reject-and-advance do not hide or recreate the dock.
204-
- Successful auto-advance updates the existing view; failed and manual merges
205-
do not advance.
206+
- Successful auto-advance updates the existing view; successful manual merges
207+
project the result into the same view without advancing, and failed merges
208+
leave it unchanged.
206209
- All unrelated dock geometries remain unchanged across proposition switches.
207210
- Cancel/hide/reopen restores the Merge dock area and docked extent.
208211
- A floating Merge dock retains its exact position and size.

design/merge-view-workflow.md

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,48 @@ GUI reports that another candidate is required.
147147

148148
After a successful merge:
149149

150-
- Merge mode ends;
151-
- Merge View is cleared and hidden while its dataset-scoped dock is retained;
152-
- Cluster View is re-enabled;
153-
- the new merged cluster becomes the blue Cluster View selection; and
154-
- Similarity View is recomputed for the new cluster using the normal post-merge
155-
workflow.
150+
- Merge mode remains active;
151+
- Merge View replaces the committed inputs with the new merged cluster as its
152+
sole staged row and blue reference;
153+
- Similarity View clears its selection and is recomputed for the new cluster;
154+
- the curator may stage or select more candidates and press `G` again; and
155+
- the dock control changes from **Cancel Merge Mode** to **Exit Merge Mode**;
156+
`V`, that control, or closing Merge View exits to Normal mode with the merged
157+
cluster selected in Cluster View.
158+
159+
Group and metadata changes remain unavailable while Merge mode is active. The
160+
curator must exit with `V`, **Exit Merge Mode**, or the Merge View close control
161+
before classifying the merged cluster as `good`, `mua`, `noise`, or another
162+
quality.
163+
164+
Successful proposition merges keep their separate review contract: they open
165+
the next pending proposition when one exists, rather than pausing on the merged
166+
result.
156167

157168
If the merge fails, the complete Merge-mode state remains unchanged.
158169

159-
## Cancelling Merge mode
170+
## Cancelling or exiting Merge mode
160171

161-
All of the following cancel Merge mode:
172+
All of the following leave Merge mode:
162173

163174
- pressing `V` while Merge mode is active;
164-
- activating a prominent **Cancel Merge Mode** control; or
175+
- activating the prominent **Cancel Merge Mode** control, renamed **Exit Merge
176+
Mode** after a successful manual commit; or
165177
- closing Merge View.
166178

167-
Cancellation performs no clustering action. It restores the exact snapshot from
168-
immediately before Merge mode was entered, regardless of additions, removals, or
169-
reordering performed in Merge mode. In other words:
179+
Cancellation performs no clustering action. Before the first commit, it restores
180+
the exact snapshot from immediately before Merge mode was entered, regardless of
181+
additions, removals, or reordering performed in Merge mode. In other words:
170182

171183
```text
172184
state A -> enter Merge mode -> edit workspace -> cancel -> state A
173185
```
174186

187+
After a successful manual merge, the continuation workspace receives a new
188+
Normal-mode entry snapshot containing the merged cluster. Cancelling that
189+
workspace therefore exits with the committed merged cluster selected; it never
190+
tries to restore source clusters that no longer exist.
191+
175192
Closing Merge View must visibly communicate that it cancels the mode. Merge mode
176193
must also be unmistakable while active: Merge View is labelled **MERGE MODE**,
177194
Cluster View is dimmed or overlaid with an explanation, and the status area shows
@@ -195,8 +212,12 @@ before `G`, including:
195212

196213
Undoing that merge restores both the original clusters and the complete Merge
197214
workspace as it existed immediately before `G`. Redoing it reapplies the merge
198-
and exits Merge mode again. This special restoration applies only to merges
199-
initiated from Merge mode; ordinary merge undo behavior remains unchanged.
215+
and restores the singleton post-merge continuation workspace. `Undo` is
216+
available in that continuation workspace and targets the committed merge
217+
directly. A manually entered workspace that has not committed a merge still
218+
cannot undo an earlier curation action. This special restoration applies only
219+
to merges initiated from Merge mode; ordinary merge undo behavior remains
220+
unchanged.
200221

201222
Workspace transfers and reordering are temporary UI operations and do not create
202223
entries in the clustering undo stack.
@@ -210,10 +231,10 @@ entries in the clustering undo stack.
210231
| Merge | Ctrl+right-click removable Merge row | Transfer candidate to Similarity |
211232
| Merge | Ctrl+Space | Select the next Similarity candidates |
212233
| Merge | Backspace | Clear only the Similarity selection |
213-
| Merge | `G` | Merge Merge contents plus selected Similarity candidates |
234+
| Merge | `G` | Commit the selection and continue with its result as the blue Merge reference |
214235
| Merge | `V`, Cancel, or close Merge View | Restore the entry snapshot exactly |
215236
| After Merge-mode merge | Undo | Restore clusters and pre-commit Merge workspace |
216-
| Restored after undo | Redo | Reapply merge and return to normal mode |
237+
| Restored after undo | Redo | Reapply merge and restore the singleton continuation workspace |
217238

218239
## Extension
219240

docs/changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ behavior they verify rather than listed separately.
4040
size; proposition navigation updates that dock without moving neighboring
4141
views. The dimmed Cluster View remains scrollable. Scientific views follow Merge View order and then
4242
selected Similarity rows in visible table order. Cancellation restores the entry state, and
43-
undo restores the full pre-merge workspace.
43+
undo restores the full pre-merge workspace. After a successful manual merge,
44+
the result remains as the sole blue Merge View reference for inspection or a
45+
further merge; press `V` to return to Normal mode before assigning quality.
4446
- Review AIND/SpikeInterface format-version 2 merge propositions from
4547
dataset-local `curation.json` in a persistent **Merge Propositions** view.
4648
Its compact rows have no action buttons and carry stable source-order display

docs/clustering.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,20 @@ order-dependent views, while each cluster keeps the same color across tables and
3131
for the entire Merge session. Press `Backspace` to clear only the Similarity View selection when the
3232
merge should contain only the staged rows.
3333

34-
Press `G` to commit the staged clusters plus the selected Similarity candidates. Press `V` again,
35-
use **Cancel Merge Mode**, or close Merge View to cancel and restore the exact state from before
36-
entry. Undoing a committed Merge-mode merge restores the complete workspace as it appeared just
37-
before `G`; Redo reapplies the merge and returns to the normal workflow. The Merge View dock is
38-
reused throughout the dataset session: proposition changes do not move it, and reopening restores
39-
its previous docked extent or floating position and size without resetting neighboring docks.
34+
Press `G` to commit the staged clusters plus the selected Similarity candidates. The resulting
35+
unit remains in Merge View as the sole staged row and blue reference, with Similarity View
36+
recomputed around it. Add more candidates to continue merging, or press `V` to return to Normal
37+
mode with the result selected before assigning its quality. Group and metadata changes remain
38+
unavailable in Merge mode.
39+
40+
Before a merge is committed, pressing `V`, using **Cancel Merge Mode**, or closing Merge View
41+
cancels and restores the exact state from before entry. After a commit, the dock control becomes
42+
**Exit Merge Mode** and those controls exit with the latest merged result selected. Undoing a
43+
committed Merge-mode merge directly restores the complete workspace as it appeared just before
44+
`G`; Redo reapplies the merge and restores its singleton continuation workspace. The Merge View
45+
dock is reused throughout the dataset session:
46+
proposition changes do not move it, and reopening restores its previous docked extent or floating
47+
position and size without resetting neighboring docks.
4048

4149
### Reviewing merge propositions
4250

docs/quickstart.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,12 @@ clusters and press `G` to merge. phy gives the result a new cluster ID. Press
117117

118118
For a longer comparison, press `V` first. Merge View keeps the candidates staged while you
119119
continue exploring Similarity View. Its status shows exactly how many clusters `G` will merge.
120-
Press `V` again or close Merge View to cancel without changing the clustering. Reopening uses the
121-
same dock and restores its previous placement and size.
120+
After `G`, the merged result stays in Merge View as the blue reference so you can inspect it or
121+
add candidates for another merge. Press `V` to return to Normal mode before assigning the result
122+
to `good`, `mua`, or `noise`. Before the first commit, `V` or closing Merge View cancels without
123+
changing the clustering; afterward it exits with the latest merged result selected. Reopening uses
124+
the same dock and restores its previous placement and size. `Ctrl+Z` directly undoes the latest
125+
commit and restores its pre-merge workspace.
122126

123127
If the Template GUI dataset includes an AIND/SpikeInterface format-version 2
124128
`curation.json` with merge suggestions, use the persistent **Merge Propositions**

phy/cluster/_selection.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ class MergeSession:
4343
ordered_ids: tuple[int, ...]
4444
entry_snapshot: NormalWorkflowSnapshot
4545
proposition_id: str | None = None
46+
is_post_merge: bool = False
4647

4748
def __post_init__(self):
4849
ordered = _as_unique_ids(self.ordered_ids)
4950
if not ordered or ordered[0] != self.reference_id:
5051
raise ValueError('The merge reference must be the first staged cluster.')
5152
if self.proposition_id is not None and not self.proposition_id:
5253
raise ValueError('The merge proposition ID cannot be empty.')
54+
if self.proposition_id is not None and self.is_post_merge:
55+
raise ValueError('A proposition workspace cannot be a post-merge continuation.')
5356
object.__setattr__(self, 'ordered_ids', ordered)
5457

5558

@@ -350,6 +353,26 @@ def enter_merge_proposition(self, proposition_id, ordered_ids, workflow_context=
350353
)
351354
)
352355

356+
def continue_after_merge(self, cluster_id, workflow_context=None):
357+
"""Continue manual Merge mode with a committed result as the new reference."""
358+
self._require_merge_mode()
359+
if self._state.merge.proposition_id is not None:
360+
raise ValueError('Proposition merges advance through the proposition workflow.')
361+
normal = CurationSelectionState(cluster_ids=(cluster_id,))
362+
merge = MergeSession(
363+
cluster_id,
364+
(cluster_id,),
365+
NormalWorkflowSnapshot(normal, workflow_context),
366+
is_post_merge=True,
367+
)
368+
return self._apply(
369+
CurationSelectionState(
370+
mode=WorkflowMode.MERGE,
371+
reference_id=cluster_id,
372+
merge=merge,
373+
)
374+
)
375+
353376
def switch_merge_proposition(self, proposition_id, ordered_ids):
354377
"""Replace the active Merge workspace while preserving its Normal entry snapshot."""
355378
self._require_merge_mode()
@@ -395,6 +418,7 @@ def add_to_merge(self, cluster_ids, insertion=None):
395418
tuple(ids),
396419
current.merge.entry_snapshot,
397420
proposition_id=current.merge.proposition_id,
421+
is_post_merge=current.merge.is_post_merge,
398422
)
399423
similar = tuple(cluster_id for cluster_id in current.similar_ids if cluster_id not in new)
400424
effective = _ordered_union(merge.ordered_ids, similar)
@@ -421,6 +445,7 @@ def remove_from_merge(self, cluster_ids):
421445
tuple(i for i in current.merge_ids if i not in removed),
422446
current.merge.entry_snapshot,
423447
proposition_id=current.merge.proposition_id,
448+
is_post_merge=current.merge.is_post_merge,
424449
)
425450
similar = _ordered_union(current.similar_ids, removed)
426451
effective = _ordered_union(merge.ordered_ids, similar)
@@ -452,6 +477,7 @@ def deselect_from_merge(self, cluster_ids):
452477
merge_ids,
453478
current.merge.entry_snapshot,
454479
proposition_id=current.merge.proposition_id,
480+
is_post_merge=current.merge.is_post_merge,
455481
)
456482
slots = list(current.color_slots)
457483
if reference != current.reference_id:
@@ -484,6 +510,7 @@ def reorder_merge(self, cluster_ids, insertion):
484510
tuple(remain),
485511
current.merge.entry_snapshot,
486512
proposition_id=current.merge.proposition_id,
513+
is_post_merge=current.merge.is_post_merge,
487514
)
488515
return self._apply(
489516
CurationSelectionState(

0 commit comments

Comments
 (0)