← back to Crazy News Channel Shadowman
Stamp lastUpdated at state-entry, not in render; clear grid at zero stories
de89fd44dbcb477612246cbba77ecb3a3258cfa3 · 2026-09-23 18:42:58 -0700 · Steve Abrams
Cody gate on e1d52f1 reproduced that the in-template lastUpdated assignment
ran after persistStories(), so Reset to Defaults saved lastUpdated:null and a
reload showed a new time. Now stampLastUpdated() runs at load and reset before
anything persists, templates are read-only, and init persists once so a fresh
load's stamps survive reload. Also patchStoryGrid([]) on the zero-stories path
so no orphan card lingers (pre-existing). Verified: reset+reload time stable,
0 grid children after deleting all, race/Cody/hash regressions still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH
Files touched
Diff
commit de89fd44dbcb477612246cbba77ecb3a3258cfa3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 23 18:42:58 2026 -0700
Stamp lastUpdated at state-entry, not in render; clear grid at zero stories
Cody gate on e1d52f1 reproduced that the in-template lastUpdated assignment
ran after persistStories(), so Reset to Defaults saved lastUpdated:null and a
reload showed a new time. Now stampLastUpdated() runs at load and reset before
anything persists, templates are read-only, and init persists once so a fresh
load's stamps survive reload. Also patchStoryGrid([]) on the zero-stories path
so no orphan card lingers (pre-existing). Verified: reset+reload time stable,
0 grid children after deleting all, race/Cody/hash regressions still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH
---
index.html | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/index.html b/index.html
index c4ee823..66660c8 100644
--- a/index.html
+++ b/index.html
@@ -1134,7 +1134,15 @@ function persistStories() {
// `let`, not `const` — deleteStory()/resetToDefaults() reassign the whole
// array (filter / clone). All readers (tick, renderStories, buildTickerItems)
// reference the outer STORIES binding at call-time, so this is safe.
-let STORIES = loadStories();
+// Every story carries a real lastUpdated from the moment it enters state, so
+// render stays read-only and whatever gets persisted matches what's shown.
+function stampLastUpdated(list) {
+ const now = new Date().toLocaleTimeString();
+ list.forEach((s) => { if (!s.lastUpdated) s.lastUpdated = now; });
+ return list;
+}
+
+let STORIES = stampLastUpdated(loadStories());
/* ---------- B. State ---------- */
const state = {
@@ -1238,7 +1246,7 @@ function storyCardHTML(story, isLead) {
<p class="story-meta">
<span>📍 ${escapeHTML(story.location)}</span>
<span>🖋 ${escapeHTML(story.byline)}</span>
- <span>🕒 Updated ${story.lastUpdated || (story.lastUpdated = new Date().toLocaleTimeString())}</span>
+ <span>🕒 Updated ${escapeHTML(story.lastUpdated || "—")}</span>
</p>
<a class="story-read-more" href="#/article/${escapeHTML(story.id)}" data-open-id="${escapeHTML(story.id)}">Read full article →</a>
</article>
@@ -1299,6 +1307,7 @@ function renderStories() {
// didn't exist before the admin panel could delete the seeded 3) — this
// is distinct from the per-category empty state below.
if (STORIES.length === 0) {
+ patchStoryGrid([]);
els.storyGrid.hidden = true;
els.emptyState.hidden = true;
els.meltdownBanner.hidden = true;
@@ -1668,7 +1677,7 @@ function deleteStory(id) {
}
function resetToDefaults() {
- STORIES = cloneStories(DEFAULT_STORIES);
+ STORIES = stampLastUpdated(cloneStories(DEFAULT_STORIES));
state.meltdownShown = false;
state.justCreatedId = null;
refreshAfterStoriesChanged();
@@ -1836,7 +1845,7 @@ function buildLiveUpdateInnerHTML(story) {
const stage = story.stages[story.stageIndex];
const concluded = story.stageIndex === story.stages.length - 1;
return `
- <span class="live-update-label">🔴 Live Update — ${escapeHTML(story.lastUpdated || (story.lastUpdated = new Date().toLocaleTimeString()))}</span>
+ <span class="live-update-label">🔴 Live Update — ${escapeHTML(story.lastUpdated || "—")}</span>
<p class="live-update-headline">${escapeHTML(stage.headline)}</p>
<p class="live-update-detail">${escapeHTML(stage.detail)}</p>
${concluded ? `<p class="live-update-concluded"><strong>STORY CONCLUDED</strong> — ${escapeHTML(stage.conclusion || "")}</p>` : ""}
@@ -2014,6 +2023,7 @@ function init() {
updateMoodUI();
setTickerMode(state.reducedMotion);
refreshTicker();
+ persistStories(); // lock in the load-time lastUpdated stamps so a reload shows the same times
// Start the 1000ms tick loop BEFORE rendering route, so malformed hashes
// can't abort init() and freeze the clock/escalation forever
setInterval(tick, 1000);
← e1d52f1 Fix story-grid click/focus race: keyed in-place patch + stab
·
back to Crazy News Channel Shadowman
·
Give admin-created stories 8 distinct escalation arcs instea e4c7519 →