Improve slide context UX #64

Open
opened 2026-05-13 15:18:11 +00:00 by casper-stevens · 3 comments
Member

The context system works correctly under the hood but the UX makes it hard to understand and use.

Problems

1. Global context is read-only in the slide editor
The slide editor shows global context as non-interactive chips. To change global context you have to navigate back to the dashboard. There should be an edit button inline.

2. Three separate surfaces for one concept
Global context = dashboard toolbar popover. Per-slide context = slide editor sidebar. Per-slide context from dashboard = a separate modal. All three should behave consistently.

3. Global vs per-slide inheritance is invisible
In the per-slide file picker, files inherited from a globally-selected folder appear pre-checked — indistinguishable from files explicitly selected per-slide. Users cannot tell what they are overriding vs inheriting.

4. localStorage with 24h TTL silently wipes context
Context expires after 24 hours with no warning. Working on a deck across multiple days means context resets silently and slides regenerate with no context.

5. No inline context summary
You only see what context is active by clicking "Preview effective context" and waiting for an RPC call. There should be a live summary (e.g. "4 files · 18KB") always visible in the panel.

6. Context changes do not mark slides as stale
Changing the context selection does not flag existing slides as needing regeneration. Slides generated under different context silently coexist with no indicator.

7. Context indicator button is hard to find
The ctx-indicator-btn starts hidden (d-none), appears only after deck selection, and is a small unlabeled button in a crowded toolbar.

Suggested fixes

  • Add edit affordance on the Global chips row in the slide editor (same popover as dashboard)
  • Show inline byte/file count in the context panel header at all times
  • Visually distinguish inherited-from-global checkboxes from explicitly-set per-slide ones
  • Remove the 24h TTL, or persist context server-side per deck
  • Show a stale indicator on slide cards when context selection has changed since last generation
  • Make the context indicator button more prominent and always visible when a deck is loaded
The context system works correctly under the hood but the UX makes it hard to understand and use. ## Problems **1. Global context is read-only in the slide editor** The slide editor shows global context as non-interactive chips. To change global context you have to navigate back to the dashboard. There should be an edit button inline. **2. Three separate surfaces for one concept** Global context = dashboard toolbar popover. Per-slide context = slide editor sidebar. Per-slide context from dashboard = a separate modal. All three should behave consistently. **3. Global vs per-slide inheritance is invisible** In the per-slide file picker, files inherited from a globally-selected folder appear pre-checked — indistinguishable from files explicitly selected per-slide. Users cannot tell what they are overriding vs inheriting. **4. localStorage with 24h TTL silently wipes context** Context expires after 24 hours with no warning. Working on a deck across multiple days means context resets silently and slides regenerate with no context. **5. No inline context summary** You only see what context is active by clicking "Preview effective context" and waiting for an RPC call. There should be a live summary (e.g. "4 files · 18KB") always visible in the panel. **6. Context changes do not mark slides as stale** Changing the context selection does not flag existing slides as needing regeneration. Slides generated under different context silently coexist with no indicator. **7. Context indicator button is hard to find** The `ctx-indicator-btn` starts hidden (`d-none`), appears only after deck selection, and is a small unlabeled button in a crowded toolbar. ## Suggested fixes - Add edit affordance on the Global chips row in the slide editor (same popover as dashboard) - Show inline byte/file count in the context panel header at all times - Visually distinguish inherited-from-global checkboxes from explicitly-set per-slide ones - Remove the 24h TTL, or persist context server-side per deck - Show a stale indicator on slide cards when context selection has changed since last generation - Make the context indicator button more prominent and always visible when a deck is loaded
Author
Member

Implementation Spec for Issue #64

Objective

Fix seven identified UX problems in the context selection system across the dashboard and slide editor. All fixes are purely frontend (JS/HTML/CSS). No new RPC endpoints are required.

Architecture Context

The context system has three JavaScript surfaces sharing one localStorage key per deck:

Surface File Entry point
Dashboard toolbar — global context dashboard.js ctx-indicator-btntoggleContextPopover()
Dashboard — per-slide overlay dashboard.js Right-click slide card → openSlideCtxModal()
Slide editor right panel slide_edit.js renderContextPanel()

The localStorage key is hero_slides_context_selection_v1__<base64(collection::deck)>. Staleness is computed server-side by slide_staleness() in hero_slides_lib via a fingerprint of all files under content/background/ — not the UI selection. This is the root cause of problem 6.

Live bug found: slide_edit.js reads e.bytes_len but the RPC returns byte_count, causing "0 B" in the context preview modal.

Requirements

  • Add an inline edit affordance on the Global chips row inside the slide editor to open a context popover without navigating back to the dashboard
  • Show an always-visible file count in the slide editor context panel header, computed without an RPC call
  • Visually distinguish inherited-from-global checkboxes in the per-slide file picker from explicitly per-slide-selected ones
  • Extend the localStorage TTL from 24 hours to 365 days so context survives multi-day editing sessions
  • Mark slide cards as "context changed" (amber badge) when the global context selection changes in the same session
  • Make the context indicator button visible as soon as any deck is selected, even if the deck has no background files
  • Fix the bytes_len vs byte_count field name mismatch in the context preview modal

Files to Modify

File Role
crates/hero_slides_admin/static/js/slide_edit.js Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL extension, bytes_len fix
crates/hero_slides_admin/static/js/dashboard.js Indicator visibility fix, context-change staleness badge, TTL extension
crates/hero_slides_admin/templates/slide_edit.html Add edit button element to global chips row, inline summary span, global context popover HTML
crates/hero_slides_admin/templates/index.html Remove d-none from ctx-indicator-btn initial state
crates/hero_slides_admin/static/css/slide_edit.css Inherited checkbox styling, inline summary styling
crates/hero_slides_admin/static/css/dashboard.css Context-changed badge, disabled indicator button style

