Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 44 additions & 26 deletions apps/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AnalysisStatusPanel } from './components/AnalysisStatusPanel';
import { DiagnosticLog } from './components/DiagnosticLog';
import { FileUpload } from './components/FileUpload';
import { WaveformPlayer } from './components/WaveformPlayer';
import { Button, DeviceRack } from './components/ui';
// Audit Finding #5: IdleValuePropPanel now occupies the Signal Monitor area
// when no file is selected. It tells the producer what ASA does and what to
// expect in 30s / 5min.
Expand Down Expand Up @@ -1016,14 +1017,20 @@ export default function App() {
<main className="space-y-5">
<section className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4">
<div className="lg:col-span-4 flex flex-col gap-4">
<div className="flex flex-col">
<div className="bg-bg-surface-dark border border-border border-b-0 rounded-t-sm px-3 py-1.5 flex items-center">
<span className="w-2 h-2 bg-accent rounded-full mr-2"></span>
<h3 className="text-[10px] font-mono text-text-secondary uppercase tracking-wider">Input Source</h3>
</div>
<DeviceRack
name="Input Source"
status={audioFile ? (isAnalyzing ? 'active' : 'success') : 'idle'}
>
{/* bg-bg-card on the inner div: locked by
tests/smoke/theme-shell.spec.ts:41 which asserts the
input-panel computed background is rgb(68, 68, 68)
(#444444 = --color-bg-card). The DeviceRack's body is
transparent by default so the rack's gradient face
would show through; we explicitly flatten the body
here to preserve the palette contract. */}
<div
data-testid="input-panel"
className="bg-bg-card border border-border rounded-b-sm p-4 flex flex-col min-h-[220px]"
className="bg-bg-card flex flex-col min-h-[220px] p-4"
>
{showInputCollapsed && audioFile ? (
// Audit N9: compact post-analysis summary. Replaces the
Expand Down Expand Up @@ -1054,20 +1061,21 @@ export default function App() {
</p>
</div>
<div className="flex items-center gap-2">
<button
type="button"
<Button
variant="secondary"
size="md"
onClick={handleFileClear}
className="flex-1 text-[10px] font-mono uppercase tracking-wider text-text-secondary border border-border bg-bg-panel hover:border-accent/40 hover:text-text-primary px-2 py-2 rounded-sm transition-colors"
className="flex-1"
>
↺ Analyze new file
</button>
<button
type="button"
</Button>
<Button
variant="secondary"
size="md"
onClick={() => setInputManuallyExpanded(true)}
className="text-[10px] font-mono uppercase tracking-wider text-text-secondary border border-border bg-bg-panel hover:border-accent/40 hover:text-text-primary px-2 py-2 rounded-sm transition-colors"
>
Adjust settings
</button>
</Button>
</div>
</div>
) : (
Expand All @@ -1079,13 +1087,13 @@ export default function App() {
<p className="text-[10px] font-mono uppercase tracking-wider text-text-secondary">
Editing analysis settings
</p>
<button
type="button"
<Button
variant="secondary"
size="sm"
onClick={() => setInputManuallyExpanded(false)}
className="text-[10px] font-mono uppercase tracking-wider text-text-secondary border border-border bg-bg-panel hover:border-accent/40 hover:text-text-primary px-2 py-1 rounded-sm transition-colors"
>
Hide
</button>
</Button>
</div>
)}
<FileUpload
Expand Down Expand Up @@ -1128,6 +1136,16 @@ export default function App() {
} ${isAnalyzing ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<div className="flex items-start gap-3">
{/* Native input kept here rather than the Radix
Checkbox primitive because Playwright smoke tests
depend on getByLabel + .toBeChecked() + .uncheck()
against this control (tests/smoke/upload-estimate-
phase1.spec.ts:242). Those locators work reliably
for native form controls; the Radix
<button role="checkbox"> equivalent is less
consistently supported. The Checkbox primitive
still exists in components/ui/ for surfaces that
aren't load-bearing for smoke selectors. */}
<input
type="checkbox"
checked={pitchNoteTranslationRequested}
Expand All @@ -1153,6 +1171,7 @@ export default function App() {
} ${isAnalyzing || !phase2ConfigEnabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<div className="flex items-start gap-3">
{/* See PITCH/NOTE toggle above for the rationale. */}
<input
type="checkbox"
checked={interpretationRequested}
Expand Down Expand Up @@ -1238,25 +1257,24 @@ export default function App() {
transition={{ duration: 0.3, ease: 'easeOut', delay: 0.1 }}
className="mt-4 flex justify-end"
>
<motion.button
<Button
variant="primary"
size="lg"
ledIndicator
leadingIcon={<Play className="w-3 h-3 fill-current" />}
onClick={handleStartAnalysis}
disabled={isAnalyzeDisabled}
whileHover={isAnalyzeDisabled ? {} : { scale: 1.02 }}
whileTap={isAnalyzeDisabled ? {} : { scale: 0.98 }}
className="group relative bg-bg-panel border border-accent/60 hover:bg-accent hover:border-accent text-accent hover:text-bg-app shadow-[0_0_10px_rgba(255,136,0,0.15)] hover:shadow-[0_0_18px_rgba(255,136,0,0.4)] disabled:opacity-40 disabled:shadow-none disabled:border-border disabled:text-text-secondary disabled:cursor-not-allowed font-bold py-2.5 px-7 rounded-sm flex items-center transition-all duration-200 uppercase tracking-wider font-mono text-xs"
title={estimateWrongService ? 'Point the UI at the Sonic Analyzer backend to enable analysis.' : undefined}
>
<span className="w-1.5 h-1.5 rounded-full bg-accent group-hover:bg-bg-app/50 group-disabled:bg-border mr-3 animate-pulse" />
<Play className="w-3 h-3 mr-2 fill-current" />
Run Analysis
</motion.button>
</Button>
</motion.div>
</>
)}
</>
)}
</div>
</div>
</DeviceRack>
</div>

<div className="lg:col-span-8 flex flex-col">
Expand Down
100 changes: 56 additions & 44 deletions apps/ui/src/components/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { UploadCloud, FileAudio, X, AlertTriangle } from 'lucide-react';

import { isSupportedAudioFile } from '../services/audioFile';
import { Button, Panel, Pill } from './ui';

interface FileUploadProps {
onFileSelect: (file: File) => void;
Expand Down Expand Up @@ -139,24 +140,26 @@ export function FileUpload({
</div>
<p className="text-sm font-bold mb-1 tracking-wide text-text-primary">Drop Audio Here</p>
<p className="text-[10px] text-text-secondary font-mono uppercase tracking-wider">or click to browse</p>
<div className="mt-4 flex gap-2">
{['MP3', 'WAV', 'FLAC', 'AIFF'].map(fmt => (
<span key={fmt} className="text-[9px] font-mono text-text-secondary border border-border px-1.5 py-0.5 rounded-sm bg-bg-panel opacity-60">
{fmt}
</span>
))}
<div className="mt-4 flex gap-1.5">
{['MP3', 'WAV', 'FLAC', 'AIFF'].map((fmt) => (
<Pill key={fmt} tone="neutral" variant="outline" size="xs">
{fmt}
</Pill>
))}
</div>
<div className="mt-4">
<Button
variant="secondary"
size="md"
onClick={(event) => {
event.stopPropagation();
void onLoadDemoTrack();
}}
disabled={isLoading || isDemoLoading}
>
{isDemoLoading ? 'Loading Demo...' : 'Load Demo Track'}
</Button>
</div>
<button
type="button"
onClick={(event) => {
event.stopPropagation();
void onLoadDemoTrack();
}}
disabled={isLoading || isDemoLoading}
className="mt-4 rounded-sm border border-border bg-bg-panel px-3 py-1.5 text-[10px] font-mono uppercase tracking-wider text-text-secondary transition-colors hover:border-accent/40 hover:text-accent disabled:opacity-50 disabled:cursor-not-allowed"
>
{isDemoLoading ? 'Loading Demo...' : 'Load Demo Track'}
</button>
{fileError && (
<div className="mt-3 flex items-center gap-2 text-error text-[10px] font-mono uppercase tracking-wider" role="alert">
<AlertTriangle className="w-3.5 h-3.5 shrink-0" />
Expand All @@ -165,36 +168,45 @@ export function FileUpload({
)}
</div>
) : (
<div className="bg-bg-card border border-border rounded-sm p-4 flex items-center justify-between relative overflow-hidden group">
<div className="absolute left-0 top-0 bottom-0 w-1 bg-accent"></div>
<div className="flex items-center space-x-4 pl-2">
<div className="bg-bg-panel p-2 rounded-sm border border-border">
<FileAudio className="w-6 h-6 text-accent" />
</div>
<div>
<p className="font-bold text-sm tracking-tight truncate max-w-[200px] md:max-w-xs">{selectedFile.name}</p>
<p className="text-[10px] text-text-secondary font-mono uppercase tracking-wider flex items-center mt-1">
<span className="w-1.5 h-1.5 rounded-full bg-success mr-2"></span>
Ready • {(selectedFile.size / (1024 * 1024)).toFixed(2)} MB
</p>
{fileSizeWarning && (
<p className="text-[10px] text-warning font-mono uppercase tracking-wider flex items-center mt-1">
<AlertTriangle className="w-3 h-3 shrink-0 mr-1.5" />
{fileSizeWarning}
<Panel variant="surface" padding="md" className="relative overflow-hidden group">
{/* Accent stripe down the left edge — the same Live device-on
affordance used elsewhere. */}
<div className="absolute left-0 top-0 bottom-0 w-1 bg-accent" aria-hidden />
<div className="flex items-center justify-between gap-3 pl-2">
<div className="flex items-center gap-3 min-w-0">
<div className="bg-bg-panel p-2 rounded-sm border border-border shrink-0">
<FileAudio className="w-6 h-6 text-accent" />
</div>
<div className="min-w-0">
<p className="font-bold text-sm tracking-tight truncate max-w-[200px] md:max-w-xs">
{selectedFile.name}
</p>
)}
<p className="text-[10px] text-text-secondary font-mono uppercase tracking-wider flex items-center mt-1">
<span className="w-1.5 h-1.5 rounded-full bg-success mr-2" aria-hidden />
Ready • {(selectedFile.size / (1024 * 1024)).toFixed(2)} MB
</p>
{fileSizeWarning && (
<p className="text-[10px] text-warning font-mono uppercase tracking-wider flex items-center mt-1">
<AlertTriangle className="w-3 h-3 shrink-0 mr-1.5" />
{fileSizeWarning}
</p>
)}
</div>
</div>
{!isLoading && (
<Button
variant="ghost"
size="sm"
iconOnly
onClick={clearFile}
title="Remove File"
aria-label="Remove File"
>
<X className="w-4 h-4" />
</Button>
)}
</div>
{!isLoading && (
<button
onClick={clearFile}
className="p-1.5 hover:bg-bg-panel rounded-sm border border-transparent hover:border-border transition-all group/btn"
title="Remove File"
>
<X className="w-4 h-4 text-text-secondary group-hover/btn:text-error" />
</button>
)}
</div>
</Panel>
)}
</div>
);
Expand Down
Loading