From 7264c321f86e06d0a8ed4acdebec2d43727de60e Mon Sep 17 00:00:00 2001 From: Palash Debnath Date: Wed, 1 Jul 2026 03:49:28 +0530 Subject: [PATCH] feat(ui): rewrite workspace/voice tail components on clean shadcn, trim their CSS (fast mode) (#832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FAST-mode shadcn/Tailwind migration of the workspace + voice "tail" components: the cleanly JSX-controlled chrome moves onto the JSX as Tailwind v4 utilities (token utilities + arbitrary var()/px to preserve exact pixels/colors), with irreducible CSS kept co-located. - WaveformPlayer: all three render branches (player, native fallback, missing notice) converted to Tailwind; WaveformPlayer.css deleted (-87). The `wf-player__btn` class is retained as the focus-visible hook for the shared a11y ring in index.css; the dead `wf-player__spin` rule + `wf-spin` keyframe + its reduced-motion block were removed. - VoicePreview: popover container/header/title/close/body/foot/hint converted to Tailwind; VoicePreview.css trimmed 87->23 lines. Kept the `voice-preview-in` entrance @keyframes (referenced via animate-[…]) and the `.voice-preview__select`/`__text` rules — they layer on top of the *unlayered* shared `.input-base`, and Tailwind utilities (in @layer utilities) would lose that cascade, so they stay unlayered. - WorkspaceHistory: finished the voice variant, which #781 left on the now-deleted `.wh`/`.wh__head`/`.wh__title`/`.wh__scroll`/`.wh__empty` classes (rendering unstyled). Converted them to the same Tailwind utilities the dub variant already uses. Kept the studio-with-history/ studio-right/shell-narrow layout + the `.studio-action-bar` sticky override (#476, guarded by workspaceHistoryReflow.test.js). - WorkspaceVoices: already fully converted by #781; its `.wv*` chrome is shared with the out-of-scope WorkspaceProjects.jsx, so the CSS stays. Verified: vite build OK, oxlint exit 0, oxfmt --check clean, 641 vitest pass (incl. workspaceHistoryReflow + waveform), 48 visual pass, bun install --frozen-lockfile clean. Eyeballed the Voice workspace (history rows + waveform players) and the VoicePreview popover in a live dev run. Co-authored-by: mergetest Co-authored-by: Claude Opus 4.8 (1M context) --- frontend/src/components/VoicePreview.css | 78 ++---------------- frontend/src/components/VoicePreview.jsx | 18 ++-- frontend/src/components/WaveformPlayer.css | 87 -------------------- frontend/src/components/WaveformPlayer.jsx | 39 +++++++-- frontend/src/components/WorkspaceHistory.jsx | 10 +-- 5 files changed, 53 insertions(+), 179 deletions(-) delete mode 100644 frontend/src/components/WaveformPlayer.css diff --git a/frontend/src/components/VoicePreview.css b/frontend/src/components/VoicePreview.css index 93ee2427..6560c245 100644 --- a/frontend/src/components/VoicePreview.css +++ b/frontend/src/components/VoicePreview.css @@ -1,63 +1,15 @@ -.voice-preview { - position: fixed; - /* Track the LogsFooter's live height so an expanded/resized footer never - slides underneath the popover (LogsFooter.jsx writes the variable). */ - bottom: calc(var(--logs-footer-height, 28px) + 16px); - right: 16px; - z-index: 900; - width: 320px; - background: var(--chrome-bg); - border: 1px solid var(--chrome-border-strong); - border-radius: 12px; - box-shadow: 0 8px 32px rgba(0,0,0,0.4); - display: flex; - flex-direction: column; - overflow: hidden; - animation: voice-preview-in 0.2s ease-out; -} +/* VoicePreview — all panel chrome converted to Tailwind utilities on + VoicePreview.jsx. Two things stay here as irreducible CSS: + 1. the entrance @keyframes (referenced via animate-[…] on the popover); and + 2. the select/text rules that layer on top of the *unlayered* shared + .input-base — Tailwind utilities live in @layer utilities and would lose + the cascade to .input-base, so these overrides must stay unlayered. */ + @keyframes voice-preview-in { from { opacity: 0; transform: translateY(12px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } -.voice-preview__head { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border-bottom: 1px solid var(--chrome-border); -} -.voice-preview__title { - display: flex; - align-items: center; - gap: 6px; - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--chrome-fg); -} -.voice-preview__close { - background: none; - border: none; - color: var(--chrome-fg-muted); - cursor: pointer; - padding: 4px; - border-radius: 6px; - transition: background 0.15s; -} -.voice-preview__close:hover { - background: var(--chrome-hover-bg); - color: var(--chrome-fg); -} - -.voice-preview__body { - padding: 12px 14px; - display: flex; - flex-direction: column; - gap: 8px; -} .voice-preview__select { font-size: 0.78rem; padding: 6px 8px; @@ -69,19 +21,3 @@ line-height: 1.4; min-height: 48px; } -.voice-preview__audio { - width: 100%; -} - -.voice-preview__foot { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 14px 10px; - border-top: 1px solid var(--chrome-border); -} -.voice-preview__hint { - font-family: var(--font-mono); - font-size: 0.65rem; - color: var(--chrome-fg-dim); -} diff --git a/frontend/src/components/VoicePreview.jsx b/frontend/src/components/VoicePreview.jsx index 11e10e6b..a1cf2e8b 100644 --- a/frontend/src/components/VoicePreview.jsx +++ b/frontend/src/components/VoicePreview.jsx @@ -98,14 +98,14 @@ export default function VoicePreview({ if (!open) return null; return ( -
-
- +
+
+ {t('voicePreview.title')}
-
+