Implementation Plan

Step 1 — Fix bytes_len field name in context preview modal

Files: slide_edit.js

  • Line 636: change e.bytes_len || 0 to e.byte_count || 0
  • Line 670: change formatBytes(e.bytes_len) to formatBytes(e.byte_count)

Dependencies: none

Step 2 — Extend localStorage TTL from 24h to 365 days

Files: dashboard.js (line 1926), slide_edit.js (line 247)

  • Change 24 * 60 * 60 * 1000 to 365 * 24 * 60 * 60 * 1000 in both files

Dependencies: none

Step 3 — Add inline context summary to the slide editor panel header

Files: slide_edit.html, slide_edit.js, slide_edit.css

  • Add <span id="ctx-summary-inline" class="ctx-summary-inline"></span> inside the #context-panel heading row in slide_edit.html
  • Add updateInlineContextSummary(ctx) function in slide_edit.js that counts selected folders and root files, writes count to #ctx-summary-inline
  • Call updateInlineContextSummary(ctx) at the bottom of renderContextPanel()
  • Add .ctx-summary-inline CSS: small, muted, right-aligned

Dependencies: none

Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker

Files: slide_edit.js, slide_edit.css

  • In renderSlidePicker(), compute inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)
  • Add class ctx-picker-item-inherited to folder rows that are inherited from global
  • Add class ctx-file-item-inherited to file rows whose folder is inherited-from-global but not explicitly per-slide selected
  • Add a small <span class="ctx-inherited-badge">global</span> badge next to inherited folder names
  • CSS: .ctx-file-item-inherited { opacity: 0.65; }, .ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }

Dependencies: none

Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor

Files: slide_edit.html, slide_edit.js

  • In slide_edit.html, add a <button id="btn-ctx-edit-global"> next to the Global chips subsection header
  • In slide_edit.html, add a <div id="ctx-global-popover" class="ctx-popover d-none"> near the end of body
  • In slide_edit.js, add renderGlobalContextPopover(), openGlobalContextPopover(), closeGlobalContextPopover(), toggleGlobalFolderFromEditor(), toggleGlobalRootFileFromEditor() functions
  • Wire btn-ctx-edit-global click in bind() to openGlobalContextPopover()
  • The popover renders availableFolders and availableRootFiles with checkboxes; on toggle calls writeContextSelection(ctx) and re-renders chips and summary

Dependencies: Step 3 (summary must update when popover changes)

Step 6 — Mark slides as context-stale when global context selection changes

