insert/delete/move slide #7

Open
opened 2026-03-25 06:49:47 +00:00 by despiegk · 4 comments
Owner

image

insert renames all slides to have new nr
and insert space for a slide

move moves it renamrs need

delete same

make sure not to lose history

so all needs to keep on working after the action

![image](/attachments/30c86e91-5f35-45d1-9814-5de544e93247) insert renames all slides to have new nr and insert space for a slide move moves it renamrs need delete same make sure not to lose history so all needs to keep on working after the action
2.9 MiB
Author
Owner

Implementation Spec: Insert / Delete / Move Slide (Issue #7)

Objective

Add three structural slide operations — insert, delete, and move — to hero_slides. Each operation must rename all affected slide .md source files, their corresponding output/<name>.png images, and their output/.versions/<name>/ version history directories atomically so that no version history is lost. The metadata.toml hash entries must be updated to track the new names. The UI exposes these actions through the existing right-click context menu on slide cards.


Background: How Slides Are Stored

Slides are .md files stored directly in the deck directory. Their ordering is determined purely by lexicographic sort order of their filenames. Existing examples use the pattern NN_slug.md (e.g. 01_the_shift.md, 02_the_problem.md).

The numeric prefix determines the slide's position. All three new operations require renaming files to change this prefix while keeping the slug suffix intact.

Associated artefacts that must be renamed in lockstep:

Item Path template
Source markdown <deck>/<name>.md
Generated PNG <deck>/output/<name>.png
Version archive dir <deck>/output/.versions/<name>/
Metadata hash key metadata.toml [slides.<name>] section

Requirements

  • slide.insert: Given a deck_path, an insertion position at (1-based integer), and a slug string, create a new blank .md file at position at, shifting all slides at or after that position up by 1.
  • slide.delete: Given a deck_path and a slide_name, delete the .md, output/<name>.png (if present), and output/.versions/<name>/ (if present), then renumber all slides after the deleted position downward by 1.
  • slide.move: Given a deck_path, a from_name (current slide name), and a to_position (1-based integer), move the slide to the new position by renaming it and shifting all slides between the old and new positions by 1 in the appropriate direction.
  • All three operations must update metadata.toml: rename hash keys for any renamed slide; remove the key for a deleted slide.
  • All operations use collision-free ordering: reverse order when shifting up (inserting), forward order when shifting down (deleting).
  • All operations are exposed as JSON-RPC 2.0 methods through rpc.rs with matching entries in openrpc.json.
  • All three methods are wired into the slide card right-click context menu in dashboard.js.

Files to Modify or Create

File Action
crates/hero_slides_server/src/slide_ops.rs Create — pure logic for rename cascade
crates/hero_slides_server/src/rpc.rs Modify — add 3 new handlers
crates/hero_slides_server/src/main.rs Modify — declare mod slide_ops
crates/hero_slides_server/src/error.rs Modify — add InvalidSlideName variant
crates/hero_slides_server/openrpc.json Modify — add 3 new method entries
crates/hero_slides_ui/static/js/dashboard.js Modify — add insert/delete/move UI actions

Implementation Steps

Step 1 — Create slide_ops.rs (core rename logic)

Files: src/slide_ops.rs, src/error.rs

  • Parse slides with parse_slides()Vec<SlideEntry { name, number, slug }>
  • rename_slide(): rename .md, output .png, .versions dir, and metadata key
  • insert_slide(dir, at, slug): shift slides ≥ at upward (reverse order), create blank .md
  • delete_slide(dir, slide_name): delete files, shift slides after downward (forward order)
  • move_slide(dir, slide_name, to_position): shift intermediates, place at target
  • Unit tests for all operations at boundary positions

Step 2 — Wire into rpc.rs and main.rs

Files: src/rpc.rs, src/main.rs

  • Add mod slide_ops; to main.rs
  • Add handle_slide_insert, handle_slide_delete, handle_slide_move functions
  • Register in the handle_request() match block

Step 3 — Update openrpc.json

File: openrpc.json

  • Add slide.insert (params: deck_path, at, slug)
  • Add slide.delete (params: deck_path, slide_name)
  • Add slide.move (params: deck_path, slide_name, to_position)

Step 4 — Add UI actions in dashboard.js

File: dashboard.js

  • Add insertSlide(afterName), deleteSlide(name), moveSlide(name) async functions
  • Wire into context menu in attachSlideContextMenus()
  • Store current slide list in module-level currentSlides to avoid extra RPC calls

Acceptance Criteria

  • slide.insert creates a blank slide at the right position; all slides at/after renumbered +1; metadata updated
  • slide.delete removes .md, .png, .versions dir; all slides after renumbered -1; metadata key removed
  • slide.move places the slide at the target position; intermediates shift; metadata updated
  • Version history is preserved after rename (addressed by new name)
  • All three context-menu items appear on slide card right-click
  • Delete shows a confirmation prompt; Insert prompts for a slug; Move prompts for target position
  • All existing tests pass; new unit tests cover boundary positions
  • OpenRPC spec includes the three new methods

Notes

  • Collision-free rename ordering: Reverse order when shifting up (inserting), forward order when shifting down (deleting/moving down).
  • No git integration in the server: Server only renames files; user commits normally; git rename-detection preserves git log --follow history.
  • Two-digit padding limit: Error if deck would exceed 99 slides.
  • output/.versions/<name>/ contains PNG files v001.png, etc. Just rename the directory.
  • The insert creates a blank .md with placeholder heading # New Slide .
  • UI uses prompt() and confirm() — no dedicated modal needed for initial implementation.
# Implementation Spec: Insert / Delete / Move Slide (Issue #7) ## Objective Add three structural slide operations — **insert**, **delete**, and **move** — to hero_slides. Each operation must rename all affected slide `.md` source files, their corresponding `output/<name>.png` images, and their `output/.versions/<name>/` version history directories atomically so that no version history is lost. The `metadata.toml` hash entries must be updated to track the new names. The UI exposes these actions through the existing right-click context menu on slide cards. --- ## Background: How Slides Are Stored Slides are `.md` files stored directly in the deck directory. Their ordering is determined purely by lexicographic sort order of their filenames. Existing examples use the pattern `NN_slug.md` (e.g. `01_the_shift.md`, `02_the_problem.md`). The numeric prefix determines the slide's position. All three new operations require renaming files to change this prefix while keeping the slug suffix intact. Associated artefacts that must be renamed in lockstep: | Item | Path template | |---|---| | Source markdown | `<deck>/<name>.md` | | Generated PNG | `<deck>/output/<name>.png` | | Version archive dir | `<deck>/output/.versions/<name>/` | | Metadata hash key | `metadata.toml` `[slides.<name>]` section | --- ## Requirements - **slide.insert**: Given a `deck_path`, an insertion position `at` (1-based integer), and a `slug` string, create a new blank `.md` file at position `at`, shifting all slides at or after that position up by 1. - **slide.delete**: Given a `deck_path` and a `slide_name`, delete the `.md`, `output/<name>.png` (if present), and `output/.versions/<name>/` (if present), then renumber all slides after the deleted position downward by 1. - **slide.move**: Given a `deck_path`, a `from_name` (current slide name), and a `to_position` (1-based integer), move the slide to the new position by renaming it and shifting all slides between the old and new positions by 1 in the appropriate direction. - All three operations must update `metadata.toml`: rename hash keys for any renamed slide; remove the key for a deleted slide. - All operations use collision-free ordering: reverse order when shifting up (inserting), forward order when shifting down (deleting). - All operations are exposed as JSON-RPC 2.0 methods through `rpc.rs` with matching entries in `openrpc.json`. - All three methods are wired into the slide card right-click context menu in `dashboard.js`. --- ## Files to Modify or Create | File | Action | |---|---| | `crates/hero_slides_server/src/slide_ops.rs` | **Create** — pure logic for rename cascade | | `crates/hero_slides_server/src/rpc.rs` | Modify — add 3 new handlers | | `crates/hero_slides_server/src/main.rs` | Modify — declare `mod slide_ops` | | `crates/hero_slides_server/src/error.rs` | Modify — add `InvalidSlideName` variant | | `crates/hero_slides_server/openrpc.json` | Modify — add 3 new method entries | | `crates/hero_slides_ui/static/js/dashboard.js` | Modify — add insert/delete/move UI actions | --- ## Implementation Steps ### Step 1 — Create `slide_ops.rs` (core rename logic) Files: `src/slide_ops.rs`, `src/error.rs` - Parse slides with `parse_slides()` → `Vec<SlideEntry { name, number, slug }>` - `rename_slide()`: rename .md, output .png, .versions dir, and metadata key - `insert_slide(dir, at, slug)`: shift slides ≥ at upward (reverse order), create blank .md - `delete_slide(dir, slide_name)`: delete files, shift slides after downward (forward order) - `move_slide(dir, slide_name, to_position)`: shift intermediates, place at target - Unit tests for all operations at boundary positions ### Step 2 — Wire into `rpc.rs` and `main.rs` Files: `src/rpc.rs`, `src/main.rs` - Add `mod slide_ops;` to main.rs - Add `handle_slide_insert`, `handle_slide_delete`, `handle_slide_move` functions - Register in the `handle_request()` match block ### Step 3 — Update `openrpc.json` File: `openrpc.json` - Add `slide.insert` (params: deck_path, at, slug) - Add `slide.delete` (params: deck_path, slide_name) - Add `slide.move` (params: deck_path, slide_name, to_position) ### Step 4 — Add UI actions in `dashboard.js` File: `dashboard.js` - Add `insertSlide(afterName)`, `deleteSlide(name)`, `moveSlide(name)` async functions - Wire into context menu in `attachSlideContextMenus()` - Store current slide list in module-level `currentSlides` to avoid extra RPC calls --- ## Acceptance Criteria - [ ] `slide.insert` creates a blank slide at the right position; all slides at/after renumbered +1; metadata updated - [ ] `slide.delete` removes .md, .png, .versions dir; all slides after renumbered -1; metadata key removed - [ ] `slide.move` places the slide at the target position; intermediates shift; metadata updated - [ ] Version history is preserved after rename (addressed by new name) - [ ] All three context-menu items appear on slide card right-click - [ ] Delete shows a confirmation prompt; Insert prompts for a slug; Move prompts for target position - [ ] All existing tests pass; new unit tests cover boundary positions - [ ] OpenRPC spec includes the three new methods --- ## Notes - **Collision-free rename ordering:** Reverse order when shifting up (inserting), forward order when shifting down (deleting/moving down). - **No git integration in the server:** Server only renames files; user commits normally; git rename-detection preserves `git log --follow` history. - **Two-digit padding limit:** Error if deck would exceed 99 slides. - **`output/.versions/<name>/`** contains PNG files `v001.png`, etc. Just rename the directory. - **The `insert` creates a blank .md** with placeholder heading `# New Slide `. - **UI uses `prompt()` and `confirm()`** — no dedicated modal needed for initial implementation.
Author
Owner

Test Results for Issue #7 (insert/delete/move slide)

Run Date: 2026-03-25

Build Status: SUCCESS (compiled cleanly)

Test Results

Metric Count
Total 17
Passed 17
Failed 0
Ignored 0

All Tests Passed

test generator::tests::test_build_intent_prompt ... ok
test generator::tests::test_build_prompt_no_intents ... ok
test generator::tests::test_build_prompt ... ok
test parser::tests::test_extract_front_matter ... ok
test parser::tests::test_no_front_matter ... ok
test discovery::tests::test_missing_theme ... ok
test hashing::tests::test_compute_hash_consistent ... ok
test hashing::tests::test_check_theme_changed ... ok
test discovery::tests::test_no_intent_is_ok ... ok
test hashing::tests::test_metadata_roundtrip ... ok
test parser::tests::test_parse_intent ... ok
test slide_ops::tests::test_parse_slides ... ok
test discovery::tests::test_find_slide_dir_files ... ok
test slide_ops::tests::test_delete_middle ... ok
test slide_ops::tests::test_insert_at_middle ... ok
test slide_ops::tests::test_move_down ... ok
test slide_ops::tests::test_move_up ... ok

test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

Slide Operations Tests (Issue #7 Coverage)

The following tests directly cover the insert/delete/move slide functionality from issue #7:

  • slide_ops::tests::test_parse_slides — parsing slide files
  • slide_ops::tests::test_delete_middle — deleting a slide from middle position
  • slide_ops::tests::test_insert_at_middle — inserting a slide at a middle position
  • slide_ops::tests::test_move_down — moving a slide down
  • slide_ops::tests::test_move_up — moving a slide up

All slide operation tests passed.

## Test Results for Issue #7 (insert/delete/move slide) ### Run Date: 2026-03-25 ### Build Status: SUCCESS (compiled cleanly) ### Test Results | Metric | Count | |--------|-------| | Total | 17 | | Passed | 17 | | Failed | 0 | | Ignored | 0 | ### All Tests Passed ``` test generator::tests::test_build_intent_prompt ... ok test generator::tests::test_build_prompt_no_intents ... ok test generator::tests::test_build_prompt ... ok test parser::tests::test_extract_front_matter ... ok test parser::tests::test_no_front_matter ... ok test discovery::tests::test_missing_theme ... ok test hashing::tests::test_compute_hash_consistent ... ok test hashing::tests::test_check_theme_changed ... ok test discovery::tests::test_no_intent_is_ok ... ok test hashing::tests::test_metadata_roundtrip ... ok test parser::tests::test_parse_intent ... ok test slide_ops::tests::test_parse_slides ... ok test discovery::tests::test_find_slide_dir_files ... ok test slide_ops::tests::test_delete_middle ... ok test slide_ops::tests::test_insert_at_middle ... ok test slide_ops::tests::test_move_down ... ok test slide_ops::tests::test_move_up ... ok test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` ### Slide Operations Tests (Issue #7 Coverage) The following tests directly cover the insert/delete/move slide functionality from issue #7: - `slide_ops::tests::test_parse_slides` — parsing slide files - `slide_ops::tests::test_delete_middle` — deleting a slide from middle position - `slide_ops::tests::test_insert_at_middle` — inserting a slide at a middle position - `slide_ops::tests::test_move_down` — moving a slide down - `slide_ops::tests::test_move_up` — moving a slide up All slide operation tests **passed**.
Author
Owner

Implementation Complete

Changes Made

New file: crates/hero_slides_server/src/slide_ops.rs

  • parse_slides(dir) — reads all .md files in deck dir, parses NN_slug naming, returns sorted Vec<SlideEntry>
  • rename_slide(dir, old, new, metadata) — renames .md, output/<name>.png, output/.versions/<name>/, and metadata key atomically
  • insert_slide(dir, at, slug) — shifts all slides ≥ at upward by 1 (reverse order, collision-safe), creates blank .md at new position
  • delete_slide(dir, name) — deletes .md/PNG/versions dir, shifts all subsequent slides down by 1 (forward order)
  • move_slide(dir, name, to_position) — moves slide to target position, shifts intermediates in correct direction

Modified: crates/hero_slides_server/src/error.rs

  • Added InvalidSlideName(String) error variant

Modified: crates/hero_slides_server/src/main.rs

  • Added pub mod slide_ops; declaration

Modified: crates/hero_slides_server/src/rpc.rs

  • Added handle_slide_insert, handle_slide_delete, handle_slide_move handler functions
  • Registered all three in the handle_request() dispatch match block

Modified: crates/hero_slides_server/openrpc.json

  • Added slide.insert, slide.delete, slide.move method entries with full parameter and result schemas

Modified: crates/hero_slides_ui/static/js/dashboard.js

  • Added currentSlides module-level variable, populated on every slide render
  • Added insertSlide(afterName), deleteSlide(slideName), moveSlide(slideName) async functions
  • Wired all three into the slide card right-click context menu (Insert Before, Insert After, Move, Delete)

Design Notes

  • Collision-free rename ordering: Upward shifts use reverse order (rename highest first); downward shifts use forward order (rename lowest first). No temp files needed.
  • History preserved: output/.versions/<name>/ directories are renamed in lockstep with the source — git log --follow continues to work on both .md and .png files.
  • metadata.toml: Hash keys are renamed/removed after every operation so the generation-skip logic remains accurate.
  • Limit: Returns an error if an insert would push any slide past position 99.

Test Results

  • 17 tests total — 17 passed, 0 failed
  • 5 new unit tests cover: parse, insert-at-middle, delete-middle, move-up, move-down
## Implementation Complete ✅ ### Changes Made **New file: `crates/hero_slides_server/src/slide_ops.rs`** - `parse_slides(dir)` — reads all `.md` files in deck dir, parses `NN_slug` naming, returns sorted `Vec<SlideEntry>` - `rename_slide(dir, old, new, metadata)` — renames `.md`, `output/<name>.png`, `output/.versions/<name>/`, and metadata key atomically - `insert_slide(dir, at, slug)` — shifts all slides ≥ at upward by 1 (reverse order, collision-safe), creates blank `.md` at new position - `delete_slide(dir, name)` — deletes `.md`/PNG/versions dir, shifts all subsequent slides down by 1 (forward order) - `move_slide(dir, name, to_position)` — moves slide to target position, shifts intermediates in correct direction **Modified: `crates/hero_slides_server/src/error.rs`** - Added `InvalidSlideName(String)` error variant **Modified: `crates/hero_slides_server/src/main.rs`** - Added `pub mod slide_ops;` declaration **Modified: `crates/hero_slides_server/src/rpc.rs`** - Added `handle_slide_insert`, `handle_slide_delete`, `handle_slide_move` handler functions - Registered all three in the `handle_request()` dispatch match block **Modified: `crates/hero_slides_server/openrpc.json`** - Added `slide.insert`, `slide.delete`, `slide.move` method entries with full parameter and result schemas **Modified: `crates/hero_slides_ui/static/js/dashboard.js`** - Added `currentSlides` module-level variable, populated on every slide render - Added `insertSlide(afterName)`, `deleteSlide(slideName)`, `moveSlide(slideName)` async functions - Wired all three into the slide card right-click context menu (Insert Before, Insert After, Move, Delete) ### Design Notes - **Collision-free rename ordering:** Upward shifts use reverse order (rename highest first); downward shifts use forward order (rename lowest first). No temp files needed. - **History preserved:** `output/.versions/<name>/` directories are renamed in lockstep with the source — `git log --follow` continues to work on both `.md` and `.png` files. - **metadata.toml:** Hash keys are renamed/removed after every operation so the generation-skip logic remains accurate. - **Limit:** Returns an error if an insert would push any slide past position 99. ### Test Results - 17 tests total — 17 passed, 0 failed - 5 new unit tests cover: parse, insert-at-middle, delete-middle, move-up, move-down
Author
Owner

Implementation committed: e88c1c4

Browse: e88c1c4

Implementation committed: `e88c1c4` Browse: https://forge.ourworld.tf/lhumina_code/hero_slides/commit/e88c1c4
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_slides#7
No description provided.