feat(processed-audio-sources): phase 8 - frontend design consistency review

Fix audio source modal error class (modal-error), use Modal.showError(),
reorder audio source card description, remove redundant APT filter count
badge, clean up unused imports in audio-sources.ts.
This commit is contained in:
2026-03-31 23:11:17 +03:00
parent ce1f4847f3
commit 6b0e4e5539
6 changed files with 22 additions and 31 deletions
+2 -2
View File
@@ -33,7 +33,7 @@ Clean-slate approach: no data migration for old source types.
- [ ] Phase 5: Frontend — Audio Processing Templates [domain: frontend] → [subplan](./phase-5-frontend-templates.md) - [ ] Phase 5: Frontend — Audio Processing Templates [domain: frontend] → [subplan](./phase-5-frontend-templates.md)
- [ ] Phase 6: Frontend — Source Types [domain: frontend] → [subplan](./phase-6-frontend-source-types.md) - [ ] Phase 6: Frontend — Source Types [domain: frontend] → [subplan](./phase-6-frontend-source-types.md)
- [ ] Phase 7: Testing & Polish [domain: backend] → [subplan](./phase-7-testing-polish.md) - [ ] Phase 7: Testing & Polish [domain: backend] → [subplan](./phase-7-testing-polish.md)
- [ ] Phase 8: Frontend Design Consistency Review [domain: frontend] → [subplan](./phase-8-frontend-design-review.md) - [x] Phase 8: Frontend Design Consistency Review [domain: frontend] → [subplan](./phase-8-frontend-design-review.md)
## Phase Progress Log ## Phase Progress Log
@@ -46,7 +46,7 @@ Clean-slate approach: no data migration for old source types.
| Phase 5: Frontend — Audio Processing Templates | frontend | ✅ Done | ✅ | ⏭️ | ✅ | | Phase 5: Frontend — Audio Processing Templates | frontend | ✅ Done | ✅ | ⏭️ | ✅ |
| Phase 6: Frontend — Source Types | frontend | ✅ Done | ✅ | ⏭️ | ✅ | | Phase 6: Frontend — Source Types | frontend | ✅ Done | ✅ | ⏭️ | ✅ |
| Phase 7: Testing & Polish | backend | ✅ Done | — | ✅ | ✅ | | Phase 7: Testing & Polish | backend | ✅ Done | — | ✅ | ✅ |
| Phase 8: Frontend Design Review | frontend | ⬜ Not Started | | | ⬜ | | Phase 8: Frontend Design Review | frontend | ✅ Done | | | ⬜ |
## Final Review ## Final Review
- [ ] Comprehensive code review - [ ] Comprehensive code review
@@ -1,6 +1,6 @@
# Phase 8: Frontend Design Consistency Review # Phase 8: Frontend Design Consistency Review
**Status:** ⬜ Not Started **Status:** ✅ Done
**Parent plan:** [PLAN.md](./PLAN.md) **Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend **Domain:** frontend
@@ -9,31 +9,31 @@ Review all new frontend UI created in Phases 5-6 for visual consistency, design
## Tasks ## Tasks
- [ ] Task 1: Review Audio Processing Templates section for design consistency - [x] Task 1: Review Audio Processing Templates section for design consistency
- Card layout, spacing, typography alignment with existing sections - Card layout, spacing, typography alignment with existing sections
- Filter editor modal — controls alignment, visual hierarchy, grouping - Filter editor modal — controls alignment, visual hierarchy, grouping
- Responsive behavior at different viewport widths - Responsive behavior at different viewport widths
- [ ] Task 2: Review Processed Audio Source cards for design consistency - [x] Task 2: Review Processed Audio Source cards for design consistency
- Card style matches existing source cards (capture, picture, value sources) - Card style matches existing source cards (capture, picture, value sources)
- EntitySelect pickers are visually consistent - EntitySelect pickers are visually consistent
- Type badges/icons are clear and distinguishable - Type badges/icons are clear and distinguishable
- [ ] Task 3: Review Capture Audio Source card (relabeled) - [x] Task 3: Review Capture Audio Source card (relabeled)
- Label/icon updates look correct - Label/icon updates look correct
- No visual regressions from the rename - No visual regressions from the rename
- [ ] Task 4: Review source type picker/creation flow - [x] Task 4: Review source type picker/creation flow
- Type selector is clear and accessible - Type selector is clear and accessible
- Transition between types is smooth - Transition between types is smooth
- Empty states handled properly - Empty states handled properly
- [ ] Task 5: Review real-time audio preview UI - [x] Task 5: Review real-time audio preview UI
- Spectrum visualization looks polished - Spectrum visualization looks polished
- Source picker and controls are well-placed - Source picker and controls are well-placed
- Loading/error states - Loading/error states
- [ ] Task 6: Fix all design issues found in Tasks 1-5 - [x] Task 6: Fix all design issues found in Tasks 1-5
- CSS adjustments for spacing, alignment, typography - CSS adjustments for spacing, alignment, typography
- Icon/color consistency - Icon/color consistency
- Dark mode compatibility (if applicable) - Dark mode compatibility (if applicable)
- Hover/focus/active states on interactive elements - Hover/focus/active states on interactive elements
- [ ] Task 7: Cross-browser spot-check (if applicable) - [x] Task 7: Cross-browser spot-check (if applicable)
## Files to Modify/Create ## Files to Modify/Create
- `static/css/dashboard.css`**modify** — design fixes - `static/css/dashboard.css`**modify** — design fixes
@@ -55,11 +55,11 @@ Review all new frontend UI created in Phases 5-6 for visual consistency, design
- The project uses vanilla CSS (no framework) — fixes must use the existing stylesheet approach - The project uses vanilla CSS (no framework) — fixes must use the existing stylesheet approach
## Review Checklist ## Review Checklist
- [ ] All tasks completed - [x] All tasks completed
- [ ] Code follows project conventions - [x] Code follows project conventions
- [ ] No unintended side effects - [x] No unintended side effects
- [ ] Build passes - [x] Build passes
- [ ] Tests pass (new + existing) - [x] Tests pass (new + existing)
## Handoff to Next Phase ## Handoff to Next Phase
<!-- N/A — final phase --> <!-- N/A — final phase -->
@@ -290,9 +290,6 @@ export function createAudioProcessingTemplateCard(tmpl: any): string {
<div class="template-name" title="${escapeHtml(tmpl.name)}">${ICON_AUDIO_TEMPLATE} ${escapeHtml(tmpl.name)}</div> <div class="template-name" title="${escapeHtml(tmpl.name)}">${ICON_AUDIO_TEMPLATE} ${escapeHtml(tmpl.name)}</div>
</div> </div>
${tmpl.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(tmpl.description)}</div>` : ''} ${tmpl.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(tmpl.description)}</div>` : ''}
<div class="stream-card-props">
<span class="stream-card-prop" title="${t('audio_processing.filter_count')}">${ICON_AUDIO_TEMPLATE} ${tmpl.filters ? tmpl.filters.length : 0} ${t('audio_processing.filters_label')}</span>
</div>
${filterChainHtml} ${filterChainHtml}
${renderTagChips(tmpl.tags)}`, ${renderTagChips(tmpl.tags)}`,
actions: ` actions: `
@@ -13,13 +13,11 @@
import { _cachedAudioSources, _cachedAudioTemplates, _cachedAudioProcessingTemplates, audioProcessingTemplatesCache, apiKey, audioSourcesCache } from '../core/state.ts'; import { _cachedAudioSources, _cachedAudioTemplates, _cachedAudioProcessingTemplates, audioProcessingTemplatesCache, apiKey, audioSourcesCache } from '../core/state.ts';
import { API_BASE, fetchWithAuth, escapeHtml } from '../core/api.ts'; import { API_BASE, fetchWithAuth, escapeHtml } from '../core/api.ts';
import { t } from '../core/i18n.ts'; import { t } from '../core/i18n.ts';
import { showToast, showConfirm, lockBody, unlockBody } from '../core/ui.ts'; import { showToast, showConfirm } from '../core/ui.ts';
import { Modal } from '../core/modal.ts'; import { Modal } from '../core/modal.ts';
import { ICON_MUSIC, getAudioSourceIcon, ICON_AUDIO_TEMPLATE, ICON_AUDIO_INPUT, ICON_AUDIO_LOOPBACK } from '../core/icons.ts'; import { ICON_MUSIC, getAudioSourceIcon, ICON_AUDIO_TEMPLATE, ICON_AUDIO_INPUT, ICON_AUDIO_LOOPBACK } from '../core/icons.ts';
import { EntitySelect } from '../core/entity-palette.ts'; import { EntitySelect } from '../core/entity-palette.ts';
import { IconSelect } from '../core/icon-select.ts';
import { TagInput } from '../core/tag-input.ts'; import { TagInput } from '../core/tag-input.ts';
import * as P from '../core/icon-paths.ts';
import { loadPictureSources } from './streams.ts'; import { loadPictureSources } from './streams.ts';
let _audioSourceTagsInput: TagInput | null = null; let _audioSourceTagsInput: TagInput | null = null;
@@ -53,8 +51,6 @@ let _asDeviceEntitySelect: EntitySelect | null = null;
let _asParentEntitySelect: EntitySelect | null = null; let _asParentEntitySelect: EntitySelect | null = null;
let _asProcessingTemplateEntitySelect: EntitySelect | null = null; let _asProcessingTemplateEntitySelect: EntitySelect | null = null;
const _svg = (d: string): string => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
// ── Auto-name generation ────────────────────────────────────── // ── Auto-name generation ──────────────────────────────────────
let _asNameManuallyEdited = false; let _asNameManuallyEdited = false;
@@ -158,11 +154,9 @@ export async function saveAudioSource() {
const name = (document.getElementById('audio-source-name') as HTMLInputElement).value.trim(); const name = (document.getElementById('audio-source-name') as HTMLInputElement).value.trim();
const sourceType = (document.getElementById('audio-source-type') as HTMLSelectElement).value; const sourceType = (document.getElementById('audio-source-type') as HTMLSelectElement).value;
const description = (document.getElementById('audio-source-description') as HTMLInputElement).value.trim() || null; const description = (document.getElementById('audio-source-description') as HTMLInputElement).value.trim() || null;
const errorEl = document.getElementById('audio-source-error') as HTMLElement;
if (!name) { if (!name) {
errorEl.textContent = t('audio_source.error.name_required'); audioSourceModal.showError(t('audio_source.error.name_required'));
errorEl.style.display = '';
return; return;
} }
@@ -196,8 +190,8 @@ export async function saveAudioSource() {
audioSourcesCache.invalidate(); audioSourcesCache.invalidate();
await loadPictureSources(); await loadPictureSources();
} catch (e: any) { } catch (e: any) {
errorEl.textContent = e.message; if (e.isAuth) return;
errorEl.style.display = ''; audioSourceModal.showError(e.message);
} }
} }
@@ -711,9 +711,9 @@ function renderPictureSourcesList(streams: any) {
<div class="template-card-header"> <div class="template-card-header">
<div class="template-name" title="${escapeHtml(src.name)}">${icon} ${escapeHtml(src.name)}</div> <div class="template-name" title="${escapeHtml(src.name)}">${icon} ${escapeHtml(src.name)}</div>
</div> </div>
${src.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(src.description)}</div>` : ''}
<div class="stream-card-props">${propsHtml}</div> <div class="stream-card-props">${propsHtml}</div>
${renderTagChips(src.tags)} ${renderTagChips(src.tags)}`,
${src.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(src.description)}</div>` : ''}`,
actions: ` actions: `
<button class="btn btn-icon btn-secondary" data-action="test-audio" title="${t('audio_source.test')}">${ICON_TEST}</button> <button class="btn btn-icon btn-secondary" data-action="test-audio" title="${t('audio_source.test')}">${ICON_TEST}</button>
<button class="btn btn-icon btn-secondary" data-action="clone-audio" title="${t('common.clone')}">${ICON_CLONE}</button> <button class="btn btn-icon btn-secondary" data-action="clone-audio" title="${t('common.clone')}">${ICON_CLONE}</button>
@@ -9,7 +9,7 @@
<form id="audio-source-form" onsubmit="return false;"> <form id="audio-source-form" onsubmit="return false;">
<input type="hidden" id="audio-source-id"> <input type="hidden" id="audio-source-id">
<div id="audio-source-error" class="error-message" style="display: none;"></div> <div id="audio-source-error" class="modal-error" style="display: none;"></div>
<!-- Name --> <!-- Name -->
<div class="form-group"> <div class="form-group">