Files: dashboard.js, dashboard.css

  • Add module-level let _ctxSelectionVersion = 0
  • In _ctxSaveImmediate(): include selection_version: _ctxSelectionVersion in saved object
  • In applyStoredContextSelection(): restore _ctxSelectionVersion from saved object
  • In onContextSelectionChanged(): call new function markSlidesContextChanged()
  • markSlidesContextChanged(): for each slide card with has_png: true that is not currently generating, add CSS class ctx-changed. Clear it for a specific slide on successful regeneration.
  • CSS: .slide-card.ctx-changed::after { content: 'Context changed'; position: absolute; bottom: 28px; left: 4px; font-size: 0.62rem; background: color-mix(in srgb, var(--accent-orange) 80%, transparent); color: #fff; padding: 1px 6px; border-radius: 3px; pointer-events: none; }

Dependencies: none

Step 7 — Make context indicator button always visible when a deck is loaded

Files: index.html, dashboard.js, dashboard.css

  • Remove d-none from ctx-indicator-btn in index.html initial markup
  • In updateContextIndicator() in dashboard.js: hide button only when !selectedDeckName; show in disabled/muted state with label "No context files" when deck has no background; existing logic otherwise
  • CSS: .ctx-indicator:disabled { opacity: 0.5; cursor: default; }

Dependencies: none

Acceptance Criteria

  • The context panel header shows a live file count that updates immediately when checkboxes change, without clicking "Preview effective context"
  • In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and visually dimmed checkbox
  • Context selection persists across browser sessions lasting more than 24 hours
  • After changing the global context selection, slide cards that already have a generated PNG show a small amber "Context changed" badge
  • The context indicator button is visible and readable as soon as any deck is selected; it shows "No context files" when the deck has no background files
  • The "Preview effective context" modal correctly shows file sizes (no longer shows "0 B")
  • Opening the slide editor shows an edit button next to the Global chips that opens a popover to change global context

Notes

  1. bytes_len vs byte_count (Step 1) is a live bug — quickest win
  2. Step 5 (global popover in slide editor) is the most complex — availableFolders/availableRootFiles must be populated by loadBackgroundInventory() before the popover opens
  3. Step 4 must not disable inherited checkboxes — they should remain interactive so the user can create explicit per-slide overrides
  4. Step 6 is session-only — the badge disappears on page reload (acceptable for now)
  5. slide_edit.html already includes dashboard.css, so .ctx-popover styles are available for Step 5
## Implementation Spec for Issue #64 ### Objective Fix seven identified UX problems in the context selection system across the dashboard and slide editor. All fixes are purely frontend (JS/HTML/CSS). No new RPC endpoints are required. ### Architecture Context The context system has three JavaScript surfaces sharing one `localStorage` key per deck: | Surface | File | Entry point | |---|---|---| | Dashboard toolbar — global context | `dashboard.js` | `ctx-indicator-btn` → `toggleContextPopover()` | | Dashboard — per-slide overlay | `dashboard.js` | Right-click slide card → `openSlideCtxModal()` | | Slide editor right panel | `slide_edit.js` | `renderContextPanel()` | The `localStorage` key is `hero_slides_context_selection_v1__<base64(collection::deck)>`. Staleness is computed server-side by `slide_staleness()` in `hero_slides_lib` via a fingerprint of **all files under `content/background/`** — not the UI selection. This is the root cause of problem 6. Live bug found: `slide_edit.js` reads `e.bytes_len` but the RPC returns `byte_count`, causing "0 B" in the context preview modal. ### Requirements - Add an inline edit affordance on the Global chips row inside the slide editor to open a context popover without navigating back to the dashboard - Show an always-visible file count in the slide editor context panel header, computed without an RPC call - Visually distinguish inherited-from-global checkboxes in the per-slide file picker from explicitly per-slide-selected ones - Extend the localStorage TTL from 24 hours to 365 days so context survives multi-day editing sessions - Mark slide cards as "context changed" (amber badge) when the global context selection changes in the same session - Make the context indicator button visible as soon as any deck is selected, even if the deck has no background files - Fix the `bytes_len` vs `byte_count` field name mismatch in the context preview modal ### Files to Modify | File | Role | |---|---| | `crates/hero_slides_admin/static/js/slide_edit.js` | Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL extension, bytes_len fix | | `crates/hero_slides_admin/static/js/dashboard.js` | Indicator visibility fix, context-change staleness badge, TTL extension | | `crates/hero_slides_admin/templates/slide_edit.html` | Add edit button element to global chips row, inline summary span, global context popover HTML | | `crates/hero_slides_admin/templates/index.html` | Remove `d-none` from `ctx-indicator-btn` initial state | | `crates/hero_slides_admin/static/css/slide_edit.css` | Inherited checkbox styling, inline summary styling | | `crates/hero_slides_admin/static/css/dashboard.css` | Context-changed badge, disabled indicator button style | ### Implementation Plan #### Step 1 — Fix `bytes_len` field name in context preview modal **Files:** `slide_edit.js` - Line 636: change `e.bytes_len || 0` to `e.byte_count || 0` - Line 670: change `formatBytes(e.bytes_len)` to `formatBytes(e.byte_count)` **Dependencies:** none #### Step 2 — Extend localStorage TTL from 24h to 365 days **Files:** `dashboard.js` (line 1926), `slide_edit.js` (line 247) - Change `24 * 60 * 60 * 1000` to `365 * 24 * 60 * 60 * 1000` in both files **Dependencies:** none #### Step 3 — Add inline context summary to the slide editor panel header **Files:** `slide_edit.html`, `slide_edit.js`, `slide_edit.css` - Add `<span id="ctx-summary-inline" class="ctx-summary-inline"></span>` inside the `#context-panel` heading row in `slide_edit.html` - Add `updateInlineContextSummary(ctx)` function in `slide_edit.js` that counts selected folders and root files, writes count to `#ctx-summary-inline` - Call `updateInlineContextSummary(ctx)` at the bottom of `renderContextPanel()` - Add `.ctx-summary-inline` CSS: small, muted, right-aligned **Dependencies:** none #### Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker **Files:** `slide_edit.js`, `slide_edit.css` - In `renderSlidePicker()`, compute `inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)` - Add class `ctx-picker-item-inherited` to folder rows that are inherited from global - Add class `ctx-file-item-inherited` to file rows whose folder is inherited-from-global but not explicitly per-slide selected - Add a small `<span class="ctx-inherited-badge">global</span>` badge next to inherited folder names - CSS: `.ctx-file-item-inherited { opacity: 0.65; }`, `.ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }` **Dependencies:** none #### Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor **Files:** `slide_edit.html`, `slide_edit.js` - In `slide_edit.html`, add a `<button id="btn-ctx-edit-global">` next to the Global chips subsection header - In `slide_edit.html`, add a `<div id="ctx-global-popover" class="ctx-popover d-none">` near the end of body - In `slide_edit.js`, add `renderGlobalContextPopover()`, `openGlobalContextPopover()`, `closeGlobalContextPopover()`, `toggleGlobalFolderFromEditor()`, `toggleGlobalRootFileFromEditor()` functions - Wire `btn-ctx-edit-global` click in `bind()` to `openGlobalContextPopover()` - The popover renders `availableFolders` and `availableRootFiles` with checkboxes; on toggle calls `writeContextSelection(ctx)` and re-renders chips and summary **Dependencies:** Step 3 (summary must update when popover changes) #### Step 6 — Mark slides as context-stale when global context selection changes **Files:** `dashboard.js`, `dashboard.css` - Add module-level `let _ctxSelectionVersion = 0` - In `_ctxSaveImmediate()`: include `selection_version: _ctxSelectionVersion` in saved object - In `applyStoredContextSelection()`: restore `_ctxSelectionVersion` from saved object - In `onContextSelectionChanged()`: call new function `markSlidesContextChanged()` - `markSlidesContextChanged()`: for each slide card with `has_png: true` that is not currently generating, add CSS class `ctx-changed`. Clear it for a specific slide on successful regeneration. - CSS: `.slide-card.ctx-changed::after { content: 'Context changed'; position: absolute; bottom: 28px; left: 4px; font-size: 0.62rem; background: color-mix(in srgb, var(--accent-orange) 80%, transparent); color: #fff; padding: 1px 6px; border-radius: 3px; pointer-events: none; }` **Dependencies:** none #### Step 7 — Make context indicator button always visible when a deck is loaded **Files:** `index.html`, `dashboard.js`, `dashboard.css` - Remove `d-none` from `ctx-indicator-btn` in `index.html` initial markup - In `updateContextIndicator()` in `dashboard.js`: hide button only when `!selectedDeckName`; show in disabled/muted state with label "No context files" when deck has no background; existing logic otherwise - CSS: `.ctx-indicator:disabled { opacity: 0.5; cursor: default; }` **Dependencies:** none ### Acceptance Criteria - [ ] The context panel header shows a live file count that updates immediately when checkboxes change, without clicking "Preview effective context" - [ ] In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and visually dimmed checkbox - [ ] Context selection persists across browser sessions lasting more than 24 hours - [ ] After changing the global context selection, slide cards that already have a generated PNG show a small amber "Context changed" badge - [ ] The context indicator button is visible and readable as soon as any deck is selected; it shows "No context files" when the deck has no background files - [ ] The "Preview effective context" modal correctly shows file sizes (no longer shows "0 B") - [ ] Opening the slide editor shows an edit button next to the Global chips that opens a popover to change global context ### Notes 1. `bytes_len` vs `byte_count` (Step 1) is a live bug — quickest win 2. Step 5 (global popover in slide editor) is the most complex — `availableFolders`/`availableRootFiles` must be populated by `loadBackgroundInventory()` before the popover opens 3. Step 4 must not disable inherited checkboxes — they should remain interactive so the user can create explicit per-slide overrides 4. Step 6 is session-only — the badge disappears on page reload (acceptable for now) 5. `slide_edit.html` already includes `dashboard.css`, so `.ctx-popover` styles are available for Step 5
Author
Member

Updated Implementation Spec (revision 2)

Two adjustments from the previous spec:

Step 2 — TTL: Remove the TTL entirely rather than extending it. localStorage has no browser-enforced expiry, so we simply stop writing expires_at and stop checking it on load in both dashboard.js and slide_edit.js. Abandoned deck entries accumulate but are negligible in practice.

Step 6 — Context changed badge: Integrate into the existing Stale badge rather than adding a second badge. When context selection changes in the current session, slides that already have a PNG get a client-side flag. In slideStatusHtml(), if that flag is set, the Stale badge appears (or if already stale, "context selection changed" is appended to its tooltip reasons). No second chip — consistent with the existing stale pattern.


Implementation Spec for Issue #64 (revision 2)

Objective

Fix seven identified UX problems in the context selection system across the dashboard and slide editor. All fixes are purely frontend (JS/HTML/CSS). No new RPC endpoints are required.

Files to Modify

File Role
crates/hero_slides_admin/static/js/slide_edit.js Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL removal
crates/hero_slides_admin/static/js/dashboard.js Indicator visibility fix, context-change staleness integration, TTL removal
crates/hero_slides_admin/templates/slide_edit.html Add edit button, inline summary span, global context popover HTML
crates/hero_slides_admin/templates/index.html Remove d-none from ctx-indicator-btn initial state
crates/hero_slides_admin/static/css/slide_edit.css Inherited checkbox styling, inline summary styling
crates/hero_slides_admin/static/css/dashboard.css Disabled indicator button style

Implementation Steps

Step 1 — Fix bytes_len field name in context preview modal

Files: slide_edit.js

  • Line 636: e.bytes_len || 0e.byte_count || 0
  • Line 670: formatBytes(e.bytes_len)formatBytes(e.byte_count)

Dependencies: none

Step 2 — Remove localStorage TTL entirely

Files: dashboard.js, slide_edit.js

  • Stop writing expires_at in _ctxSaveImmediate() in both files
  • Stop checking expires_at in _ctxLoad() in both files
  • Context persists indefinitely (until user clears browser storage)

Dependencies: none

Step 3 — Add inline context summary to the slide editor panel header

Files: slide_edit.html, slide_edit.js, slide_edit.css

  • Add <span id="ctx-summary-inline" class="ctx-summary-inline"></span> inside the #context-panel heading row
  • Add updateInlineContextSummary(ctx) in slide_edit.js that counts selected folders and root files, writes result to #ctx-summary-inline
  • Call it at the bottom of renderContextPanel()
  • CSS: small, muted, right-aligned

Dependencies: none

Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker

Files: slide_edit.js, slide_edit.css

  • In renderSlidePicker(), compute inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)
  • Add class ctx-picker-item-inherited to inherited folder rows
  • Add class ctx-file-item-inherited to file rows whose folder is inherited-from-global but not explicitly per-slide
  • Add <span class="ctx-inherited-badge">global</span> badge next to inherited folder names
  • CSS: .ctx-file-item-inherited { opacity: 0.65; } / .ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }
  • Inherited checkboxes remain interactive (clicking creates an explicit per-slide override)

