bug: deselecting a background file does not clear stale tag #69
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
When a file is added to
content/background/, it is automatically included in the global context selection and all slides become stale (correct). However, deselecting that file in the UI does not clear the stale tag.Root Cause
Context selection (
selectedBackgroundFolders,selectedBackgroundRootFiles, etc.) is stored entirely in localStorage.compute_context_fingerprintin the Rust lib hashes all files incontent/background/unconditionally — it has no access to the localStorage selection state.This means:
The code even acknowledges this in a comment:
Fix
Move context selection out of localStorage and into server-side metadata (
metadata.toml). Thencompute_context_fingerprintcan read the selection and hash only the selected files.This also fixes the inverse false-positive: adding a file but not selecting it would no longer mark slides stale.
Implementation Spec for Issue #69
Objective
Move the global context selection (
selectedBackgroundFolders,selectedBackgroundRootFiles,selectedBackgroundFolderFiles) from browserlocalStorageintometadata.tomlso thatcompute_context_fingerprintcan hash only the selected files. This fixes both the described bug (deselecting a file never clears the stale tag) and the inverse false-positive (adding a file but not selecting it marks all slides stale).Requirements
metadata.tomlmust store aSelectionSet-shapedcontext_selectionfield in its[global]section.compute_context_fingerprintmust accept an optionalSelectionSetand hash only the selected files when provided; fall back to the current whole-directory hash whenNone.deck.getContextSelectionanddeck.setContextSelection— expose the persisted selection to the frontend.dashboard.jsmust calldeck.getContextSelectionwhen loading a deck (instead of reading fromlocalStorage), and calldeck.setContextSelectionwhenever the selection changes.slide_edit.jsmust read the global context from the server-side selection rather than fromlocalStorage(per-slide overlay stays localStorage-resident).context_selectioninmetadata.tomlare treated as "all files selected" — no regression.nullis stored (field omitted byskip_serializing_if) to keepmetadata.tomlclean.per_slide) remains inlocalStorage— moving it is out of scope.Files to Modify
crates/hero_slides_lib/src/hashing.rscontext_selection: Option<SelectionSet>toGlobalMeta; makecompute_context_fingerprintselection-aware; updateDeckInputsContext::for_deckandfor_linked_slidecrates/hero_slides_lib/src/lib.rsload_context_selectionandsave_context_selection; updatecompute_context_fingerprintre-exportcrates/hero_slides_server/src/rpc.rsdeck.getContextSelectionanddeck.setContextSelectionhandlerscrates/hero_slides_server/openrpc.jsoncrates/hero_slides_admin/static/js/dashboard.jscrates/hero_slides_admin/static/js/slide_edit.jsinit(); write only per-slide data to localStorageImplementation Plan
Step 1 — Extend
GlobalMetaand updatecompute_context_fingerprintFiles:
crates/hero_slides_lib/src/hashing.rsDependencies: None
context_selection: Option<crate::context::SelectionSet>field toGlobalMetawith#[serde(default, skip_serializing_if = "Option::is_none")]compute_context_fingerprint(deck_path: &Path)signature tocompute_context_fingerprint(deck_path: &Path, selection: Option<&SelectionSet>) -> StringselectionisSome, hash only the selected files via a newgather_selected_fileshelper; whenNone, hash all files (existing behaviour)DeckInputsContext::for_deck: callload_metadata, extractglobal.context_selection, pass tocompute_context_fingerprintDeckInputsContext::for_linked_slidesimilarly for the source deckStep 2 — Add
load_context_selection/save_context_selectionhelpersFiles:
crates/hero_slides_lib/src/hashing.rs,crates/hero_slides_lib/src/lib.rsDependencies: Step 1
load_context_selection(deck_path) -> Result<Option<SelectionSet>>— readsmetadata.tomland returns the fieldsave_context_selection(deck_path, Option<&SelectionSet>) -> Result<()>— writes the field back tometadata.tomllib.rsStep 3 — Add RPC handlers
Files:
crates/hero_slides_server/src/rpc.rsDependencies: Step 2
handle_deck_get_context_selectionandhandle_deck_set_context_selectionasync fnsmatchblocklegacy_param_shimalready handles{collection, deck}→deck_pathStep 4 — Document new methods in
openrpc.jsonFiles:
crates/hero_slides_server/openrpc.jsonDependencies: Step 3
deck.getContextSelectionanddeck.setContextSelectionmethod definitionsStep 5 — Update
dashboard.jsFiles:
crates/hero_slides_admin/static/js/dashboard.jsDependencies: Step 3
_ctxSaveImmediate(global scope) with_ctxSaveToServer()— callsdeck.setContextSelection; sendsnullwhen all files are selected_ctxLoad(global scope) with_ctxLoadFromServer()— callsdeck.getContextSelectionlocalStorageonly forper_slidedataexpires_atTTL machinery for the global scopeStep 6 — Update
slide_edit.jsFiles:
crates/hero_slides_admin/static/js/slide_edit.jsDependencies: Step 3
_serverGlobalSelectionmodule variable +loadServerContextSelection()async loader called frominit()readContextSelection: merge_serverGlobalSelection(from server) with per-slide localStorage datawriteContextSelection: only persistper_slideto localStorage; global changes never happen inslide_edit.jsSteps 1–2 can be worked on in sequence (Rust lib). Steps 3–4 depend on steps 1–2. Steps 5–6 are independent of each other but both depend on Step 3.
Acceptance Criteria
deck.setContextSelection; a subsequent staleness check reports no stale slides from that causecontext_selectioninmetadata.toml) behave as "all files selected"metadata.tomlfor a partial selection contains[global.context_selection]metadata.tomlfor "all files selected" contains nocontext_selectionkeydeck.getContextSelectionreturnsnullfor decks with no saved selectiondeck.setContextSelectionwithnullresets to "all files"TypeErrorfrom old localStorage global selection codeNotes
compute_context_fingerprintis only called insidehashing.rs(viafor_deck/for_linked_slide) and re-exported fromlib.rs— verify no external callers before merging.deck.setContextSelectionwritesmetadata.tomlon every selection change; the 500 ms debounce indashboard.jsis sufficient.localStorage— staleness fingerprint is deck-level, so per-slide overlays do not affect it.for_linked_slidegains one newload_metadataI/O call on the staleness hot-path; acceptable given linked decks are few.Test Results
Build Failure
The test run did not complete — compilation failed in the
hero_slides_examplescrate with 4 errors:Error E0433 — unresolved module
tokio(2 occurrences)crates/hero_slides_examples/examples/health.rs:8andcrates/hero_slides_examples/examples/basic_usage.rs:8both use#[tokio::main]buttokiois not declared as a dependency in that crate.Error E0752 — async main not allowed (2 occurrences)
Because
tokiois missing, theasync fn main()functions in both example files are rejected by the compiler.Root cause:
hero_slides_examplesis missingtokioin itsCargo.tomldependencies.Fix: Add
tokio = { version = "1", features = ["full"] }(or at minimum["macros", "rt-multi-thread"]) tocrates/hero_slides_examples/Cargo.toml.Implementation Summary
Changes Made
crates/hero_slides_lib/src/hashing.rscontext_selection: Option<SelectionSet>toGlobalMeta(#[serde(default, skip_serializing_if = "Option::is_none")])compute_context_fingerprintnow acceptsOption<&SelectionSet>: whenNone, all files are hashed (legacy behaviour); whenSome, only the selected files are hashed via a newgather_selected_fileshelperDeckInputsContext::for_deckandfor_linked_slidenow read the saved selection frommetadata.tomland pass it tocompute_context_fingerprintload_context_selectionandsave_context_selectionpublic helperscrates/hero_slides_lib/src/lib.rsload_context_selectionandsave_context_selectioncrates/hero_slides_server/src/rpc.rsdeck.getContextSelectionanddeck.setContextSelectionRPC handlerscrates/hero_slides_server/openrpc.jsoncrates/hero_slides_admin/static/js/dashboard.js_ctxSaveImmediate: global selection now written to server viadeck.setContextSelection(sendsnullwhen all files selected); localStorage retains only per-slide dataloadBgPanel(deck-switch branch): global selection now loaded from server viadeck.getContextSelection; per-slide data still read from localStoragecrates/hero_slides_admin/static/js/slide_edit.js_serverGlobalSelectionmodule variable andloadServerContextSelection()async loader (called frominit())readContextSelection:globalportion comes from server cache;per_slidefrom localStoragewriteContextSelection: only persists per-slide data to localStorageTest Results
Tests run against
hero_slides_libandhero_slides_server. A pre-existing build error inhero_slides_examples(missingtokiodependency) preventscargo test --workspacefrom completing, but it is unrelated to this fix.Behaviour after this fix
deck.setContextSelection, which updatesmetadata.toml. The next staleness check hashes only the selected files, so deselected files no longer cause false stale markers.context_selectioninmetadata.tomlbehave as before (all files hashed).