feat: 扩展主题界面适配与 Agent 服务工具

This commit is contained in:
2026-09-09 21:44:21 +08:00
parent fbd6ed5fa2
commit 2321ede1cd
26 changed files with 703 additions and 92 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
border: 1px solid color-mix(in srgb, var(--callout-color) 35%, transparent);
border-inline-start: 4px solid var(--callout-color);
border-radius: var(--radius-md, 8px);
background: color-mix(in srgb, var(--callout-color) 8%, var(--color-surface-primary, var(--paper, #fffdf7)));
background: color-mix(in srgb, var(--callout-color) 8%, var(--color-surface-primary));
color: var(--color-text-primary, var(--ink, inherit));
margin: 12px 0; padding: 12px 16px; min-width: 0;
}
+18 -2
View File
@@ -2,7 +2,7 @@
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { expect, it } from 'vitest'
import { getCommunityThemePreviewCss, mockCommunityThemes } from '@/services/themePackageService'
import { getCommunityThemePreviewCss, mockCommunityThemes, REQUIRED_THEME_COLOR_TOKENS, withThemeContract } from '@/services/themePackageService'
const root = join(process.cwd(), 'src')
const files = Object.fromEntries(readdirSync(root, { recursive: true }).map(String).filter(path => /\.(vue|css|ts|theme)$/.test(path)).map(path => [path, readFileSync(join(root, path), 'utf8')]))
it('resolves semantic style token references throughout the component source tree', () => {
@@ -17,12 +17,28 @@ it('resolves semantic style token references throughout the component source tre
})
it.each(mockCommunityThemes)('provides interaction and Markdown colors in $theme_id', theme => {
const css = getCommunityThemePreviewCss(theme.theme_id)
for (const token of ['accent-primary-active', 'accent-soft-hover', 'border-focus', 'text-inverse', 'markdown-grid', 'markdown-marker', 'markdown-table-header']) {
for (const token of REQUIRED_THEME_COLOR_TOKENS) {
expect(css).toContain(`--color-${token}:`)
}
expect(css).toContain(`color-scheme: ${theme.is_dark ? 'dark' : 'light'}`)
})
it.each([false, true])('fills an incomplete custom theme with the complete semantic contract (dark=%s)', isDark => {
const css = withThemeContract('minimal', isDark, '[data-theme="minimal"] { --color-accent-primary: hotpink; }')
for (const token of REQUIRED_THEME_COLOR_TOKENS) expect(css).toContain(`--color-${token}:`)
expect(css).toContain(`color-scheme: ${isDark ? 'dark' : 'light'}`)
expect(css.endsWith('--color-accent-primary: hotpink; }')).toBe(true)
})
it('keeps page and component styles on semantic colors', () => {
const rawColors: string[] = []
for (const [path, source] of Object.entries(files)) {
if (!path.endsWith('.vue') || path.endsWith('.spec.ts') || path.includes('features/themes/ThemesView.vue') || path.includes('features\\themes\\ThemesView.vue')) continue
for (const match of source.matchAll(/(?:#[\da-f]{3,8}|rgba?\([^)]*\))/gi)) rawColors.push(`${path}: ${match[0]}`)
}
expect(rawColors).toEqual([])
})
const calloutThemes = ['light', 'dark', 'sepia', ...mockCommunityThemes.map(theme => theme.theme_id)]
const calloutTones = ['info', 'success', 'warning', 'danger', 'important', 'quote']
it.each(calloutThemes)('keeps callout headings readable against tinted surfaces in %s', themeId => {
+8
View File
@@ -47,6 +47,12 @@
--color-error-soft: #ffebe9;
--color-info: #0969da;
--color-info-soft: #ddf4ff;
--color-on-error: #ffffff;
--color-on-success: #ffffff;
--color-brand-surface: #ffffff;
--color-brand-ink: #252b36;
--color-brand-surface-dark: #111111;
--color-highlight-overlay: #ffffff30;
/* Border */
--color-border-default: #e4e7eb;
@@ -128,6 +134,7 @@
}
[data-theme='light'] {
color-scheme: light;
--color-callout-info: #175da6;
--color-callout-success: #236b3b;
--color-callout-warning: #855700;
@@ -193,6 +200,7 @@
}
[data-theme='sepia'] {
color-scheme: light;
--color-callout-info: #396578;
--color-callout-success: #496b3b;
--color-callout-warning: #805918;