Dependencies: none

Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor

Files: slide_edit.html, slide_edit.js

  • Add <button id="btn-ctx-edit-global"> next to the Global chips subsection header in slide_edit.html
  • Add <div id="ctx-global-popover" class="ctx-popover d-none"> near end of body
  • In slide_edit.js: add renderGlobalContextPopover(), openGlobalContextPopover(), closeGlobalContextPopover(), toggleGlobalFolderFromEditor(), toggleGlobalRootFileFromEditor()
  • Wire btn-ctx-edit-global click in bind() to openGlobalContextPopover()
  • Popover renders availableFolders and availableRootFiles with checkboxes; on toggle calls writeContextSelection(ctx) and re-renders chips and inline summary
  • slide_edit.html already includes dashboard.css, so .ctx-popover styles are available

Dependencies: Step 3 (summary must update when popover changes)

Step 6 — Integrate context selection changes into the Stale badge

Files: dashboard.js

  • Add module-level let _ctxChangedSlides = new Set() to track which slides had context changed this session
  • In onContextSelectionChanged(): call markSlidesContextChanged() which adds all slides with has_png: true and not currently generating to _ctxChangedSlides
  • On successful slide regeneration: remove that slide from _ctxChangedSlides
  • In slideStatusHtml(s): if _ctxChangedSlides.has(s.name):
    • If s.is_stale: append "context selection changed" to staleReasons before rendering the tooltip
    • If !s.is_stale && s.has_png: render the Stale badge with title="Stale: context selection changed"
  • No new CSS class needed — reuses existing state-badge-stale

