Files
DeepSeek-Reasonix/docs/theme-pack.schema.json
T
HaoyueQinandSivanCola a144e55e41 feat(desktop): add pane opacity control and fix base style revert on custom theme save / 新增面板透明度控制,修复自定义主题保存后基础风格回退 (#6645)
* feat(desktop): add pane opacity control and fix base style revert on custom theme save

New feature — per-scene pane opacity (面板透明度):

- ThemePackBackground / ThemePackSceneBackground gain a paneOpacity field
  (0-1) that controls how transparent the shell panels render when a theme
  pack is active. Home defaults to 0.50, task defaults to 0.68.
- Independent sliders in ThemeGallery editor for both scenes.
- applyBackgroundCSSVars pre-computes CSS variables (--theme-pane-alpha,
  --theme-pane-shell-pct, --theme-pane-task-*) for color-mix rules.
- CSS rules under :root[data-theme-pack] apply pane opacity to sidebar,
  topicbar, dock, statusbar, chat-pane, composer-card, and transcript
  with cascading opacity tiers (shell < card < inner).
- ThemePreviewSurface preview cards now reflect pane opacity.
- Task background opacity caps relaxed from 0.45 to 1.0 per user demand.
- All 8 official mock themes include paneOpacity: 0.50.
- Schema and docs (EN/ZH) updated with paneOpacity field.

Bug fix — base style revert on Save & apply:

When ThemeGallery.saveEditor(activate=true) called activateThemePack(),
the downstream onExperienceChange callback passed exp.baseStyle (the
config-file appearance, e.g. graphite) to the parent's onThemeStyle.
The parent then called applyTheme(theme, OLD_style) which overwrote
the pack's baseStyle (e.g. refined) that applyThemePack had just set
on the DOM.

Fixes:
- AppearanceOverview.onExperienceChange now syncs exp.effectiveStyle
  instead of exp.baseStyle, and no longer calls onTheme (which reads
  stale themeStyle from its closure).
- SettingsPanel.onThemeStyle now reads the current theme from the DOM
  via getTheme() instead of the React closure theme state.
- ThemeGallery.saveEditor moves cancelThemePreview() to only the
  non-activate branch; the activate path must not restore the preview
  snapshot after the new pack is applied.
- themeExperience.activateThemePack calls clearPreviewSnapshotOnly()
  first so endPreviewIfAny() cannot overwrite the newly activated pack.
- Layout transparency CSS selectors gain .app:not(.app--creation) to
  exclude the creation-mode surface.

Test contract:
- TestTaskOpacityCapped renamed to TestOpacityClamped, now validates
  the new 0-1 clamping and PaneOpacity defaults for both scenes.
- 'layout background transparent' contract test updated to a single
  flexible regex that tolerates intermediate selectors.
- i18n: taskBackgroundHint adjusted (removed stale 45% cap reference);
  new paneOpacity label in EN/ZH/ZH-TW.

* fix(desktop): preserve theme pane transparency state

Problem:
- Explicit 0% pane opacity was replaced by legacy defaults.
- Token-only theme packs received background transparency rules.
- Failed theme activation discarded the reversible preview snapshot.

Root cause:
- A scalar JSON field could not distinguish omission from an explicit zero.
- Extended pane selectors were scoped to any theme pack instead of packs with backgrounds.
- The preview snapshot was cleared before persistence succeeded.

Fix:
- Represent pane opacity as an optional persisted value and preserve explicit zero through normalization and JSON round trips.
- Guard background and pane transparency CSS with data-theme-has-bg.
- Commit preview state only after successful activation and add focused regressions.

Verification:
- cd desktop && go test ./...
- cd desktop/frontend && pnpm run test
- cd desktop/frontend && pnpm run build
- Browser DOM/CSS checks for token-only and background theme previews

* fix(desktop): separate theme base state from pack effects

Problem:
- Applying a theme pack mirrored its effective style into the configured base appearance, so direct theme reset restored the pack style.
- Pane opacity previews changed while live card surfaces remained fixed at 88%.

Root cause:
- The gallery callback reused the configured-style owner callback for a temporary effective style.
- Card-tier CSS variables were computed but not consumed, and preview offsets differed from live formulas.

Fix:
- Synchronize configured base style only when no active pack owns the effective style.
- Consume scene-specific card opacity variables in live surfaces and use the same formulas in previews.
- Remove unused pane variables and add regressions for direct reset and opacity consumers.

Verification:
- cd desktop/frontend && pnpm run test
- cd desktop/frontend && pnpm run build
- cd desktop && go test ./...
- Browser: Sage/Slate reset restored Graphite; live and preview card opacity matched scene variables

* fix(desktop): preserve theme preview lifecycle

Problem:
- Save and apply closed the theme editor before activation completed, allowing unmount cleanup to restore the old preview snapshot.
- Base and token-only gallery previews received background pane transparency that live surfaces do not apply.

Root cause:
- Editor teardown occurred before activateThemePack committed the preview.
- Preview opacity variables were derived without checking whether the pack had a background.

Fix:
- Activate or cancel the preview before closing the editor and reloading the gallery.
- Keep pane previews opaque when no theme background is present.
- Add regressions for lifecycle ordering and preview opacity.

Verification:
- cd desktop/frontend && pnpm run test
- cd desktop/frontend && pnpm run build
- cd desktop && go test ./...
- Browser preview check: base previews stayed opaque while background previews retained configured alpha

* fix(desktop): keep theme activation and previews consistent

Problem:
- Save and apply persisted activation twice, so a later activation failure could split persistent state from the reversible preview.
- Browser official-theme mocks used 50% pane opacity while embedded manifests fell back to legacy defaults.
- Legacy task scene payloads without pane opacity did not inherit the home setting.

Root cause:
- ThemeGallery delegated activation to SaveThemePack and then called the experience activation controller again.
- Official manifests omitted the new field, and the task fallback tested object presence instead of field presence.

Fix:
- Make the experience controller the sole owner of active-theme pointer updates after saving.
- Declare 50% pane opacity in every official manifest and verify Wails/browser parity.
- Use nullish field fallback for legacy task scenes and add regression coverage.

Verification:
- pnpm exec tsx src/__tests__/theme-pack.test.ts (230 passed)
- pnpm run typecheck
- pnpm run check:css
- pnpm run test
- pnpm run build
- go test ./...

---------

Co-authored-by: SivanCola <[email protected]>
2026-07-19 20:46:58 +08:00

110 lines
4.0 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://reasonix.dev/schemas/theme-pack-v2.json",
"title": "Reasonix Theme Pack V2",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "id", "name", "baseStyle"],
"properties": {
"schemaVersion": { "type": "integer", "enum": [1, 2] },
"id": {
"type": "string",
"pattern": "^[a-z]([a-z0-9-]{0,62}[a-z0-9])?$"
},
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
"author": { "type": "string", "maxLength": 240 },
"description": { "type": "string", "maxLength": 240 },
"license": { "type": "string", "maxLength": 240 },
"baseStyle": {
"type": "string",
"enum": ["graphite", "aurora", "slate", "carbon", "nocturne", "amber"]
},
"tokens": {
"type": "object",
"additionalProperties": false,
"properties": {
"light": { "$ref": "#/$defs/tokenMap" },
"dark": { "$ref": "#/$defs/tokenMap" }
}
},
"recipes": {
"type": "object",
"additionalProperties": false,
"properties": {
"density": { "type": "string", "enum": ["compact", "comfortable"] },
"corners": { "type": "string", "enum": ["square", "soft", "round"] }
}
},
"background": {
"type": "object",
"additionalProperties": false,
"properties": {
"image": {
"type": "string",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,120}\\.(png|jpe?g|webp)$"
},
"focusX": { "type": "number", "minimum": 0, "maximum": 1 },
"focusY": { "type": "number", "minimum": 0, "maximum": 1 },
"safeArea": { "type": "string", "enum": ["left", "right", "center"] },
"homeOpacity": { "type": "number", "minimum": 0, "maximum": 1 },
"taskOpacity": { "type": "number", "minimum": 0, "maximum": 1 },
"overlayStrength": { "type": "number", "minimum": 0, "maximum": 1 },
"paneOpacity": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"taskBackground": { "$ref": "#/$defs/sceneBackground" }
},
"allOf": [
{
"if": { "required": ["taskBackground"] },
"then": { "properties": { "schemaVersion": { "const": 2 } } }
}
],
"$defs": {
"hexColor": {
"type": "string",
"pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
},
"sceneBackground": {
"type": "object",
"additionalProperties": false,
"properties": {
"image": {
"type": "string",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,120}\\.(png|jpe?g|webp)$"
},
"focusX": { "type": "number", "minimum": 0, "maximum": 1 },
"focusY": { "type": "number", "minimum": 0, "maximum": 1 },
"safeArea": { "type": "string", "enum": ["left", "right", "center"] },
"opacity": { "type": "number", "minimum": 0, "maximum": 1 },
"overlayStrength": { "type": "number", "minimum": 0, "maximum": 1 },
"paneOpacity": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"tokenMap": {
"type": "object",
"additionalProperties": false,
"properties": {
"bg": { "$ref": "#/$defs/hexColor" },
"bgSoft": { "$ref": "#/$defs/hexColor" },
"bgElev": { "$ref": "#/$defs/hexColor" },
"panel": { "$ref": "#/$defs/hexColor" },
"sidebar": { "$ref": "#/$defs/hexColor" },
"chat": { "$ref": "#/$defs/hexColor" },
"workspace": { "$ref": "#/$defs/hexColor" },
"workspaceFiles": { "$ref": "#/$defs/hexColor" },
"border": { "$ref": "#/$defs/hexColor" },
"borderSoft": { "$ref": "#/$defs/hexColor" },
"fg": { "$ref": "#/$defs/hexColor" },
"fgDim": { "$ref": "#/$defs/hexColor" },
"fgFaint": { "$ref": "#/$defs/hexColor" },
"accent": { "$ref": "#/$defs/hexColor" },
"accentFg": { "$ref": "#/$defs/hexColor" },
"ok": { "$ref": "#/$defs/hexColor" },
"warn": { "$ref": "#/$defs/hexColor" },
"err": { "$ref": "#/$defs/hexColor" }
}
}
}
}