From e69b594da9ef08fae435654f92b5ec1f50f47ef4 Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Sat, 18 Apr 2026 01:08:34 -0400 Subject: [PATCH] make optional overrides in the template actually optional --- src/shared/themeState.ts | 83 +++++++++++++++++++++++--------------- test/theme-library.test.ts | 35 ++++++++++++---- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/shared/themeState.ts b/src/shared/themeState.ts index f332b07..c39b159 100644 --- a/src/shared/themeState.ts +++ b/src/shared/themeState.ts @@ -824,6 +824,16 @@ function serializeSection( return lines } +function commentExampleTokens(lines: string[]): string[] { + return lines.map((line) => { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';') || /^\[.+\]$/.test(trimmed)) { + return line + } + return `# ${line}` + }) +} + export function serializeThemeFile(theme: PrismTheme): string { const normalized = normalizeTheme(theme, theme.name) const sections: string[] = [ @@ -871,78 +881,84 @@ export function createTemplateThemeFile(): string { `text = ${toThemeChannels(base.app.text ?? 'rgb(255, 255, 255)')}`, '', '# Optional palette extras:', - `text_muted = ${toThemeChannels(base.app.textMuted ?? 'rgba(255, 255, 255, 0.42)')}`, - `success = ${toThemeChannels(base.app.success ?? DEFAULT_SUCCESS)}`, - `warning = ${toThemeChannels(base.app.warning ?? DEFAULT_WARNING)}`, - `danger = ${toThemeChannels(base.app.danger ?? DEFAULT_DANGER)}`, + ...commentExampleTokens([ + `text_muted = ${toThemeChannels(base.app.textMuted ?? 'rgba(255, 255, 255, 0.42)')}`, + `success = ${toThemeChannels(base.app.success ?? DEFAULT_SUCCESS)}`, + `warning = ${toThemeChannels(base.app.warning ?? DEFAULT_WARNING)}`, + `danger = ${toThemeChannels(base.app.danger ?? DEFAULT_DANGER)}`, + ]), '', '# Optional shell overrides:', - `toolbar_bg = ${toThemeChannels(withAlpha(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)', 0.78))}`, - `settings_bg_top = ${toThemeChannels(base.app.surface ?? 'rgba(8, 11, 16, 0.92)')}`, - `settings_bg_bottom = ${toThemeChannels(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)')}`, - `bottom_bar_bg = ${toThemeChannels(withAlpha(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)', 0.98))}`, + ...commentExampleTokens([ + `toolbar_bg = ${toThemeChannels(withAlpha(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)', 0.78))}`, + `settings_bg_top = ${toThemeChannels(base.app.surface ?? 'rgba(8, 11, 16, 0.92)')}`, + `settings_bg_bottom = ${toThemeChannels(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)')}`, + `bottom_bar_bg = ${toThemeChannels(withAlpha(base.app.surfaceAlt ?? 'rgba(4, 8, 12, 0.98)', 0.98))}`, + ]), ] - const controlsSection = serializeSection('Controls', { + const controlsSection = commentExampleTokens(serializeSection('Controls', { ...base.controls, flatControls: 'false', - }, CONTROLS_SCHEMA as SectionSchema>) - controlsSection.splice(1, 0, '# Entire section optional. Remove tokens or the whole section to use Prism defaults.') + }, CONTROLS_SCHEMA as SectionSchema>)) + controlsSection.splice(1, 0, '# Entire section optional. Uncomment tokens here only if you want to override Prism defaults.') - const scopesSection = serializeSection('Scopes', { ...base.scopes }, SCOPES_SCHEMA as SectionSchema>) - scopesSection.splice(1, 0, '# Entire section optional. Remove tokens or the whole section to use Prism defaults.') + const scopesSection = commentExampleTokens( + serializeSection('Scopes', { ...base.scopes }, SCOPES_SCHEMA as SectionSchema>), + ) + scopesSection.splice(1, 0, '# Entire section optional. Uncomment tokens here only if you want to override Prism defaults.') - const spectrumSection = serializeSection('Spectrum', { + const spectrumSection = commentExampleTokens(serializeSection('Spectrum', { ...base.spectrum, background: resolved.spectrum.background, guides: resolved.spectrum.guides, labels: resolved.spectrum.labels, heatBase: resolved.spectrum.heatBase, - }, SPECTRUM_SCHEMA as SectionSchema>) + }, SPECTRUM_SCHEMA as SectionSchema>)) - const oscilloscopeSection = serializeSection('Oscilloscope', { + const oscilloscopeSection = commentExampleTokens(serializeSection('Oscilloscope', { ...base.oscilloscope, background: resolved.oscilloscope.background, guides: resolved.oscilloscope.guides, - }, OSCILLOSCOPE_SCHEMA as SectionSchema>) + }, OSCILLOSCOPE_SCHEMA as SectionSchema>)) - const vectorscopeSection = serializeSection('Vectorscope', { + const vectorscopeSection = commentExampleTokens(serializeSection('Vectorscope', { ...base.vectorscope, background: resolved.vectorscope.background, guides: resolved.vectorscope.guides, labels: resolved.vectorscope.labels, - }, VECTORSCOPE_SCHEMA as SectionSchema>) + }, VECTORSCOPE_SCHEMA as SectionSchema>)) - const spectrogramSection = serializeSection('Spectrogram', { + const spectrogramSection = commentExampleTokens(serializeSection('Spectrogram', { ...base.spectrogram, background: resolved.spectrogram.background, - }, SPECTROGRAM_SCHEMA as SectionSchema>) + }, SPECTROGRAM_SCHEMA as SectionSchema>)) - const vumeterSection = serializeSection('VUMeter', { + const vumeterSection = commentExampleTokens(serializeSection('VUMeter', { ...base.vumeter, background: resolved.vumeter.background, scale: resolved.vumeter.scale, labels: resolved.vumeter.labels, - }, VUMETER_SCHEMA as SectionSchema>) + }, VUMETER_SCHEMA as SectionSchema>)) - const lufsmeterSection = serializeSection('LUFSMeter', { + const lufsmeterSection = commentExampleTokens(serializeSection('LUFSMeter', { ...base.lufsmeter, background: resolved.lufsmeter.background, scale: resolved.lufsmeter.scale, labels: resolved.lufsmeter.labels, - }, LUFSMETER_SCHEMA as SectionSchema>) + }, LUFSMETER_SCHEMA as SectionSchema>)) - const waveformSection = serializeSection('Waveform', { + const waveformSection = commentExampleTokens(serializeSection('Waveform', { ...base.waveform, background: resolved.waveform.background, guides: resolved.waveform.guides, - }, WAVEFORM_SCHEMA as SectionSchema>) + }, WAVEFORM_SCHEMA as SectionSchema>)) - const nowPlayingSection = serializeSection( + const nowPlayingSection = commentExampleTokens(serializeSection( 'Now Playing', { ...base.nowPlaying }, NOW_PLAYING_SCHEMA as SectionSchema>, - ) + )) return `# Prism theme template # @@ -950,13 +966,14 @@ export function createTemplateThemeFile(): string { # CSS colors like #hex, rgb(), and rgba() also work # # Start with [App]. -# Everything else below is optional and can be removed to inherit defaults. +# Everything else below is optional and starts commented out. +# Uncomment the tokens you want to customize and leave the rest commented to inherit defaults. # # [Controls] and [Scopes] are shared override groups. # Module sections show the full set of supported tokens for each module. # -# Remove any token to let Prism inherit or derive it. -# Remove an entire section if that area should use Prism's defaults. +# Comment out any optional token to let Prism inherit or derive it. +# Leave an entire optional section commented if that area should use Prism's defaults. # ${[ themeSection.join('\n'), @@ -964,7 +981,7 @@ ${[ controlsSection.join('\n'), scopesSection.join('\n'), '# Module sections below are optional overrides.', - '# Keep the tokens you want to customize and delete the rest.', + '# Uncomment the tokens you want to customize and leave the rest as examples.', spectrumSection.join('\n'), oscilloscopeSection.join('\n'), vectorscopeSection.join('\n'), diff --git a/test/theme-library.test.ts b/test/theme-library.test.ts index 7b1739d..149868d 100644 --- a/test/theme-library.test.ts +++ b/test/theme-library.test.ts @@ -206,21 +206,40 @@ test('createTemplateThemeFile presents a simplified recommended theme layout', ( const parsed = parseThemeFileContent(template, 'Template Theme') assert.match(template, /# Start with \[App\]\./) - assert.match(template, /# Everything else below is optional and can be removed to inherit defaults\./) + assert.match(template, /# Everything else below is optional and starts commented out\./) + assert.match(template, /# Uncomment the tokens you want to customize and leave the rest commented to inherit defaults\./) assert.match(template, /# \[Controls\] and \[Scopes\] are shared override groups\./) assert.match(template, /# Module sections show the full set of supported tokens for each module\./) - assert.match(template, /# Remove any token to let Prism inherit or derive it\./) - assert.match(template, /# Remove an entire section if that area should use Prism's defaults\./) + assert.match(template, /# Comment out any optional token to let Prism inherit or derive it\./) + assert.match(template, /# Leave an entire optional section commented if that area should use Prism's defaults\./) assert.match(template, /# Optional palette extras:/) assert.match(template, /# Optional shell overrides:/) assert.match(template, /^\[Controls\]$/m) assert.match(template, /^\[Scopes\]$/m) assert.match(template, /^\[Spectrum\]$/m) - assert.match(template, /^flat_controls = false$/m) - assert.match(template, /^toolbar_bg = 4, 8, 12, 199$/m) + assert.match(template, /^# flat_controls = false$/m) + assert.match(template, /^# toolbar_bg = 4, 8, 12, 199$/m) assert.equal(parsed.app.accent, 'rgb(56, 189, 248)') - assert.equal(parsed.app.textMuted, 'rgba(255, 255, 255, 0.42)') - assert.equal(parsed.controls.flatControls, 'false') + assert.equal(parsed.app.textMuted, undefined) + assert.equal(parsed.controls.flatControls, undefined) + assert.equal(parsed.spectrum.line, undefined) + assert.equal(parsed.nowPlaying.background, undefined) +}) + +test('parsed template keeps module colors and backgrounds derived from starter app tokens', () => { + const parsed = parseThemeFileContent(createTemplateThemeFile(), 'Template Theme') + parsed.app.accent = 'rgb(74, 222, 128)' + parsed.app.background = 'rgb(3, 7, 18)' + + const resolved = resolveTheme(parsed) + + assert.equal(resolved.spectrum.line, 'rgb(74, 222, 128)') + assert.equal(resolved.oscilloscope.line, 'rgb(74, 222, 128)') + assert.equal(resolved.vectorscope.trace, 'rgb(74, 222, 128)') + assert.equal(resolved.waveform.line, 'rgb(74, 222, 128)') + assert.equal(resolved.interface.scopeBackground, 'rgb(3, 7, 18)') + assert.equal(resolved.spectrum.background, 'rgb(3, 7, 18)') + assert.equal(resolved.nowPlaying.background, 'rgb(3, 7, 18)') }) test('resolveTheme exposes now playing button tokens and derived button states', () => { @@ -287,6 +306,7 @@ test('library seeds default themes and template file', async () => { assert.match(templateContent, /\[App\]/) assert.match(templateContent, /^\[Controls\]$/m) assert.match(templateContent, /^\[Scopes\]$/m) + assert.match(templateContent, /^# flat_controls = false$/m) } finally { await harness.cleanup() } @@ -352,6 +372,7 @@ test('library refreshes the managed template when its generated layout changes', assert.match(templateContent, /# Start with \[App\]\./) assert.match(templateContent, /^\[Controls\]$/m) assert.match(templateContent, /^\[Spectrum\]$/m) + assert.match(templateContent, /^# flat_controls = false$/m) } finally { await harness.cleanup() }