Skip to content

Rolls: opt-in per-roll edit fork for a shared photo - #1126

Draft
PabloRuizCuevas wants to merge 3 commits into
marcinz606:mainfrom
PabloRuizCuevas:feat/pr-roll-edit-fork
Draft

PabloRuizCuevas wants to merge 3 commits into
marcinz606:mainfrom
PabloRuizCuevas:feat/pr-roll-edit-fork

Conversation

@PabloRuizCuevas

Copy link
Copy Markdown
Contributor

Summary

A photo can already belong to more than one roll, and its edit is shared wherever it's opened from -- by design. This adds an explicit, reversible way to break that sharing for one frame under one roll.

  • "Edit Independently in This Roll": right-click a frame that belongs to 2+ rolls to seed an independent copy of the shared edit, scoped to the active roll alone. Every other roll it belongs to keeps using the shared edit, undisturbed.
  • "Use the Shared Edit Again": deletes that roll-specific edit and reverts the frame to the shared one.
  • Identity via hash suffix: reuses the half-frame convention (#roll:<id> instead of #<half>), so every existing hash-keyed store -- edits, undo history, thumbnails, render cache -- picks up the fork for free with no schema change. AppController._apply_roll_forks() rewrites a discovered asset's hash to its fork on open; load_or_promote()'s new forked flag keeps a fork from ever falling back to the shared edit's path-based recovery or sidecar (which would otherwise steal or delete the shared row).
  • Marks stay with the physical scan: triage marks (keeper/reject) and legacy-hash migration read through the suffix (unforked_hash), since a keep/reject judgement belongs to the scan itself, not to one roll's fork of it.

Depends on #1109 for the Roll concept (negpy/services/assets/rolls.py, the rolls store) this fork mechanism is layered on top of.

Note: this PR is based on main but its branch includes #1109's commits (the Roll concept the fork mechanism depends on) -- please review/merge #1109 first. Once it lands, this diff will automatically shrink to just the roll-fork changes.

Testing

make all (ruff check/format, full pytest) passes with only one known unrelated failure (test_load_tooltip_follows_a_rebinding, a macOS key-symbol rendering difference, pre-existing on main). Fork-specific coverage: tests/test_rolls.py (roll_edit_hash, unforked_hash, fork_edit, is_forked, unfork_edit), tests/test_roll_edit_fork.py (AppController._apply_roll_forks, request_fork_edit_for_roll, request_unfork_edit_for_roll), tests/test_sidecar.py (load_or_promote's forked flag skips path/sidecar fallback), and tests/test_file_browser_widget.py (the context-menu entries).

🤖 Generated with Claude Code

PabloRuizCuevas and others added 3 commits September 17, 2026 17:13
… workflow

Rolls (negpy/services/assets/rolls.py) replace folders as the library's
primary listing: importing a folder recognizes it as a roll; Save as
Roll… turns any loaded batch into a reopenable virtual roll, not
necessarily from one folder. The Library tree lists every roll flat,
color-coded (folder amber, virtual coral, no left-side twisty gutter),
with Rename…, bulk Delete…, and an opt-in "rename the folder on disk"
checkbox that rehomes every loaded path when the renamed roll is the
active one.

Roll Settings dialog tags gear, capture, place, process and scanning
metadata for the current frame, a selection, or the whole roll in one
dialog, with a Load-preset flow to fill and tick fields.

New Roll moves off the toolbar onto the Film Strip section's own
header menu (CollapsibleSection.set_actions_menu), next to Reset Roll
to Defaults — a roll-wide reset (reused _asset_defaults, one frame's
worth of logic applied to every visible one) with its own confirm
dialog. The top-level toolbar is gone: actions live in the row of the
section they act on, Library's own corner row or the Film Strip row
next to the loaded frames. restore_session now recognizes the active
roll on restart too, the same "only when unambiguous" rule an
already-open multi-folder session uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A photo can already belong to more than one roll, and its edit is
shared wherever it's opened from -- by design. This adds an explicit,
reversible way to break that sharing for one frame under one roll:
"Edit Independently in This Roll" (right-click a frame that belongs to
2+ rolls) seeds an independent copy of the whole edit; "Use the Shared
Edit Again" deletes it and reverts.

Identity reuses the half-frame hash-suffix convention (`#roll:<id>`
instead of `#<half>`), so every existing hash-keyed store -- edits,
undo history, thumbnails, render cache -- picks up the fork for free
with no schema change. Triage marks and legacy-hash migration read
through the suffix (`unforked_hash`), since a keep/reject judgement
belongs to the physical scan, not to one roll's fork of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fork_edit's config parameter is typed WorkspaceConfig; a plain object()
sentinel fails ty's strict argument check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant