refactor: collapse per-component staleness snapshots into a mirrored struct #62
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
SlideMetaEntrymaintains two parallel representations of the same staleness data:last_generated(composite hash)last_theme_hash,last_context_fingerprint,last_prompts_hash,last_image_modelThe attribution fields in
SlideMetaEntrymanually mirrorDeckInputsContext. Adding any new input component requires touching 4 places:DeckInputsContext,SlideMetaEntry(current + last_*),compose_inputs_hash, andcompute_stale_reasons. The#[allow(clippy::too_many_arguments)]oncompute_stale_reasons(8 args) is the surface symptom.Note: staleness is checked live without a page reload, so the fix must not change the shape of the JSON returned by the staleness RPC — only the internal Rust data model and
metadata.tomlserialization need to be updated.Fix
Introduce a
ComponentSnapshotsstruct that mirrorsDeckInputsContext:Replace the four flat
last_*fields inSlideMetaEntrywithlast_components: Option<ComponentSnapshots>. Thencompute_stale_reasonstakes(current: &DeckInputsContext, last: Option<&ComponentSnapshots>)instead of 8 loose arguments.Adding a new input component then becomes a 2-place change (add field to
DeckInputsContext+ComponentSnapshots) instead of 4.Also fix
compose_inputs_hashhas a deadhasher.update(b"")placeholder reserved for a future layout hash — remove or replace with a real value when the feature lands.slide_stalenesscallsdeck_stalenessand filters — fine for now, but worth noting as a future perf concern for large decks.Files
crates/hero_slides_lib/src/hashing.rs—SlideMetaEntry,DeckInputsContext,compose_inputs_hashcrates/hero_slides_lib/src/deck.rs—compute_stale_reasons,deck_staleness,slide_stalenessImplementation Spec for Issue #62
Objective
Replace the four flat
last_*attribution fields inSlideMetaEntry(last_theme_hash,last_context_fingerprint,last_prompts_hash,last_image_model) with a single nestedlast_components: Option<ComponentSnapshots>struct that mirrorsDeckInputsContext. Simplifycompute_stale_reasonsto acceptmeta: Option<&SlideMetaEntry>and read snapshot fields vialast_componentsinstead of 8 loose arguments. Remove the deadhasher.update(b"")placeholder incompose_inputs_hash. The JSON shape returned by staleness RPCs must not change.Requirements
ComponentSnapshotsmust deriveDefault,Clone,Serialize,DeserializesoOption<ComponentSnapshots>round-trips throughmetadata.tomlcorrectly.#[serde(default, skip_serializing_if = "Option::is_none")]onlast_componentsinSlideMetaEntryso legacymetadata.tomlfiles (nolast_componentskey) deserialize without error and fall back toNone.StaleSlidetype and all RPC response shapes must not change.#[allow(clippy::too_many_arguments)]oncompute_stale_reasonsmust be removed.hasher.update(b"")placeholder incompose_inputs_hashmust be removed.ComponentSnapshotsmust be re-exported fromlib.rs.Files to Modify
crates/hero_slides_lib/src/hashing.rs— addComponentSnapshots, replace four flatlast_*fields inSlideMetaEntry, remove deadhasher.update(b"")incompose_inputs_hashcrates/hero_slides_lib/src/deck.rs— updatecompute_stale_reasonsbody, update all fourSlideMetaEntryconstruction sites, remove#[allow(clippy::too_many_arguments)]crates/hero_slides_lib/src/lib.rs— addComponentSnapshotsto public re-exportsexamples/hero_slides_intro/metadata.toml— migrate flat keys to TOML subtable formatImplementation Plan
Step 1: Add
ComponentSnapshotsstruct tohashing.rsFile:
crates/hero_slides_lib/src/hashing.rsDeckInputsContext, insert a new struct:Dependencies: none
Step 2: Replace four flat fields in
SlideMetaEntrywithlast_componentsFile:
crates/hero_slides_lib/src/hashing.rslast_theme_hash,last_context_fingerprint,last_prompts_hash,last_image_modelfromSlideMetaEntry.#[serde(default, skip_serializing_if = "Option::is_none")] pub last_components: Option<ComponentSnapshots>,Dependencies: Step 1
Step 3: Remove dead
hasher.update(b"")incompose_inputs_hashFile:
crates/hero_slides_lib/src/hashing.rscompose_inputs_hash, remove the layout-hash placeholder lines (comment +hasher.update(b"")+hasher.update(b"\0")).Dependencies: Step 2
Step 4: Update
compute_stale_reasonsbody indeck.rsFile:
crates/hero_slides_lib/src/deck.rs#[allow(clippy::too_many_arguments)].m.last_theme_hash.as_deref()withm.last_components.as_ref().and_then(|c| c.theme_hash.as_deref())and similarly for the other three fields.Dependencies: Step 2
Step 5: Update four
SlideMetaEntryconstruction sites indeck.rsFile:
crates/hero_slides_lib/src/deck.rsAt each of the four sites that currently set four flat
last_*fields, replace with:(Adjust field sources per site — see Notes below for the linked-slide fast path.)
Dependencies: Step 2, Step 4
Step 6: Add
ComponentSnapshotstolib.rsre-exportsFile:
crates/hero_slides_lib/src/lib.rsComponentSnapshotsto the existinghashingre-export line alongsideSlideMetaEntry.Dependencies: Step 1
Step 7: Migrate
examples/hero_slides_intro/metadata.tomlFile:
examples/hero_slides_intro/metadata.tomllast_theme_hash,last_context_fingerprint,last_prompts_hashkeys under each slide section with the TOML subtable[slides.<id>.last_components].Dependencies: Step 2
Acceptance Criteria
SlideMetaEntryhas no fields namedlast_theme_hash,last_context_fingerprint,last_prompts_hash, orlast_image_modelComponentSnapshotsstruct exists inhashing.rswith the fourOption<String>fieldsSlideMetaEntryhaslast_components: Option<ComponentSnapshots>with correct serde attributescompose_inputs_hashhas nohasher.update(b"")placeholdercompute_stale_reasonshas no#[allow(clippy::too_many_arguments)]deck.rsconstruct aComponentSnapshotsvalueComponentSnapshotsis inlib.rsre-exportsexamples/hero_slides_intro/metadata.tomluses the TOML subtable formatcargo checkandcargo testpass with no new warningsStaleSlideRPC type and its JSON shape are unchangedNotes
SlideMetaEntry.image_model(the current per-slide model override) stays as a flat field — only the snapshot (last_image_model) moves intoComponentSnapshotsasimage_model.metadata.tomlfiles withoutlast_componentsdeserialize asNone, which falls through to the existing"inputs_changed"fallback — no migration code needed.hasher.update(b"")changes the composite hash formula, causing a one-time regeneration of all slides on next staleness check — this is deliberate and acceptable.slide_copy_to_deckusesSlideMetaEntry { hash, ..Default::default() }and requires no change.hero_slides_lib; no other crate references the flatlast_*fields.Test Results
All 111 unit tests, 6 integration tests, and 2 doc tests passed.
Also fixed two pre-existing test compilation errors in
deck.rs(test-onlyThemestruct literals missingsource_pdf_fileandsource_pdf_pagesfields added in a prior commit).Implementation Summary
All changes are in
crates/hero_slides_lib.Changes made
src/hashing.rsComponentSnapshotsstruct (derivesDebug,Default,Clone,Serialize,Deserialize) with fourOption<String>fields mirroringDeckInputsContext:theme_hash,context_fingerprint,prompts_hash,image_modelSlideMetaEntry(last_theme_hash,last_context_fingerprint,last_prompts_hash,last_image_model) with a singlelast_components: Option<ComponentSnapshots>field with#[serde(default, skip_serializing_if = "Option::is_none")]hasher.update(b"")layout-hash placeholder and its associated comment fromcompose_inputs_hashsrc/deck.rs#[allow(clippy::too_many_arguments)]fromcompute_stale_reasonscompute_stale_reasonsbody to read staleness attribution fromm.last_componentsinstead of four flat fieldsSlideMetaEntryconstruction sites (indeck_generate,slide_generate_with_selection,slide_generate_with_context, and the linked-slide fast path) to writelast_components: Some(ComponentSnapshots { ... })instead of four flat fieldsThemestruct fields)src/lib.rsComponentSnapshotsto the public re-export listexamples/hero_slides_intro/metadata.tomllast_theme_hash,last_context_fingerprint,last_prompts_hashkeys to the TOML subtable format[slides.05_themes.last_components]Acceptance criteria
SlideMetaEntryhas no flatlast_*fieldsComponentSnapshotsexists with fourOption<String>fieldscompute_stale_reasonshas no#[allow(clippy::too_many_arguments)]ComponentSnapshotsvalueComponentSnapshotsis publicly exportedcompose_inputs_hashhas no dead placeholdermetadata.tomluses TOML subtable formatStaleSlideRPC type and JSON shape unchanged