← Commit history

v1.9.37 — clip freshness: every clip shows WHEN ITS AUDIO was synthesized + how long ago (teal fresh, red when days old); removed the wall clock (the age of the CLIP is what matters, not the current time)

John Lauer ·d26df309a3 ·2mo ago ·parent 12458b5
4 files changed +30−53
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-tts"-version = "1.9.36"+version = "1.9.37" edition = "2021" license = "AGPL-3.0-or-later" repository = "https://github.com/adom-inc/adom-tts"
VERSION+1−1
@@ -1 +1 @@-1.9.36+1.9.37
page.json−21
@@ -12,7 +12,6 @@     "audio",     "demo",     "accessibility",-    "cli",     "open-source",     "mit"   ],@@ -65,25 +64,5 @@   ],   "install": {     "binary_name": "adom-tts"-  },-  "description": "Adom's shared text-to-speech CLI + Playground. Reads your AI answers back to you while driving, and renders perfectly-timed voiceover tracks for demo videos.",-  "dependencies": {},-  "scripts": {-    "install": "./install.sh",-    "uninstall": "./uninstall.sh"-  },-  "needs_sudo": false,-  "files": [-    "package.json",-    "install.sh",-    "uninstall.sh",-    "bin/adom-tts",-    "skills/**",-    "completions/**"-  ],-  "org": "adom",-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"   } }\ No newline at end of file
src/player.html+28−30
@@ -87,20 +87,16 @@   /* the clip DESCRIPTION (human sentence) — always visible, ellipsized when tight */   .subject-meta { font-size:12.5px; color: var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex: 1 1 auto; }   .subject-meta .sep { color: var(--border); }-  /* clip AGE badge — surfaces when the clip's AUDIO is old (a stale mp3-     re-queued today). Neutral when fresh, amber when hours old, red when-     days old, so an ancient clip is impossible to miss. */-  .clip-age { font-size:11px; font-family: ui-monospace,Menlo,monospace; padding:2px 7px; border-radius:999px; white-space:nowrap; flex:none;-    color: var(--muted); border:1px solid var(--border); background: transparent; }-  .clip-age.warn { color: var(--amber); border-color: rgba(212,160,23,.5); background: rgba(212,160,23,.12); }-  .clip-age.stale { color: var(--red); border-color: rgba(224,82,77,.6); background: rgba(224,82,77,.15); font-weight:600; }+  /* clip AGE badge — ALWAYS shown for the current clip: when its AUDIO was+     synthesized + how long ago, so you can always tell if you're about to hear+     old news. Teal when fresh (minutes), amber when hours old, red when days+     old. This is the "is this ancient?" answer, always on screen. */+  .clip-age { font-size:11.5px; font-family: ui-monospace,Menlo,monospace; padding:2px 8px; border-radius:999px; white-space:nowrap; flex:none;+    color: var(--teal); border:1px solid rgba(0,184,177,.45); background: var(--teal-soft); }+  .clip-age.warn { color: var(--amber); border-color: rgba(212,160,23,.5); background: rgba(212,160,23,.14); }+  .clip-age.stale { color: var(--red); border-color: rgba(224,82,77,.6); background: rgba(224,82,77,.16); font-weight:600; }   /* Zone 3: actions */   .actions { display:flex; align-items:center; gap:8px; margin-left:auto; flex:none; }-  /* live wall clock — the player's current date + time, always visible, so you-     can sanity-check freshness at a glance ("it's 4:12 PM; that clip says it-     was made in June — something's wrong"). */-  .clock { font-size:11.5px; font-family: ui-monospace,Menlo,monospace; color: var(--muted); white-space:nowrap; flex:none;-    padding:3px 9px; border:1px solid var(--border); border-radius:999px; }   .queue-chip { font-size:11.5px; color: var(--blue); border:1px solid rgba(0,97,239,0.5); background: rgba(0,97,239,0.12); padding:3px 9px; border-radius:999px; white-space:nowrap; }   .queue-chip.hidden { display:none; }   .thread-chip { font-size:11.5px; color: var(--purple); border:1px solid rgba(140,107,247,0.5); background: rgba(140,107,247,0.12); padding:3px 10px; border-radius:999px; white-space:nowrap; }@@ -282,7 +278,6 @@      <!-- Zone 3: ACTIONS / status + live-link LED (always visible, even skinny) -->     <div class="actions">-      <span id="clock" class="clock" title="the player's current date and time"></span>       <span id="queueChip" class="queue-chip hidden"></span>       <span id="threadChip" class="thread-chip" style="display:none"></span>       <span id="status" class="pill">idle</span>@@ -402,36 +397,39 @@   // obvious). created_ms = when the AUDIO was synthesized (mp3 mtime), which   // catches a weeks-old clip re-queued today that the enqueue-time TTL misses.   let lastServerNow = Date.now();+  let curCreatedMs = 0;   // synth time of the CURRENT clip, so the age can re-tick   function fmtAgo(ms){     const s = Math.max(0, Math.floor(ms/1000));-    if (s < 90) return s + 's ago';+    if (s < 45) return 'just now';+    if (s < 90) return '1m ago';     const m = Math.floor(s/60); if (m < 90) return m + 'm ago';     const h = Math.floor(m/60); if (h < 36) return h + 'h ago';     const d = Math.floor(h/24); return d + ' day' + (d===1?'':'s') + ' ago';   }+  // ALWAYS show, for the current clip, WHEN its audio was made + how long ago,+  // color-coded so old news is unmissable. This is the whole point: you should+  // never have to wonder whether a clip is fresh.   function setClipAge(created, now){     const el = document.getElementById('clipAge');     const sp = document.getElementById('spCreated');-    if (!created || !now){ el.style.display='none'; sp.textContent = '—'; return; }-    const age = Math.max(0, now - created);+    curCreatedMs = created || 0;+    if (!created){ el.style.display='none'; sp.textContent = '—'; return; }+    const age = Math.max(0, (now || lastServerNow) - created);     const abs = new Date(created).toLocaleString(undefined,       {month:'short', day:'numeric', hour:'numeric', minute:'2-digit'});-    sp.textContent = abs + ' · ' + fmtAgo(age);+    const ago = fmtAgo(age);+    sp.textContent = abs + ' · ' + ago;+    el.style.display = '';     el.className = 'clip-age';     const min = age / 60000;-    if (min < 10){ el.style.display='none'; return; }   // fresh: no badge, card still shows it-    el.style.display = '';-    if (min >= 1440){ el.classList.add('stale'); el.textContent = '⚠ ' + fmtAgo(age); }-    else if (min >= 60){ el.classList.add('warn'); el.textContent = fmtAgo(age); }-    else { el.textContent = fmtAgo(age); }-  }-  // live wall clock — the player's current date + time, always on screen-  function tickClock(){-    const el = document.getElementById('clock');-    if (el) el.textContent = new Date().toLocaleString(undefined,-      {weekday:'short', month:'short', day:'numeric', hour:'numeric', minute:'2-digit'});+    if (min >= 1440){ el.classList.add('stale'); el.textContent = '⚠ audio from ' + abs + ' · ' + ago; }+    else if (min >= 60){ el.classList.add('warn'); el.textContent = 'audio from ' + abs + ' · ' + ago; }+    else { el.textContent = 'audio ' + ago; }   // fresh: teal, compact+    el.title = 'this clip’s audio was synthesized ' + abs + ' (' + ago + ')';   }-  tickClock(); setInterval(tickClock, 15000);+  // keep the relative age honest without a new clip event (polls refresh+  // lastServerNow every ~700ms, so this just re-renders the text)+  setInterval(function(){ if (curCreatedMs) setClipAge(curCreatedMs, lastServerNow); }, 10000);   function applyClip(c){     curClipId = c.id;     // Subject = WHO is speaking (AI thread) + WHAT the clip says (--desc).@@ -595,7 +593,7 @@       renderHistory(j.history || [], j.now_ms || Date.now());       // NEW clip? swap without reloading the page.       if (j.id && j.id !== curClipId) applyClip(j);-      else if (!j.id && curClipId && a.ended) { subjectName.textContent = 'waiting for a clip…'; }+      else if (!j.id && curClipId && a.ended) { subjectName.textContent = 'waiting for a clip…'; setClipAge(0); }       // control commands (replay/pause/play/toggle)       if (j.seq > lastCmdSeq) {         lastCmdSeq = j.seq;