Rework song player view to match design system

- New split layout: waveform/transport/queue left, comment panel right
- Avatar pins above waveform positioned by timestamp with hover tooltips
- Transport bar: speed selector, ±30s skip, 46px amber play/pause, volume
- Comment compose: live timestamp pill, suggestion/issue/keeper tag buttons
- Comment list: per-author colour avatars, amber timestamp seek chips,
  playhead-proximity highlight, delete only shown on own comments
- Queue panel showing other songs in the same session
- Waveform colours updated to amber/dim palette (104px height)
- Add GET /songs/{song_id} endpoint for song metadata
- Add tag field to SongComment (model, schema, router, migration 0005)
- Fix migration 0005 down_revision to use short ID "0004"
- Fix ESLint no-unused-expressions in keyboard shortcut handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mistral Vibe
2026-04-06 21:14:56 +02:00
parent a31f7db619
commit fdf9f52f6f
7 changed files with 1005 additions and 221 deletions

View File

@@ -23,6 +23,7 @@ export function useWaveform(
const [isPlaying, setIsPlaying] = useState(false);
const [isReady, setIsReady] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const wasPlayingRef = useRef(false);
const markersRef = useRef<CommentMarker[]>([]);
@@ -31,12 +32,12 @@ export function useWaveform(
const ws = WaveSurfer.create({
container: containerRef.current,
waveColor: "#2A3050",
progressColor: "#F0A840",
cursorColor: "#FFD080",
waveColor: "rgba(255,255,255,0.09)",
progressColor: "#c8861a",
cursorColor: "#e8a22a",
barWidth: 2,
barRadius: 2,
height: 80,
height: 104,
normalize: true,
});
@@ -45,6 +46,7 @@ export function useWaveform(
ws.on("ready", () => {
setIsReady(true);
setDuration(ws.getDuration());
options.onReady?.(ws.getDuration());
// Reset playing state when switching versions
setIsPlaying(false);
@@ -141,7 +143,7 @@ export function useWaveform(
markersRef.current = [];
};
return { isPlaying, isReady, currentTime, play, pause, seekTo, addMarker, clearMarkers };
return { isPlaying, isReady, currentTime, duration, play, pause, seekTo, addMarker, clearMarkers };
}
function formatTime(seconds: number): string {