Improve slide context UX #64
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?
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-btnstarts hidden (d-none), appears only after deck selection, and is a small unlabeled button in a crowded toolbar.Suggested fixes
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
localStoragekey per deck:dashboard.jsctx-indicator-btn→toggleContextPopover()dashboard.jsopenSlideCtxModal()slide_edit.jsrenderContextPanel()The
localStoragekey ishero_slides_context_selection_v1__<base64(collection::deck)>. Staleness is computed server-side byslide_staleness()inhero_slides_libvia a fingerprint of all files undercontent/background/— not the UI selection. This is the root cause of problem 6.Live bug found:
slide_edit.jsreadse.bytes_lenbut the RPC returnsbyte_count, causing "0 B" in the context preview modal.Requirements
bytes_lenvsbyte_countfield name mismatch in the context preview modalFiles to Modify
crates/hero_slides_admin/static/js/slide_edit.jscrates/hero_slides_admin/static/js/dashboard.jscrates/hero_slides_admin/templates/slide_edit.htmlcrates/hero_slides_admin/templates/index.htmld-nonefromctx-indicator-btninitial statecrates/hero_slides_admin/static/css/slide_edit.csscrates/hero_slides_admin/static/css/dashboard.cssImplementation Plan
Step 1 — Fix
bytes_lenfield name in context preview modalFiles:
slide_edit.jse.bytes_len || 0toe.byte_count || 0formatBytes(e.bytes_len)toformatBytes(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)24 * 60 * 60 * 1000to365 * 24 * 60 * 60 * 1000in both filesDependencies: none
Step 3 — Add inline context summary to the slide editor panel header
Files:
slide_edit.html,slide_edit.js,slide_edit.css<span id="ctx-summary-inline" class="ctx-summary-inline"></span>inside the#context-panelheading row inslide_edit.htmlupdateInlineContextSummary(ctx)function inslide_edit.jsthat counts selected folders and root files, writes count to#ctx-summary-inlineupdateInlineContextSummary(ctx)at the bottom ofrenderContextPanel().ctx-summary-inlineCSS: small, muted, right-alignedDependencies: none
Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker
Files:
slide_edit.js,slide_edit.cssrenderSlidePicker(), computeinheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)ctx-picker-item-inheritedto folder rows that are inherited from globalctx-file-item-inheritedto file rows whose folder is inherited-from-global but not explicitly per-slide selected<span class="ctx-inherited-badge">global</span>badge next to inherited folder names.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.jsslide_edit.html, add a<button id="btn-ctx-edit-global">next to the Global chips subsection headerslide_edit.html, add a<div id="ctx-global-popover" class="ctx-popover d-none">near the end of bodyslide_edit.js, addrenderGlobalContextPopover(),openGlobalContextPopover(),closeGlobalContextPopover(),toggleGlobalFolderFromEditor(),toggleGlobalRootFileFromEditor()functionsbtn-ctx-edit-globalclick inbind()toopenGlobalContextPopover()availableFoldersandavailableRootFileswith checkboxes; on toggle callswriteContextSelection(ctx)and re-renders chips and summaryDependencies: Step 3 (summary must update when popover changes)
Step 6 — Mark slides as context-stale when global context selection changes
Files:
dashboard.js,dashboard.csslet _ctxSelectionVersion = 0_ctxSaveImmediate(): includeselection_version: _ctxSelectionVersionin saved objectapplyStoredContextSelection(): restore_ctxSelectionVersionfrom saved objectonContextSelectionChanged(): call new functionmarkSlidesContextChanged()markSlidesContextChanged(): for each slide card withhas_png: truethat is not currently generating, add CSS classctx-changed. Clear it for a specific slide on successful regeneration..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.cssd-nonefromctx-indicator-btninindex.htmlinitial markupupdateContextIndicator()indashboard.js: hide button only when!selectedDeckName; show in disabled/muted state with label "No context files" when deck has no background; existing logic otherwise.ctx-indicator:disabled { opacity: 0.5; cursor: default; }Dependencies: none
Acceptance Criteria
Notes
bytes_lenvsbyte_count(Step 1) is a live bug — quickest winavailableFolders/availableRootFilesmust be populated byloadBackgroundInventory()before the popover opensslide_edit.htmlalready includesdashboard.css, so.ctx-popoverstyles are available for Step 5Updated Implementation Spec (revision 2)
Two adjustments from the previous spec:
Step 2 — TTL: Remove the TTL entirely rather than extending it.
localStoragehas no browser-enforced expiry, so we simply stop writingexpires_atand stop checking it on load in bothdashboard.jsandslide_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
crates/hero_slides_admin/static/js/slide_edit.jscrates/hero_slides_admin/static/js/dashboard.jscrates/hero_slides_admin/templates/slide_edit.htmlcrates/hero_slides_admin/templates/index.htmld-nonefromctx-indicator-btninitial statecrates/hero_slides_admin/static/css/slide_edit.csscrates/hero_slides_admin/static/css/dashboard.cssImplementation Steps
Step 1 — Fix
bytes_lenfield name in context preview modalFiles:
slide_edit.jse.bytes_len || 0→e.byte_count || 0formatBytes(e.bytes_len)→formatBytes(e.byte_count)Dependencies: none
Step 2 — Remove localStorage TTL entirely
Files:
dashboard.js,slide_edit.jsexpires_atin_ctxSaveImmediate()in both filesexpires_atin_ctxLoad()in both filesDependencies: none
Step 3 — Add inline context summary to the slide editor panel header
Files:
slide_edit.html,slide_edit.js,slide_edit.css<span id="ctx-summary-inline" class="ctx-summary-inline"></span>inside the#context-panelheading rowupdateInlineContextSummary(ctx)inslide_edit.jsthat counts selected folders and root files, writes result to#ctx-summary-inlinerenderContextPanel()Dependencies: none
Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker
Files:
slide_edit.js,slide_edit.cssrenderSlidePicker(), computeinheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)ctx-picker-item-inheritedto inherited folder rowsctx-file-item-inheritedto file rows whose folder is inherited-from-global but not explicitly per-slide<span class="ctx-inherited-badge">global</span>badge next to inherited folder names.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<button id="btn-ctx-edit-global">next to the Global chips subsection header inslide_edit.html<div id="ctx-global-popover" class="ctx-popover d-none">near end of bodyslide_edit.js: addrenderGlobalContextPopover(),openGlobalContextPopover(),closeGlobalContextPopover(),toggleGlobalFolderFromEditor(),toggleGlobalRootFileFromEditor()btn-ctx-edit-globalclick inbind()toopenGlobalContextPopover()availableFoldersandavailableRootFileswith checkboxes; on toggle callswriteContextSelection(ctx)and re-renders chips and inline summaryslide_edit.htmlalready includesdashboard.css, so.ctx-popoverstyles are availableDependencies: Step 3 (summary must update when popover changes)
Step 6 — Integrate context selection changes into the Stale badge
Files:
dashboard.jslet _ctxChangedSlides = new Set()to track which slides had context changed this sessiononContextSelectionChanged(): callmarkSlidesContextChanged()which adds all slides withhas_png: trueand not currently generating to_ctxChangedSlides_ctxChangedSlidesslideStatusHtml(s): if_ctxChangedSlides.has(s.name):s.is_stale: append "context selection changed" tostaleReasonsbefore rendering the tooltip!s.is_stale && s.has_png: render the Stale badge withtitle="Stale: context selection changed"state-badge-staleDependencies: none
Step 7 — Make context indicator button always visible when a deck is loaded
Files:
index.html,dashboard.js,dashboard.cssd-nonefromctx-indicator-btninindex.htmlupdateContextIndicator()indashboard.js: hide button only when!selectedDeckName; show disabled/muted with label "No context files" when deck has no background; existing logic otherwise.ctx-indicator:disabled { opacity: 0.5; cursor: default; }Dependencies: none
Acceptance Criteria
Notes
bytes_lenvsbyte_count(Step 1) is a live bug — quickest winavailableFolders/availableRootFilesmust be populated byloadBackgroundInventory()before the popover opens_ctxChangedSlidesis a module-level variable, cleared on page reloadUpdated 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:
updateInlineContextSummary()is called every time any checkbox changes, not just onrenderContextPanel()initrenderSlidePicker()is re-called on context change so inherited badges reflect the current global statemarkSlidesContextChanged()directly patches the.slide-card-statusDOM element of each affected card in-place — no deck reload, norefreshCurrentDeck()callupdateContextIndicator()already fires on context changes; ensure it also fires immediately on deck selectionImplementation 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
crates/hero_slides_admin/static/js/slide_edit.jscrates/hero_slides_admin/static/js/dashboard.jscrates/hero_slides_admin/templates/slide_edit.htmlcrates/hero_slides_admin/templates/index.htmld-nonefromctx-indicator-btninitial statecrates/hero_slides_admin/static/css/slide_edit.csscrates/hero_slides_admin/static/css/dashboard.cssImplementation Steps
Step 1 — Fix
bytes_lenfield name in context preview modalFiles:
slide_edit.jse.bytes_len || 0→e.byte_count || 0formatBytes(e.bytes_len)→formatBytes(e.byte_count)Dependencies: none
Step 2 — Remove localStorage TTL entirely
Files:
dashboard.js,slide_edit.jsexpires_atin_ctxSaveImmediate()in both filesexpires_atin_ctxLoad()in both filesDependencies: none
Step 3 — Add inline context summary to the slide editor panel header (live)
Files:
slide_edit.html,slide_edit.js,slide_edit.css<span id="ctx-summary-inline" class="ctx-summary-inline"></span>in the#context-panelheading rowupdateInlineContextSummary(ctx)that counts selected folders and root files, writes to#ctx-summary-inlinerenderContextPanel()on init AND from every checkbox toggle handler so the count updates immediately without re-rendering the panelDependencies: none
Step 4 — Visually distinguish inherited-from-global checkboxes in the per-slide picker (live)
Files:
slide_edit.js,slide_edit.cssrenderSlidePicker(), computeinheritedFromGlobal = (ctx.global.folders || []).includes(folder.name)ctx-picker-item-inheritedto inherited folder rows; add<span class="ctx-inherited-badge">global</span>next to namectx-file-item-inheritedto file rows inherited from global but not explicitly per-sliderenderSlidePicker()is re-called whenever global context changes so inherited state reflects current selection live.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<button id="btn-ctx-edit-global">next to the Global chips subsection header<div id="ctx-global-popover" class="ctx-popover d-none">near end of bodyslide_edit.js: addrenderGlobalContextPopover(),openGlobalContextPopover(),closeGlobalContextPopover(),toggleGlobalFolderFromEditor(),toggleGlobalRootFileFromEditor()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 reloadbtn-ctx-edit-globalclick inbind()toopenGlobalContextPopover()slide_edit.htmlalready includesdashboard.css, so.ctx-popoverstyles are availableDependencies: Step 3 (summary must update when popover changes)
Step 6 — Integrate context selection changes into the Stale badge (live, in-place)
Files:
dashboard.jslet _ctxChangedSlides = new Set()onContextSelectionChanged(): callmarkSlidesContextChanged()which adds all slides withhas_png: trueand not generating to_ctxChangedSlides, then immediately patches the DOM for each affected card:.slide-card-statuselement by slide nametitleattribute<span class="state-badge state-badge-stale" title="Stale: context selection changed">Stale</span>into the status elementrefreshCurrentDeck(), no full re-render — only the status span of each affected card is patched_ctxChangedSlidesand patch its status span back to normalstate-badge-staleDependencies: none
Step 7 — Make context indicator button always visible when a deck is loaded (live)
Files:
index.html,dashboard.js,dashboard.cssd-nonefromctx-indicator-btninindex.htmlupdateContextIndicator(): hide only when!selectedDeckName; show disabled with "No context files" label when deck has no background; existing logic otherwiseupdateContextIndicator()already fires on context changes and background inventory load — no additional wiring needed.ctx-indicator:disabled { opacity: 0.5; cursor: default; }Dependencies: none
Acceptance Criteria
Notes
bytes_lenvsbyte_count(Step 1) is a live bug — quickest winavailableFolders/availableRootFilesmust be populated before the popover opensdata-slideattribute or equivalent