Dependencies: none

Step 7 — Make context indicator button always visible when a deck is loaded

Files: index.html, dashboard.js, dashboard.css

  • Remove d-none from ctx-indicator-btn in index.html
  • In updateContextIndicator() in dashboard.js: hide button only when !selectedDeckName; show disabled/muted with label "No context files" when deck has no background; existing logic otherwise
  • CSS: .ctx-indicator:disabled { opacity: 0.5; cursor: default; }

Dependencies: none

Acceptance Criteria

  • The context panel header shows a live file count that updates immediately when checkboxes change
  • In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and dimmed checkbox
  • Context selection persists indefinitely (no TTL)
  • After changing global context selection, slides with a PNG show a Stale badge with "context selection changed" in the tooltip
  • The context indicator button is visible as soon as any deck is selected; shows "No context files" when deck has no background files
  • The "Preview effective context" modal correctly shows file sizes (no longer shows "0 B")
  • Opening the slide editor shows an edit button next to the Global chips that opens a context popover

Notes

  1. bytes_len vs byte_count (Step 1) is a live bug — quickest win
  2. Step 5 (global popover in slide editor) is the most complex — availableFolders/availableRootFiles must be populated by loadBackgroundInventory() before the popover opens
  3. Step 4 must not disable inherited checkboxes — they remain interactive so users can create explicit per-slide overrides
  4. Step 6 is session-only — _ctxChangedSlides is a module-level variable, cleared on page reload
## Updated Implementation Spec (revision 2) Two adjustments from the previous spec: **Step 2 — TTL:** Remove the TTL entirely rather than extending it. `localStorage` has no browser-enforced expiry, so we simply stop writing `expires_at` and stop checking it on load in both `dashboard.js` and `slide_edit.js`. Abandoned deck entries accumulate but are negligible in practice. **Step 6 — Context changed badge:** Integrate into the existing Stale badge rather than adding a second badge. When context selection changes in the current session, slides that already have a PNG get a client-side flag. In `slideStatusHtml()`, if that flag is set, the Stale badge appears (or if already stale, "context selection changed" is appended to its tooltip reasons). No second chip — consistent with the existing stale pattern. --- ## Implementation Spec for Issue #64 (revision 2) ### Objective Fix seven identified UX problems in the context selection system across the dashboard and slide editor. All fixes are purely frontend (JS/HTML/CSS). No new RPC endpoints are required. ### Files to Modify | File | Role | |---|---| | `crates/hero_slides_admin/static/js/slide_edit.js` | Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL removal | | `crates/hero_slides_admin/static/js/dashboard.js` | Indicator visibility fix, context-change staleness integration, TTL removal | | `crates/hero_slides_admin/templates/slide_edit.html` | Add edit button, inline summary span, global context popover HTML | | `crates/hero_slides_admin/templates/index.html` | Remove `d-none` from `ctx-indicator-btn` initial state | | `crates/hero_slides_admin/static/css/slide_edit.css` | Inherited checkbox styling, inline summary styling | | `crates/hero_slides_admin/static/css/dashboard.css` | Disabled indicator button style | ### Implementation Steps #### Step 1 — Fix `bytes_len` field name in context preview modal **Files:** `slide_edit.js` - Line 636: `e.bytes_len || 0` → `e.byte_count || 0` - Line 670: `formatBytes(e.bytes_len)` → `formatBytes(e.byte_count)` **Dependencies:** none #### Step 2 — Remove localStorage TTL entirely **Files:** `dashboard.js`, `slide_edit.js` - Stop writing `expires_at` in `_ctxSaveImmediate()` in both files - Stop checking `expires_at` in `_ctxLoad()` in both files - Context persists indefinitely (until user clears browser storage) **Dependencies:** none #### Step 3 — Add inline context summary to the slide editor panel header **Files:** `slide_edit.html`, `slide_edit.js`, `slide_edit.css` - Add `<span id="ctx-summary-inline" class="ctx-summary-inline"></span>` inside the `#context-panel` heading row - Add `updateInlineContextSummary(ctx)` in `slide_edit.js` that counts selected folders and root files, writes result to `#ctx-summary-inline` - Call it at the bottom of `renderContextPanel()` - CSS: small, muted, right-aligned **Dependencies:** none #### Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker **Files:** `slide_edit.js`, `slide_edit.css` - In `renderSlidePicker()`, compute `inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)` - Add class `ctx-picker-item-inherited` to inherited folder rows - Add class `ctx-file-item-inherited` to file rows whose folder is inherited-from-global but not explicitly per-slide - Add `<span class="ctx-inherited-badge">global</span>` badge next to inherited folder names - CSS: `.ctx-file-item-inherited { opacity: 0.65; }` / `.ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }` - Inherited checkboxes remain interactive (clicking creates an explicit per-slide override) **Dependencies:** none #### Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor **Files:** `slide_edit.html`, `slide_edit.js` - Add `<button id="btn-ctx-edit-global">` next to the Global chips subsection header in `slide_edit.html` - Add `<div id="ctx-global-popover" class="ctx-popover d-none">` near end of body - In `slide_edit.js`: add `renderGlobalContextPopover()`, `openGlobalContextPopover()`, `closeGlobalContextPopover()`, `toggleGlobalFolderFromEditor()`, `toggleGlobalRootFileFromEditor()` - Wire `btn-ctx-edit-global` click in `bind()` to `openGlobalContextPopover()` - Popover renders `availableFolders` and `availableRootFiles` with checkboxes; on toggle calls `writeContextSelection(ctx)` and re-renders chips and inline summary - `slide_edit.html` already includes `dashboard.css`, so `.ctx-popover` styles are available **Dependencies:** Step 3 (summary must update when popover changes) #### Step 6 — Integrate context selection changes into the Stale badge **Files:** `dashboard.js` - Add module-level `let _ctxChangedSlides = new Set()` to track which slides had context changed this session - In `onContextSelectionChanged()`: call `markSlidesContextChanged()` which adds all slides with `has_png: true` and not currently generating to `_ctxChangedSlides` - On successful slide regeneration: remove that slide from `_ctxChangedSlides` - In `slideStatusHtml(s)`: if `_ctxChangedSlides.has(s.name)`: - If `s.is_stale`: append "context selection changed" to `staleReasons` before rendering the tooltip - If `!s.is_stale && s.has_png`: render the Stale badge with `title="Stale: context selection changed"` - No new CSS class needed — reuses existing `state-badge-stale` **Dependencies:** none #### Step 7 — Make context indicator button always visible when a deck is loaded **Files:** `index.html`, `dashboard.js`, `dashboard.css` - Remove `d-none` from `ctx-indicator-btn` in `index.html` - In `updateContextIndicator()` in `dashboard.js`: hide button only when `!selectedDeckName`; show disabled/muted with label "No context files" when deck has no background; existing logic otherwise - CSS: `.ctx-indicator:disabled { opacity: 0.5; cursor: default; }` **Dependencies:** none ### Acceptance Criteria - [ ] The context panel header shows a live file count that updates immediately when checkboxes change - [ ] In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and dimmed checkbox - [ ] Context selection persists indefinitely (no TTL) - [ ] After changing global context selection, slides with a PNG show a Stale badge with "context selection changed" in the tooltip - [ ] The context indicator button is visible as soon as any deck is selected; shows "No context files" when deck has no background files - [ ] The "Preview effective context" modal correctly shows file sizes (no longer shows "0 B") - [ ] Opening the slide editor shows an edit button next to the Global chips that opens a context popover ### Notes 1. `bytes_len` vs `byte_count` (Step 1) is a live bug — quickest win 2. Step 5 (global popover in slide editor) is the most complex — `availableFolders`/`availableRootFiles` must be populated by `loadBackgroundInventory()` before the popover opens 3. Step 4 must not disable inherited checkboxes — they remain interactive so users can create explicit per-slide overrides 4. Step 6 is session-only — `_ctxChangedSlides` is a module-level variable, cleared on page reload
Author
Member

