Skip to content

CMS-024N — Delete saved PGN position from ProjectTab #36

Description

@levallem

Context

Audited against current main:

9478ed314a9cd7528ed38025709c2873a1f65970

This is the merge of CMS-024M / PR #33.

CMS-024M already provides the persistence API:

pub enum PgnPositionSnapshotDeleteResult {
    Deleted,
    NotFound,
}

pub fn delete_pgn_position_snapshot(
    path: &Path,
    chapter_id: i32,
    snapshot: &PgnPositionSnapshot,
) -> Result<PgnPositionSnapshotDeleteResult, String>

Its persisted identity remains:

chapter_id
+ initial_fen
+ main_line_uci
+ ply_index

The current ProjectTab already owns a contextual saved-PGN cache keyed by project path + active chapter, and pgn_position_snapshot_at(index) reads only from that cache.

main.rs was also audited: it special-cases only ProjectMessage::OpenPgnPosition(index); all other project messages already flow through ProjectTab::update(...). Therefore this task does not require a production change in src/main.rs.

Objective

Allow a user to delete one saved PGN position from the active chapter in ProjectTab, with an inline confirmation flow that is safe across project/chapter context changes.

Conceptual flow:

Saved PGN position
→ Delete
→ inline confirmation
→ Confirm delete / Cancel
→ delete_pgn_position_snapshot(...)
→ refresh contextual PGN cache
→ row disappears

No modal/dialog framework.

Safety rule: never confirm by visual index

The initial Delete click may originate from the current rendered index, but the destructive confirmation must not depend on that index still pointing to the same item.

Store an owned pending-delete value conceptually equivalent to:

pending delete =
    PgnPositionsContext { path, chapter_id }
    + cloned PgnPositionSnapshot

Only one pending delete is needed at a time.

At confirm time, compare the pending context with the current active PGN context.

If they do not match:

  • clear the pending delete;
  • perform no persistence write;
  • perform no deletion.

The persistence call must receive the cloned snapshot, never a UI index.

ProjectMessage additions

Add small ProjectTab-local messages conceptually equivalent to:

RequestDeletePgnPosition(usize)
ConfirmDeletePgnPosition
CancelDeletePgnPosition

Exact names may follow existing style, but keep the three-step semantics explicit.

Request Delete behavior

On the first Delete click:

  • resolve the snapshot only through the current contextual pgn_positions() cache;
  • clone the snapshot and current PgnPositionsContext into pending state;
  • do not write SQLite;
  • do not refresh the cache;
  • do not remove the row locally;
  • show the inline confirmation only for that pending snapshot.

If the cache is missing, stale, failed, or the index is out of range:

  • fail closed;
  • do not create pending state;
  • do not write SQLite.

Cancel behavior

Cancel must:

  • clear pending delete;
  • perform no persistence write;
  • perform no cache refresh;
  • leave the row/cache unchanged.

Confirm behavior

Context mismatch

If current active PGN context != pending context:

  • clear pending;
  • do not call the delete API;
  • do not delete anything.

Deleted

When the persistence API returns Deleted:

  • clear pending;
  • refresh pgn_positions_cache using the existing explicit refresh boundary;
  • show success feedback;
  • the deleted row must disappear from the refreshed list.

NotFound

This may happen when the database changed externally while the UI cache was stale.

When the API returns NotFound:

  • clear pending;
  • refresh pgn_positions_cache;
  • show neutral "not found" feedback;
  • recover to the current SQLite truth rather than pretending a local success.

Persistence error

When the delete API returns Err:

  • clear pending;
  • do not fabricate success;
  • do not remove the row from the loaded cache;
  • do not perform a speculative local cache mutation;
  • show failure feedback including the underlying error text;
  • fail closed.

A later normal contextual refresh may replace the cache if appropriate.

Lifecycle invalidation

Any successful operation that changes the active PGN project/chapter context must cancel pending delete.

At minimum:

  • successful project creation;
  • successful project open/switch;
  • Close project;
  • successful chapter creation when it activates the new chapter;
  • successful chapter selection.

This prevents a confirmation created in one context from being applied to another.

UI

Keep confirmation inline inside ProjectTab.

Normal row:

[Open]
[Delete]

Pending row:

Delete this saved PGN position?
[Confirm delete]
[Cancel]

Open must continue to behave exactly as it does now.

Do not add a generic modal/dialog system.

Translation keys

Add only task-specific keys to all six current translation bundles.

Suggested keys:

delete_saved_pgn_position
confirm_delete_saved_pgn_position
confirm_delete_saved_pgn_position_button
cancel_delete_saved_pgn_position
saved_pgn_position_deleted
saved_pgn_position_not_found
saved_pgn_position_delete_failed

Suggested English meanings:

Delete
Delete this saved PGN position?
Confirm delete
Cancel
Saved PGN position deleted
Saved PGN position not found
Could not delete saved PGN position

For the failure status, append the persistence error using the existing "<translated prefix>: {error}" style.

Extend the existing translation-key coverage test so every new key is non-empty for every supported language.

Scope

Expected modified files only:

src/project_tab.rs
translations/en-US/ocp.ftl
translations/es/ocp.ftl
translations/pt-BR/ocp.ftl
translations/fr/ocp.ftl
translations/cn/ocp.ftl
translations/nl/ocp.ftl

Do not modify

Do not modify:

src/project.rs
src/main.rs
src/pgn_tab.rs
src/pgn_review.rs
src/pgn_import.rs
src/schema.rs
project_migrations/
README.md
Cargo.toml
Cargo.lock

If implementation reveals a genuine need to touch one of these, stop and report it rather than broadening scope silently.

Schema and persistence

No schema or migration change.

Do not change CMS-024M's delete API or identity semantics.

PROJECT_SCHEMA_VERSION must remain 4.

No SQL should be added to ProjectTab; call delete_pgn_position_snapshot(...).

Tests

Add focused tests in the existing src/project_tab.rs test module.

At minimum cover:

Request creates pending without writing

  • load a chapter with a saved PGN position;
  • request Delete by current visual index;
  • pending stores cloned snapshot + exact project/chapter context;
  • SQLite row remains;
  • loaded cache remains unchanged.

Cancel

  • create pending;
  • Cancel;
  • pending becomes None;
  • SQLite row remains;
  • cache remains unchanged.

Confirm Deleted

  • save at least two positions;
  • request one;
  • confirm;
  • only the requested persisted identity is deleted;
  • pending is cleared;
  • cache is refreshed;
  • deleted row disappears;
  • the other row remains.

Confirm NotFound with stale cache

  • load the snapshot into ProjectTab cache;
  • remove that persisted snapshot externally through the CMS-024M API so the cache becomes stale;
  • request Delete from the stale cache;
  • confirm;
  • receive NotFound;
  • refresh;
  • cache reflects SQLite truth;
  • pending is cleared.

Persistence error fails closed

Create a deterministic persistence failure after pending state exists.

Verify:

  • no local success is fabricated;
  • pending is cleared;
  • loaded cache is not locally pruned;
  • failure status contains the translated prefix and underlying error.

Invalid/stale request paths

Cover:

  • out-of-range index;
  • missing cache;
  • mismatched/stale cache context;
  • failed cache state.

None may create pending or write SQLite.

Context invalidation

Verify pending is cancelled across the relevant lifecycle boundaries:

  • chapter selection;
  • chapter creation/activation;
  • project switch/open;
  • project creation;
  • Close project.

Render boundary

Preserve the existing invariant:

  • render/content reads the contextual cache only;
  • no SQLite read/write occurs merely because pgn_positions_content() renders.

Existing Open behavior

Existing saved-position Open behavior and cached accessor semantics must remain green.

Acceptance criteria

  • Saved PGN rows have a Delete action in ProjectTab.
  • First click only creates pending state; no SQLite write.
  • Confirmation is inline; no modal framework.
  • Pending state owns exact project/chapter context + cloned snapshot.
  • Confirmation never uses a visual list index as persistence identity.
  • Cancel performs no write and leaves the row intact.
  • Context mismatch fails closed with no delete.
  • Deleted refreshes the contextual cache and removes only the target row.
  • NotFound refreshes stale cache to SQLite truth.
  • Persistence errors never fabricate deletion or locally prune the cache.
  • Successful project/chapter context changes cancel pending delete.
  • Missing/stale/failed cache and out-of-range requests fail closed.
  • Rendering performs no SQLite I/O.
  • Existing Open behavior is unchanged.
  • No Puzzle/Analysis/PgnTab production behavior is changed.
  • No SQL is added to ProjectTab.
  • CMS-024M persistence identity is unchanged.
  • No schema/migration changes.
  • PROJECT_SCHEMA_VERSION == 4.
  • Only the seven scoped files change.
  • Translation coverage includes all new keys in all six bundles.
  • Existing and new tests are green.

Verification

Run:

git status
cargo fmt
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo check
cargo build
git diff --check
git diff --name-only
git diff -- src/project_tab.rs
git status

Before finishing, confirm the diff is limited to the seven scoped files.

Out of scope

Do not implement:

  • bulk delete;
  • delete-all;
  • reorder;
  • edit/update saved PGN positions;
  • deleting from PgnTab;
  • generic modal/dialog infrastructure;
  • schema changes;
  • migration changes;
  • persistence identity changes;
  • generic editorial-material abstractions;
  • unrelated refactors.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions