Skip to content

Fixes #281 - #282

Merged
guijacquemet merged 3 commits into
mainfrom
claude/header-name-confirm
Nov 11, 2026
Merged

guijacquemet merged 3 commits into
mainfrom
claude/header-name-confirm

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

Fixes #281. Base: main 2d1ab30 (the merge of #280). One commit: 6ee1b46.

Summary

This follows the owner decision of 11 November: an explicit confirm on Channels.

  • Channels step: a second question sits under the moves question (Notebook: no way to record a channel re-confirmation after re-ordering a unit's channels #258), in the same single column.
    • It lists each saved channel that lost a header name, with its names saved and now. For example: "DNA: header names “DAPI”, “Hoechst” → “DAPI”", or "DNA: header name “DAPI” → none".
    • It asks once: Yes, this header name changed, with Undo. Nothing is ticked for you.
  • What gets recorded: only that click records the change. Publication gets one §4.6 reconfirmation per channel: the channel ID, new_id null, the date of the click, and both SHA-256 values. This is the record format moves have used since Fixes #258, fixes #252 #278.
  • What the click covers: exactly the change that was shown, so a later change asks again. The list is computed with publication's own comparison (§4.6 (c), header_names as stored).
  • No removal or rename answer accepts a lost header name any more. This holds whether the loss comes alone, with a removal, or with a rename. The published file keeps no names per folder, so Yes, remove cannot show that a name went with its folder.
    • So "Hoechst" → "DAPI" (another saved name) with a removal no longer publishes unseen. That was question 1 of the Fixes #279 #280 review.
    • When the removal and the click both touch the same ID, they share one line per ID, which carries the removal's date. Move plus removal already works this way.
  • A lost header name has no confirmation path: a later re-tag of that channel is refused for good #281 dead end: say a removal was saved without a re-tag, so the removed folder's names stayed in the file. A later re-tag of that channel was then refused for good. That re-tag is now listed, and it saves once you click.
  • Line beside the disabled Save: it names each change still waiting, for example "DNA: header name “DAPI” → none is not confirmed: see Channels (Yes, this header name changed)". This fixes question 2 of the Fixes #279 #280 review: folders whose headers never stated names are no longer told they "lost a saved header name in ctrl/rep1".
  • Moved channel that also lost a name: it is now in both lists, so neither click accepts the other change unseen. Before, a lost name kept a channel out of the moves list, and the name click would have accepted the move unseen.
  • The red refusal text now names the way out: "…a channel that moved is confirmed on Channels, and so is a header name that changed."
  • Code:
    • channel_moves.name_changes is new (pure);
    • reconfirmations(..., names=) accepts a header-name loss only for channels confirmed as shown;
    • Fixes #279 #280's headers_changed / HEADER_NAME are removed (no longer used);
    • the question widget is shared by both questions;
    • notebook_check_save.py stays under 600 lines.
  • CLI describe: it does not share this logic. It holds no answers and builds no re-confirmations. Only its user-guide sentence was extended.
  • No Spec change. tests/spec_fixtures/ is untouched.

For review: a conflict in the requested tests

"A name held only by the removed folder is still covered by the removal" cannot hold together with "'Hoechst' → 'DAPI' with a removal needs the click" when the user re-tags.

  • Both cases leave the same published file (DNA = ["DAPI", "Hoechst"]), the same folders on disk and the same user actions. Fixes #279 #280's review already pointed this out.
  • I followed the owner rule ("any header-name loss … needs that click; nothing publishes unseen").
  • So the guard now reads like this:
    • Without a re-tag, the removal alone still covers the removed folder's name. Nothing more is asked, DNA gets one record, and "Hoechst" stays in the file.
    • A re-tag that drops it is asked like any other loss. After the click, DNA still gets one record.
  • This changed Fixes #279 #280's guard test test_a_header_name_held_only_by_the_removed_folder_is_the_removals, which re-tagged and expected Save to be enabled without asking. It is now two tests: no re-tag, and re-tag plus click. Please confirm this reading.

Evidence

  • New and changed tests, all failing on main. Run against main's src, they fail at the missing question (names_box). Main's behaviour in each scenario, from a probe on main:

    Scenario Main
    A lost header name has no confirmation path: a later re-tag of that channel is refused for good #281 later re-tag refused, "these answers are refused" (dead end)
    Hoechst → DAPI + removal published unseen, Hoechst dropped
    Hoechst → no names + removal refused for good
    Folders that never had names refused with "ACTIN lost a saved header name in ctrl/rep1 …" ×9
    Lost name alone refused, no way out
    • tests/test_header_name_confirm.py:
    • tests/test_reconfirm_scope.py: the parametrized test now covers other name, other saved name ("DAPI"), empty name and no header names, each with a removal. In each case Save stays disabled with the reason beside it until the click, and then saves with one DNA record. The removed-folder guard is split as described above.
    • test_notebook_channel_moves.py::test_a_lost_header_name_is_asked_besides_the_move replaces the old "not offered as a move" test.
    • test_notebook_stepper.py: the Channels page now ends with the header-name question.
  • Byte-identical publishes, main vs this branch: the SHA-256 of morphobricks.toml, experiment_design.csv and morphobricks.records.jsonl is identical in all five scenarios:

    • first save;
    • header rename via header_names;
    • confirmed removal;
    • confirmed move;
    • move plus removal.
  • Records files: checked with MorphoBricks-Spec main b4b1008 tools/audit/refrecords.py check. All pass:

    File Result
    move 2 records OK
    removal 7 records OK
    move plus removal 7 records OK
    name alone 1 record OK
    A lost header name has no confirmation path: a later re-tag of that channel is refused for good #281 dead end 8 records OK
    removal plus name click 7 records OK
    folders that never had names 7 records OK
  • Screenshots (headless Chromium via ipywidgets embed):

    • before: Channels shows no question, and Save is refused;
    • after: the question, the confirmed state, and the line beside Save.

    The widgets were not looked at in a real JupyterLab or Colab.

Tests

python tools/check.py on 6ee1b46 passed:

  • ruff check: all checks passed;
  • ruff format --check: 438 files already formatted;
  • pytest: 5299 passed, 4 skipped;
  • code rules: passed.

The 4 skips are environment-only:

  • test_cli_log.py:166, test_folder_rules.py:199 and test_folder_rules.py:230: "this file system tells names apart by case";
  • test_discovery.py:665: "running with permissions that ignore file modes".

Spec modules: tests/test_spec_*.py 1733 passed, 0 skipped.


Generated by Claude Code

A saved channel that loses a header name is a changed entity (experiment-v1
4.6 (c)) and had no confirmation: after a removal saved without a re-tag, any
later re-tag of that channel was refused for good. The Channels step now lists
each such channel with its header names saved and now and asks once ("Yes, this
header name changed"); only that click gives publication one reconfirmation per
channel (new_id null, the date of the click, both SHA-256), as for a move. The
click covers exactly the change shown; a later change asks again.

No removal or rename answer accepts a lost header name any more: the published
file keeps no names per folder, so a switch to another saved name with a removal
no longer publishes unseen. The line beside the disabled Save names each change
still waiting, instead of telling folders that never had names that they lost
one. A channel that moved and lost a name is asked both questions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…s missing (#281)

The line beside the disabled Save said only "1 answer still missing" while a
moved channel or a lost header name also waited on Channels, so that question
was found only after answering. It now names both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Reopened as saved, the step is folded behind "Saved on ..., nothing changed".
A re-tag on Channels then left it folded with only [Run] [Change...] and no
line, hiding the waiting header-name question and the line beside Save. A
change since the save (or a refusal, as before) now unfolds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc

Copy link
Copy Markdown
Contributor Author

Independent review of 6ee1b46, with two fixes pushed (head now 644591c)

Checked from a fresh clone, venv .[dev,notebook,zarr]

Behaviour on main vs this branch

I wrote probe scripts that do not use the new widget, so they also run on main. "Click" means Yes, this header name changed. "Saves" means Save is enabled and publishing succeeds.

Scenario Main This branch, before the click This branch, after the click
#281 later re-tag refused, no way out refused, and the line names the change saves; one DNA record
"Hoechst" → "DAPI", with a removal published unseen ("Hoechst" dropped) refused saves; one DNA record
"Hoechst" → no name, with a removal refused for good refused saves
Folders that never had names refused, with the misleading "lost … in ctrl/rep1" refused; the line names ACTIN, DNA and PHASE saves
Lost name alone refused, no way out refused saves; one record

So main's tests fail for behavioural reasons too, not only because the widget is missing.

Adversarial paths (branch)

  • Move and lost name on the same channel. No click, the moves click only, or the names click only: each stays refused, and the line names whatever is still open. Both clicks: saves, with one line each for ACTIN and DNA.
  • Two removals (drug/rep1 and ctrl/rep2) plus a lost name: refused until the click, then saves with one DNA line.
  • Undo, a further re-tag, then a re-tag back to the first change: that change is asked again, and it saves only after a new click.
  • Re-tag of another channel after a click: asked again (the list changes, so the toggle resets).
  • Rename plus lost name: checked in the pure layer only. _names_lost does not depend on any answer, so a rename never accepts the loss. The notebook states no folder renames.
  • RGB: R, G and B have no header names, so they are never listed.
  • Empty vs missing names: covered by the parametrized cases.

Records and byte identity

  • Records files: checked with MorphoBricks-Spec main b4b1008 refrecords.py check. Move: 2 records OK. Removal, move plus removal, and removal plus name click: 7 records OK each. Name click after that: 8 records OK. Each publication has one line per ID. When a removal and a click touch the same ID, the line carries the removal's date, as move plus removal already did.
  • Byte-identical publishes, main vs branch: the SHA-256 of morphobricks.toml, experiment_design.csv and morphobricks.records.jsonl matches in all five earlier scenarios (first save, header rename, removal, move, move plus removal). I re-ran these myself on fixed paths.

Fixes (one commit each; each regression test fails before its fix)

  1. 7aa4169: the line beside Save named the open Channels questions only once nothing else was missing.
    • Example: a re-order plus a lost name reopens "same way?", so an answer is missing.
    • The line said only "1 answer still missing" while both Channels questions were shown and waiting.
    • It now names them too.
    • Test: test_the_line_beside_save_names_the_changes_waiting_while_an_answer_is_missing.
  2. 644591c: a change since the save left Check and save folded.
    • This was already on main, and it is visible in screenshots 02/12/14.
    • Flow: reopen as saved, then re-tag on Channels, then open the Save step.
    • The step showed only [Run] [Change…], with no line at all. The waiting question and the line beside Save were hidden.
    • A change since the save now unfolds the step, as a refusal already did.
    • Test: test_a_change_on_channels_unfolds_the_saved_reopening.

notebook_check_save.py is 600 lines, which is the limit.

Gate on 644591c (python tools/check.py): ruff passed; 438 files formatted; 5301 passed, 4 skipped; code rules passed. The 4 skips are the same environment-only ones as before. Spec modules: 1733 passed, 0 skipped.

I accept the guard-test reading: a re-tag that drops the removed folder's name is asked like any other loss.

Not fixed: on main and outside this PR. Removing every member of a repeat (ctrl/rep2 and drug/rep2) is refused with R_USED_ID_DROPPED at used_ids.repeats. Section 10 says the ID stays in used_ids. Reproduced on main 2d1ab30. I found no existing issue for it.


Generated by Claude Code


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A lost header name has no confirmation path: a later re-tag of that channel is refused for good

2 participants