Updated Implementation Spec (revision 3)

Key addition: All UX changes must be live — no page reload required. Every state change (context selection, context-changed flag, indicator button state, inline summary, inherited styling) must immediately update the affected DOM elements in-place.

This means:

  • Step 3 (inline summary): updateInlineContextSummary() is called every time any checkbox changes, not just on renderContextPanel() init
  • Step 4 (inherited styling): renderSlidePicker() is re-called on context change so inherited badges reflect the current global state
  • Step 5 (global context popover in slide editor): toggling a folder/file in the popover immediately updates the global chips row and the inline summary without re-rendering the whole panel
  • Step 6 (stale badge): markSlidesContextChanged() directly patches the .slide-card-status DOM element of each affected card in-place — no deck reload, no refreshCurrentDeck() call
  • Step 7 (indicator button): updateContextIndicator() already fires on context changes; ensure it also fires immediately on deck selection

Implementation Spec for Issue #64 (revision 3)

Objective

Fix seven UX problems in the context selection system. All fixes are purely frontend (JS/HTML/CSS). All changes are live — no page reload required for any state update.

Files to Modify

File Role
crates/hero_slides_admin/static/js/slide_edit.js Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL removal
crates/hero_slides_admin/static/js/dashboard.js Indicator visibility fix, context-change staleness integration, TTL removal
crates/hero_slides_admin/templates/slide_edit.html Add edit button, inline summary span, global context popover HTML
crates/hero_slides_admin/templates/index.html Remove d-none from ctx-indicator-btn initial state
crates/hero_slides_admin/static/css/slide_edit.css Inherited checkbox styling, inline summary styling
crates/hero_slides_admin/static/css/dashboard.css Disabled indicator button style

Implementation Steps

Step 1 — Fix bytes_len field name in context preview modal

Files: slide_edit.js

  • Line 636: e.bytes_len || 0e.byte_count || 0
  • Line 670: formatBytes(e.bytes_len)formatBytes(e.byte_count)

Dependencies: none

Step 2 — Remove localStorage TTL entirely

Files: dashboard.js, slide_edit.js

  • Stop writing expires_at in _ctxSaveImmediate() in both files
  • Stop checking expires_at in _ctxLoad() in both files
  • Context persists indefinitely (until user clears browser storage)

Dependencies: none

Step 3 — Add inline context summary to the slide editor panel header (live)

Files: slide_edit.html, slide_edit.js, slide_edit.css

  • Add <span id="ctx-summary-inline" class="ctx-summary-inline"></span> in the #context-panel heading row
  • Add updateInlineContextSummary(ctx) that counts selected folders and root files, writes to #ctx-summary-inline
  • Call from renderContextPanel() on init AND from every checkbox toggle handler so the count updates immediately without re-rendering the panel

