From aa5741eafbc98170581c3ebdc1795c1aba652db4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 20:54:48 -0400 Subject: [PATCH] icon --- TODO.md | 67 ++++++++++++++++++++------ clio-ui/RecordingsViewer.html | 9 +--- clio-ui/components/App.jsx | 2 - clio-ui/components/Dashboard.jsx | 4 -- clio-ui/components/DayView.jsx | 8 +-- clio-ui/components/RecordingDetail.jsx | 22 ++++----- clio-ui/components/SearchView.jsx | 14 ++---- clio-ui/favicon.svg | 9 ++++ 8 files changed, 76 insertions(+), 59 deletions(-) create mode 100644 clio-ui/favicon.svg diff --git a/TODO.md b/TODO.md index e6bb78c..e0a5e28 100644 --- a/TODO.md +++ b/TODO.md @@ -1,26 +1,61 @@ # Clio TODO -## Audio Player Issues -- [ ] Short audio files show wrong duration (e.g., shows 1 second instead of 13 seconds) - - Progress bar doesn't move for these - - Can't seek - - Need to investigate why audioDuration isn't being set correctly for short files +## Requirements / Expected Behaviors -- [ ] Hard to click on audio controls for small recordings - - Add proper prev/next buttons for navigation - - Make clickable area larger +These are expected behaviors that should be preserved. If something breaks, check this list. -## URL Routing Issue -- [ ] URL shows `#/detail` instead of `#/recording/{id}` - - Loses recording info on refresh - - Need to debug why buildHash isn't being called or URL isn't updating +### Audio Player +- [ ] Audio should auto-play when opening a recording +- [x] Clicking a highlight should seek to that position and auto-play +- [x] Clicking a search result should seek to that position +- [ ] Skip buttons (-30s, -5s, +5s, +30s) should work +- [ ] Speed controls should persist during playback +- [ ] Native audio controls (play/pause, seek, volume) should work -## Security (Completed) -- [x] Add API key authentication to all endpoints +### Navigation +- [x] URL should show `#/recording/{id}` when viewing a recording +- [x] URL should show `#/calendar/{date}` when viewing a specific day +- [x] Browser back/forward should work correctly +- [x] Refreshing page should preserve current view + +### Dates/Times +- [x] All dates should display in local timezone (not UTC) +- [ ] Calendar should highlight today's date + +### Highlights +- [ ] Creating a highlight should save to API +- [ ] Deleting a highlight should remove from API +- [ ] Highlights should appear in the highlights panel +- [ ] Clicking a highlight in HighlightsView should open recording at that time + +## Known Issues + +### Audio Player +- [ ] Short audio files may show wrong duration initially +- [ ] DayView density bars may show duplicate lines + +### Data Import +- [ ] Need to import large historical dataset before removing duplicate calendar + +## Completed + +### Security +- [x] API key authentication on all endpoints - [x] UI prompts for API key and stores in localStorage - [x] Audio fetched via authenticated blob URL -## Deployment +### Deployment - [x] Forgejo workflow for UI deployment -- [x] Forgejo workflow for API deployment (needs sudo permission for restart) +- [x] Forgejo workflow for API deployment - [ ] Add sudoers rule for gitea-runner to restart phone-recorder + +### Cleanup +- [x] Removed dead quality code (was hardcoded to 'good') +- [x] Simplified audio player to native HTML5 controls +- [x] Added favicon + +## Future Ideas +- [ ] Prev/next recording buttons +- [ ] Keyboard shortcuts (space to play/pause, arrow keys to seek) +- [ ] Playwright tests +- [ ] Waveform visualization (when transcripts available, show speaking density) diff --git a/clio-ui/RecordingsViewer.html b/clio-ui/RecordingsViewer.html index 17f1cf6..aef2b39 100644 --- a/clio-ui/RecordingsViewer.html +++ b/clio-ui/RecordingsViewer.html @@ -3,7 +3,8 @@ -RecordingsViewer +Clio + @@ -50,12 +51,6 @@ .mono { font-family: var(--font-mono); } - /* Quality badge colors */ - .q-excellent { color: var(--green); } - .q-good { color: var(--teal); } - .q-fair { color: var(--amber); } - .q-poor { color: var(--red); } - /* Source pill */ .source-pill { display: inline-flex; align-items: center; gap: 5px; diff --git a/clio-ui/components/App.jsx b/clio-ui/components/App.jsx index bef834f..643c244 100644 --- a/clio-ui/components/App.jsx +++ b/clio-ui/components/App.jsx @@ -78,7 +78,6 @@ function transformRecording(rec) { return { id: rec.id, source: rec.source_name, - quality: 'good', // API doesn't track quality, default to good durationMinutes, startTime, endTime, @@ -103,7 +102,6 @@ function transformRecording(rec) { })), sampleRate: rec.sample_rate || 44100, bitDepth: rec.bit_depth || 16, - peakDB: -30, audioPath: rec.audio_path, }; } diff --git a/clio-ui/components/Dashboard.jsx b/clio-ui/components/Dashboard.jsx index cbff06a..0b4fea5 100644 --- a/clio-ui/components/Dashboard.jsx +++ b/clio-ui/components/Dashboard.jsx @@ -46,9 +46,6 @@ function Dashboard({ data, setView, setSelectedDay, setSelectedRecording }) { return h > 0 ? `${h}h ${m}m` : `${m}m`; } - function qualityClass(q) { - return `q-${q}`; - } return (
@@ -196,7 +193,6 @@ function RecordingRow({ recording, onClick }) {
{dateStr} {timeStr}
{r.source}
{dur}
-
{r.quality}
{r.fileSizeMB} MB
{r.highlights.length > 0 && (
diff --git a/clio-ui/components/DayView.jsx b/clio-ui/components/DayView.jsx index ed2e2ec..f1524a4 100644 --- a/clio-ui/components/DayView.jsx +++ b/clio-ui/components/DayView.jsx @@ -184,10 +184,6 @@ function DayTimeline({ recordings, onSelect }) { return Math.max((r.durationMinutes / 60) * HOUR_H, 8); } - function qualityColor(q) { - const map = { excellent: 'var(--green)', good: 'var(--teal)', fair: 'var(--amber)', poor: 'var(--red)' }; - return map[q] || 'var(--text3)'; - } const totalH = 24 * HOUR_H; @@ -230,7 +226,7 @@ function DayTimeline({ recordings, onSelect }) {
onSelect(r)} - title={`${r.source} · ${Math.floor(r.durationMinutes / 60)}h ${Math.round(r.durationMinutes % 60)}m · ${r.quality}`} + title={`${r.source} · ${Math.floor(r.durationMinutes / 60)}h ${Math.round(r.durationMinutes % 60)}m`} style={{ position: 'absolute', top: top + 1, @@ -254,7 +250,6 @@ function DayTimeline({ recordings, onSelect }) { {Math.floor(r.durationMinutes / 60)}h {Math.round(r.durationMinutes % 60)}m - {r.quality} {r.highlights.length > 0 && ( ◈ {r.highlights.length} )} @@ -321,7 +316,6 @@ function DayTimeline({ recordings, onSelect }) { {Math.floor(r.durationMinutes / 60)}h {Math.round(r.durationMinutes % 60)}m
- {r.quality} {r.sampleRate / 1000}kHz · {r.bitDepth}bit · {r.fileSizeMB}MB {r.hasTranscript && ✦ transcript} {r.highlights.length > 0 && ◈ {r.highlights.length}} diff --git a/clio-ui/components/RecordingDetail.jsx b/clio-ui/components/RecordingDetail.jsx index a650f26..b676ca2 100644 --- a/clio-ui/components/RecordingDetail.jsx +++ b/clio-ui/components/RecordingDetail.jsx @@ -88,6 +88,7 @@ function RecordingDetail({ recording: r, data, onBack, allHighlights, setAllHigh // Handle seeking to initial time (from search results, highlights, etc.) useEffectRD(() => { if (initialSeekTime == null) return; + if (!audioBlobUrl) return; // Wait for audio to be loaded const audio = audioRef.current; if (!audio) return; @@ -103,7 +104,7 @@ function RecordingDetail({ recording: r, data, onBack, allHighlights, setAllHigh audio.addEventListener('loadedmetadata', doSeek, { once: true }); return () => audio.removeEventListener('loadedmetadata', doSeek); } - }, [initialSeekTime, onSeekHandled]); + }, [initialSeekTime, onSeekHandled, audioBlobUrl]); // Sync playback state with audio element useEffectRD(() => { @@ -279,13 +280,10 @@ function RecordingDetail({ recording: r, data, onBack, allHighlights, setAllHigh 'follow-up': 'var(--amber-dim)', meeting: 'var(--green)', personal: 'var(--text2)', work: 'var(--teal)', }; - const qualityDetails = [ - { label: 'Quality', value: r.quality, cls: `q-${r.quality}` }, - { label: 'Sample Rate', value: `${r.sampleRate / 1000} kHz`, cls: '' }, - { label: 'Bit Depth', value: `${r.bitDepth}-bit`, cls: '' }, - { label: 'Peak', value: `${r.peakDB.toFixed(1)} dB`, cls: r.peakDB > -30 ? 'q-good' : r.peakDB > -45 ? 'q-fair' : 'q-poor' }, - { label: 'Size', value: `${r.fileSizeMB} MB`, cls: '' }, - { label: 'Source', value: r.source, cls: `source-${r.source}` }, + const recordingDetails = [ + { label: 'Sample Rate', value: `${r.sampleRate / 1000} kHz` }, + { label: 'Bit Depth', value: `${r.bitDepth}-bit` }, + { label: 'Size', value: `${r.fileSizeMB} MB` }, ]; return ( @@ -300,10 +298,10 @@ function RecordingDetail({ recording: r, data, onBack, allHighlights, setAllHigh
{r.source} - {qualityDetails.map(q => ( - - {q.label}: - {q.value} + {recordingDetails.map(d => ( + + {d.label}: + {d.value} ))}
diff --git a/clio-ui/components/SearchView.jsx b/clio-ui/components/SearchView.jsx index e72df81..fdda392 100644 --- a/clio-ui/components/SearchView.jsx +++ b/clio-ui/components/SearchView.jsx @@ -4,18 +4,16 @@ const { useState: useStateSV, useMemo: useMemoSV } = React; function SearchView({ data, setSelectedRecording, setView }) { const [query, setQuery] = useStateSV(''); const [filterSource, setFilterSource] = useStateSV([]); - const [filterQuality, setFilterQuality] = useStateSV([]); const [filterHasTranscript, setFilterHasTranscript] = useStateSV(false); const [filterHasHighlights, setFilterHasHighlights] = useStateSV(false); const results = useMemoSV(() => { - if (!query.trim() && filterSource.length === 0 && filterQuality.length === 0 && !filterHasTranscript && !filterHasHighlights) return []; + if (!query.trim() && filterSource.length === 0 && !filterHasTranscript && !filterHasHighlights) return []; const q = query.toLowerCase().trim(); const matches = []; for (const r of data.recordings) { if (filterSource.length > 0 && !filterSource.includes(r.source)) continue; - if (filterQuality.length > 0 && !filterQuality.includes(r.quality)) continue; if (filterHasTranscript && !r.hasTranscript) continue; if (filterHasHighlights && r.highlights.length === 0) continue; @@ -31,7 +29,7 @@ function SearchView({ data, setSelectedRecording, setView }) { } return matches.sort((a, b) => b.matchingSegments.length - a.matchingSegments.length || b.recording.startTime - a.recording.startTime); - }, [query, filterSource, filterQuality, filterHasTranscript, filterHasHighlights, data]); + }, [query, filterSource, filterHasTranscript, filterHasHighlights, data]); function toggleArr(arr, setArr, val) { setArr(prev => prev.includes(val) ? prev.filter(x => x !== val) : [...prev, val]); @@ -61,9 +59,8 @@ function SearchView({ data, setSelectedRecording, setView }) { } const SOURCES = ['portable', 'stationary', 'phone']; - const QUALITIES = ['excellent', 'good', 'fair', 'poor']; - const hasFilters = query.trim() || filterSource.length > 0 || filterQuality.length > 0 || filterHasTranscript || filterHasHighlights; + const hasFilters = query.trim() || filterSource.length > 0 || filterHasTranscript || filterHasHighlights; return (
@@ -97,10 +94,6 @@ function SearchView({ data, setSelectedRecording, setView }) { toggleArr(filterSource, setFilterSource, s)} /> ))}
- {QUALITIES.map(q => ( - toggleArr(filterQuality, setFilterQuality, q)} /> - ))} -
setFilterHasTranscript(v => !v)} /> setFilterHasHighlights(v => !v)} />
@@ -176,7 +169,6 @@ function SearchResultCard({ recording: r, matchingSegments, query, highlightText {dateStr} {timeStr} {r.source} {dur} - {r.quality} {r.highlights.length > 0 && ( ◈ {r.highlights.length} )} diff --git a/clio-ui/favicon.svg b/clio-ui/favicon.svg new file mode 100644 index 0000000..25762e3 --- /dev/null +++ b/clio-ui/favicon.svg @@ -0,0 +1,9 @@ + + + + + + + + +