Dependencies: none

Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker (live)

Files: slide_edit.js, slide_edit.css

  • In renderSlidePicker(), compute inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)
  • Add class ctx-picker-item-inherited to inherited folder rows; add <span class="ctx-inherited-badge">global</span> next to name
  • Add class ctx-file-item-inherited to file rows inherited from global but not explicitly per-slide
  • Ensure renderSlidePicker() is re-called whenever global context changes so inherited state reflects current selection live
  • Inherited checkboxes remain interactive (clicking creates an explicit per-slide override)
  • CSS: .ctx-file-item-inherited { opacity: 0.65; } / .ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }

Dependencies: none

Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor (live)

Files: slide_edit.html, slide_edit.js

  • Add <button id="btn-ctx-edit-global"> next to the Global chips subsection header
  • Add <div id="ctx-global-popover" class="ctx-popover d-none"> near end of body
  • In slide_edit.js: add renderGlobalContextPopover(), openGlobalContextPopover(), closeGlobalContextPopover(), toggleGlobalFolderFromEditor(), toggleGlobalRootFileFromEditor()
  • On toggle: call writeContextSelection(ctx), immediately re-render the global chips row in-place, update the inline summary, and re-render the slide picker if open — all without any page reload
  • Wire btn-ctx-edit-global click in bind() to openGlobalContextPopover()
  • slide_edit.html already includes dashboard.css, so .ctx-popover styles are available

Dependencies: Step 3 (summary must update when popover changes)

Step 6 — Integrate context selection changes into the Stale badge (live, in-place)

Files: dashboard.js

  • Add module-level let _ctxChangedSlides = new Set()
  • In onContextSelectionChanged(): call markSlidesContextChanged() which adds all slides with has_png: true and not generating to _ctxChangedSlides, then immediately patches the DOM for each affected card:
    • Find the card's .slide-card-status element by slide name
    • If already showing a Stale badge: append "context selection changed" to its title attribute
    • If not stale: inject a <span class="state-badge state-badge-stale" title="Stale: context selection changed">Stale</span> into the status element
    • No deck reload, no refreshCurrentDeck(), no full re-render — only the status span of each affected card is patched
  • On successful slide regeneration: remove that slide from _ctxChangedSlides and patch its status span back to normal
  • No new CSS — reuses existing state-badge-stale

Dependencies: none

Step 7 — Make context indicator button always visible when a deck is loaded (live)

Files: index.html, dashboard.js, dashboard.css

  • Remove d-none from ctx-indicator-btn in index.html
  • In updateContextIndicator(): hide only when !selectedDeckName; show disabled with "No context files" label when deck has no background; existing logic otherwise
  • updateContextIndicator() already fires on context changes and background inventory load — no additional wiring needed
  • CSS: .ctx-indicator:disabled { opacity: 0.5; cursor: default; }

Dependencies: none

Acceptance Criteria

  • All state changes are reflected in the UI immediately — no page reload required for any of the fixes
  • The context panel header shows a live file count that updates on every checkbox change
  • In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and dimmed checkbox, updating live when global context changes
  • Context selection persists indefinitely (no TTL)
  • After changing global context selection, affected slide cards immediately show a Stale badge with "context selection changed" in the tooltip — no reload needed
  • The context indicator button is visible as soon as any deck is selected; shows "No context files" when deck has no background files
  • The "Preview effective context" modal correctly shows file sizes (no longer "0 B")
  • Opening the slide editor shows an edit button next to the Global chips that opens a live context popover

Notes

  1. bytes_len vs byte_count (Step 1) is a live bug — quickest win
  2. Step 5 is the most complex — availableFolders/availableRootFiles must be populated before the popover opens
  3. Step 4 must not disable inherited checkboxes — they remain interactive
  4. Step 6 patches DOM in-place rather than triggering a full re-render — keep card element selection by data-slide attribute or equivalent
## Updated Implementation Spec (revision 3) **Key addition:** All UX changes must be live — no page reload required. Every state change (context selection, context-changed flag, indicator button state, inline summary, inherited styling) must immediately update the affected DOM elements in-place. This means: - Step 3 (inline summary): `updateInlineContextSummary()` is called every time any checkbox changes, not just on `renderContextPanel()` init - Step 4 (inherited styling): `renderSlidePicker()` is re-called on context change so inherited badges reflect the current global state - Step 5 (global context popover in slide editor): toggling a folder/file in the popover immediately updates the global chips row and the inline summary without re-rendering the whole panel - Step 6 (stale badge): `markSlidesContextChanged()` directly patches the `.slide-card-status` DOM element of each affected card in-place — no deck reload, no `refreshCurrentDeck()` call - Step 7 (indicator button): `updateContextIndicator()` already fires on context changes; ensure it also fires immediately on deck selection --- ## Implementation Spec for Issue #64 (revision 3) ### Objective Fix seven UX problems in the context selection system. All fixes are purely frontend (JS/HTML/CSS). All changes are live — no page reload required for any state update. ### Files to Modify | File | Role | |---|---| | `crates/hero_slides_admin/static/js/slide_edit.js` | Editor-side fixes: global chips edit button, inline summary, inherited checkbox styling, TTL removal | | `crates/hero_slides_admin/static/js/dashboard.js` | Indicator visibility fix, context-change staleness integration, TTL removal | | `crates/hero_slides_admin/templates/slide_edit.html` | Add edit button, inline summary span, global context popover HTML | | `crates/hero_slides_admin/templates/index.html` | Remove `d-none` from `ctx-indicator-btn` initial state | | `crates/hero_slides_admin/static/css/slide_edit.css` | Inherited checkbox styling, inline summary styling | | `crates/hero_slides_admin/static/css/dashboard.css` | Disabled indicator button style | ### Implementation Steps #### Step 1 — Fix `bytes_len` field name in context preview modal **Files:** `slide_edit.js` - Line 636: `e.bytes_len || 0` → `e.byte_count || 0` - Line 670: `formatBytes(e.bytes_len)` → `formatBytes(e.byte_count)` **Dependencies:** none #### Step 2 — Remove localStorage TTL entirely **Files:** `dashboard.js`, `slide_edit.js` - Stop writing `expires_at` in `_ctxSaveImmediate()` in both files - Stop checking `expires_at` in `_ctxLoad()` in both files - Context persists indefinitely (until user clears browser storage) **Dependencies:** none #### Step 3 — Add inline context summary to the slide editor panel header (live) **Files:** `slide_edit.html`, `slide_edit.js`, `slide_edit.css` - Add `<span id="ctx-summary-inline" class="ctx-summary-inline"></span>` in the `#context-panel` heading row - Add `updateInlineContextSummary(ctx)` that counts selected folders and root files, writes to `#ctx-summary-inline` - Call from `renderContextPanel()` on init AND from every checkbox toggle handler so the count updates immediately without re-rendering the panel **Dependencies:** none #### Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker (live) **Files:** `slide_edit.js`, `slide_edit.css` - In `renderSlidePicker()`, compute `inheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)` - Add class `ctx-picker-item-inherited` to inherited folder rows; add `<span class="ctx-inherited-badge">global</span>` next to name - Add class `ctx-file-item-inherited` to file rows inherited from global but not explicitly per-slide - Ensure `renderSlidePicker()` is re-called whenever global context changes so inherited state reflects current selection live - Inherited checkboxes remain interactive (clicking creates an explicit per-slide override) - CSS: `.ctx-file-item-inherited { opacity: 0.65; }` / `.ctx-inherited-badge { font-size: 0.6rem; opacity: 0.6; margin-left: 4px; font-style: italic; }` **Dependencies:** none #### Step 5 — Add "Edit Global Context" button to the global chips row in the slide editor (live) **Files:** `slide_edit.html`, `slide_edit.js` - Add `<button id="btn-ctx-edit-global">` next to the Global chips subsection header - Add `<div id="ctx-global-popover" class="ctx-popover d-none">` near end of body - In `slide_edit.js`: add `renderGlobalContextPopover()`, `openGlobalContextPopover()`, `closeGlobalContextPopover()`, `toggleGlobalFolderFromEditor()`, `toggleGlobalRootFileFromEditor()` - On toggle: call `writeContextSelection(ctx)`, immediately re-render the global chips row in-place, update the inline summary, and re-render the slide picker if open — all without any page reload - Wire `btn-ctx-edit-global` click in `bind()` to `openGlobalContextPopover()` - `slide_edit.html` already includes `dashboard.css`, so `.ctx-popover` styles are available **Dependencies:** Step 3 (summary must update when popover changes) #### Step 6 — Integrate context selection changes into the Stale badge (live, in-place) **Files:** `dashboard.js` - Add module-level `let _ctxChangedSlides = new Set()` - In `onContextSelectionChanged()`: call `markSlidesContextChanged()` which adds all slides with `has_png: true` and not generating to `_ctxChangedSlides`, then immediately patches the DOM for each affected card: - Find the card's `.slide-card-status` element by slide name - If already showing a Stale badge: append "context selection changed" to its `title` attribute - If not stale: inject a `<span class="state-badge state-badge-stale" title="Stale: context selection changed">Stale</span>` into the status element - No deck reload, no `refreshCurrentDeck()`, no full re-render — only the status span of each affected card is patched - On successful slide regeneration: remove that slide from `_ctxChangedSlides` and patch its status span back to normal - No new CSS — reuses existing `state-badge-stale` **Dependencies:** none #### Step 7 — Make context indicator button always visible when a deck is loaded (live) **Files:** `index.html`, `dashboard.js`, `dashboard.css` - Remove `d-none` from `ctx-indicator-btn` in `index.html` - In `updateContextIndicator()`: hide only when `!selectedDeckName`; show disabled with "No context files" label when deck has no background; existing logic otherwise - `updateContextIndicator()` already fires on context changes and background inventory load — no additional wiring needed - CSS: `.ctx-indicator:disabled { opacity: 0.5; cursor: default; }` **Dependencies:** none ### Acceptance Criteria - [ ] All state changes are reflected in the UI immediately — no page reload required for any of the fixes - [ ] The context panel header shows a live file count that updates on every checkbox change - [ ] In the per-slide file picker, files inherited from a globally-selected folder display with a muted "(global)" badge and dimmed checkbox, updating live when global context changes - [ ] Context selection persists indefinitely (no TTL) - [ ] After changing global context selection, affected slide cards immediately show a Stale badge with "context selection changed" in the tooltip — no reload needed - [ ] The context indicator button is visible as soon as any deck is selected; shows "No context files" when deck has no background files - [ ] The "Preview effective context" modal correctly shows file sizes (no longer "0 B") - [ ] Opening the slide editor shows an edit button next to the Global chips that opens a live context popover ### Notes 1. `bytes_len` vs `byte_count` (Step 1) is a live bug — quickest win 2. Step 5 is the most complex — `availableFolders`/`availableRootFiles` must be populated before the popover opens 3. Step 4 must not disable inherited checkboxes — they remain interactive 4. Step 6 patches DOM in-place rather than triggering a full re-render — keep card element selection by `data-slide` attribute or equivalent
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#64
No description provided.