diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 3e477b75..7c5688de 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -45,10 +45,10 @@ jobs:
ln -sf "$PWD/DreamcoderShell/.config/fish/conf.d/05-dreamcoder-theme.fish" ~/.config/fish/conf.d/05-dreamcoder-theme.fish
# Deploy theme variant files from the repo, mirroring the real
# install (ghostty dual-mode and kitty theme switching reference
- # the -dark/-light/-night variants at runtime).
+ # the -dark/-light variants at runtime).
rm -rf ~/.config/ghostty/themes
ln -sfn "$PWD/DreamcoderGhostty/.config/ghostty/themes" ~/.config/ghostty/themes
- for f in colors-dreamcoder.conf colors-dreamcoder-dark.conf colors-dreamcoder-light.conf colors-dreamcoder-night.conf; do
+ for f in colors-dreamcoder.conf colors-dreamcoder-dark.conf colors-dreamcoder-light.conf; do
ln -sf "$PWD/DreamcoderKitty/.config/kitty/$f" ~/.config/kitty/$f
done
diff --git a/AGENTS.md b/AGENTS.md
index 519d96f0..3bd212ef 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,7 +41,7 @@ When editing documentation, follow these maintenance rules:
- Run `python scripts/validate-markdown-links.py` after doc changes (pre-commit does this automatically for staged files).
- Docs are English, user-facing guides in neutral professional English.
- Identity is always "Dreamcoder Workbench" (never "Dreamcoder OS" / "DreamcoderDots" / bare "dreamcoder-dots").
-- Color modes are `dark/light/night` (never "dusk" as a user-facing mode).
+- Color modes are `dark/light` (never "dusk" as a user-facing mode).
- One file = one purpose: do not duplicate content across docs — link to the source page.
- Update the smallest relevant page for a change; keep every doc reachable from [docs/README.md](docs/README.md).
- Commit doc changes with the `docs:` conventional commit type.
diff --git a/CLAUDE.md b/CLAUDE.md
index 8426a853..1078a863 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -55,9 +55,9 @@ focus #3B82F6
opacity 0.76
```
-Dark and Light are the user-facing modes. Night is a derived render profile of
-Dark; legacy Dusk remains a compatibility token set, and OLED behavior is defined
-by `modes.dark.surface_policy`.
+Dark and Light are the only user-facing modes. Legacy Dusk remains a
+compatibility token set, and OLED behavior is defined by
+`modes.dark.surface_policy`.
**Light — Cocoa/Lúcuma**
diff --git a/COMPARISON.md b/COMPARISON.md
index a23767ee..1fa13083 100644
--- a/COMPARISON.md
+++ b/COMPARISON.md
@@ -8,7 +8,7 @@
| Feature | Gentleman.Dots | ML4W | **Dreamcoder Workbench** |
| --------------------- | ---------------------- | ------------------- | ------------------------------ |
| **Theme Engine** | — Catppuccin static | ✓ Matugen dynamic | ✓ **Token-based + WCAG/APCA** |
-| **Dark/Light/Night** | — Dark only | ✓ Light/Dark | ✓ **+ night transition mode** |
+| **Dark/Light** | — Dark only | ✓ Light/Dark | ✓ **+ scheduled switching** |
| **Accessibility** | — | — | ✓ **WCAG 4.5:1 + APCA 75** |
| **AI Session Prompt** | — | — | ✓ **Live AI session state** |
| **Neovim Plugins** | ✓ 29 (LazyVim) | — | ◐ Dreamcoder colorscheme |
@@ -70,13 +70,12 @@ After installing Dreamcoder Workbench, everything Gentleman provides keeps worki
No other dotfiles setup does this. Every color is validated against WCAG 4.5:1 and APCA.
-### 3 Color Modes
+### 2 Color Modes
| Mode | Name | Schedule |
| ------- | ----------------- | -------------- |
| 🌙 Dark | Anthracite Steel | 18:00-07:00 |
-| ☀️ Light| Cocoa/Lúcuma | 07:00-16:00 |
-| 🌆 Night| Low-light transition | 16:00-18:00 |
+| ☀️ Light| Cocoa/Lúcuma | 07:00-18:00 |
### AI Session State in the Prompt
@@ -139,7 +138,7 @@ Without Dreamcoder Workbench:
┌─────────────────────────────────────────────┐
│ Gentleman.Dots: Catppuccin │
│ ML4W: Matugen colors │
-│ No WCAG, no AI prompt, no night mode │
+│ No WCAG, no AI prompt, no auto switching │
└─────────────────────────────────────────────┘
With Dreamcoder Workbench:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 81e3955d..752996af 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,7 +17,7 @@ Need a smaller first step? Pick an open issue, fix a docs typo, or add a rendere
```text
src/dreamcoder_theme/ # Python theme engine (pip-installable package)
├── palette.py # Color math, adaptive palette derivation
- ├── palette_tokens.py # Static design tokens (dark/light/night)
+ ├── palette_tokens.py # Static design tokens (dark/light/dusk)
├── renderers.py # Public renderer import hub
├── renderers_kitty.py # Kitty format renderer
├── renderers_ghostty_warp.py
@@ -51,7 +51,7 @@ src/dreamcoder_theme/ # Python theme engine (pip-installable package)
├── backups.py # Theme backup/restore
├── audit.py # Theme audit & validation
├── docs_report.py # Documentation health report
- ├── motion.py # Day/night mode scheduler
+ ├── motion.py # Motion preset scheduler
├── tui.py # Terminal UI
├── visual_regression.py # Visual regression testing
└── settings_store.py # Settings persistence
@@ -201,7 +201,7 @@ Documentation changes are reviewed like code changes.
- [ ] Run `python scripts/validate-markdown-links.py` and fix any broken links.
- [ ] Update the smallest relevant page; do not duplicate content that already lives in another doc — link to it instead.
- [ ] The changed page is reachable from the [docs index](docs/README.md) or the README documentation table; add it there if it is a new page.
-- [ ] Keep the identity string "Dreamcoder Workbench" and mode names `dark/light/night` consistent with the rest of the docs.
+- [ ] Keep the identity string "Dreamcoder Workbench" and mode names `dark/light` consistent with the rest of the docs.
- [ ] Commit with the `docs:` conventional commit type.
Do not duplicate references: link to the source page for detailed behavior.
diff --git a/DreamcoderAntigravity/Dreamcoder-Night.json b/DreamcoderAntigravity/Dreamcoder-Night.json
deleted file mode 100644
index 1caae53a..00000000
--- a/DreamcoderAntigravity/Dreamcoder-Night.json
+++ /dev/null
@@ -1,119 +0,0 @@
-{
- "name": "Dreamcoder Dark Night",
- "type": "dark",
- "colors": {
- "editor.background": "#000000",
- "editor.foreground": "#c6c6c6",
- "activityBar.background": "#090909",
- "activityBar.foreground": "#a4afee",
- "activityBar.inactiveForeground": "#b3b3b3",
- "activityBar.border": "#797979",
- "sideBar.background": "#090909",
- "sideBar.foreground": "#c6c6c6",
- "sideBar.border": "#797979",
- "statusBar.background": "#000000",
- "statusBar.foreground": "#c6c6c6",
- "statusBar.border": "#797979",
- "editorGroupHeader.tabsBackground": "#090909",
- "tab.activeBackground": "#000000",
- "tab.activeForeground": "#a4afee",
- "tab.inactiveBackground": "#090909",
- "tab.inactiveForeground": "#b3b3b3",
- "tab.border": "#797979",
- "editor.lineHighlightBackground": "#090909",
- "editorLineNumber.foreground": "#b3b3b3",
- "editorLineNumber.activeForeground": "#a4afee",
- "editorWidget.background": "#0b0b0d",
- "editorWidget.border": "#797979",
- "input.background": "#0b0b0d",
- "input.foreground": "#c6c6c6",
- "input.border": "#3a76d6",
- "button.background": "#b48ce9",
- "button.foreground": "#000000",
- "list.activeSelectionBackground": "#0b0b0d",
- "list.activeSelectionForeground": "#c6c6c6",
- "list.hoverBackground": "#090909",
- "editor.selectionBackground": "#0b0b0d",
- "terminal.background": "#000000",
- "terminal.foreground": "#c6c6c6",
- "terminal.ansiBlack": "#090909",
- "terminal.ansiRed": "#e88484",
- "terminal.ansiGreen": "#43b48b",
- "terminal.ansiYellow": "#d1a126",
- "terminal.ansiBlue": "#a4afee",
- "terminal.ansiMagenta": "#b98beb",
- "terminal.ansiCyan": "#62b9e2",
- "terminal.ansiWhite": "#c6c6c6"
- },
- "tokenColors": [
- {
- "scope": [
- "comment",
- "punctuation.definition.comment"
- ],
- "settings": {
- "foreground": "#b3b3b3",
- "fontStyle": "italic"
- }
- },
- {
- "scope": [
- "keyword",
- "storage.type",
- "storage.modifier",
- "keyword.operator"
- ],
- "settings": {
- "foreground": "#a4afee",
- "fontStyle": "bold"
- }
- },
- {
- "scope": [
- "entity.name.function",
- "support.function",
- "entity.name.method"
- ],
- "settings": {
- "foreground": "#62b9e2"
- }
- },
- {
- "scope": [
- "string",
- "punctuation.definition.string"
- ],
- "settings": {
- "foreground": "#43b48b"
- }
- },
- {
- "scope": [
- "constant.numeric",
- "constant.language"
- ],
- "settings": {
- "foreground": "#b48ce9"
- }
- },
- {
- "scope": [
- "support.type",
- "entity.name.type",
- "entity.name.class"
- ],
- "settings": {
- "foreground": "#b48ce9"
- }
- },
- {
- "scope": [
- "variable",
- "meta.definition.variable"
- ],
- "settings": {
- "foreground": "#c6c6c6"
- }
- }
- ]
-}
diff --git a/DreamcoderBat/.config/bat/themes/Dreamcoder-Night.tmTheme b/DreamcoderBat/.config/bat/themes/Dreamcoder-Night.tmTheme
deleted file mode 100644
index d43863aa..00000000
--- a/DreamcoderBat/.config/bat/themes/Dreamcoder-Night.tmTheme
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
- nameDreamcoder
- settings
-
- settings
- background#000000
- foreground#c6c6c6
- caret#a4afee
- selection#323232
- selectionForeground#c6c6c6
- lineHighlight#0b0b0d
- gutter#1b1b1b
- gutterForeground#ababab
- invisibles#b3b3b3
-
- scopecommentsettingsforeground#b3b3b3fontStyleitalic
- scopekeyword, storagesettingsforeground#a4afeefontStylebold
- scopeentity.name.function, support.functionsettingsforeground#b48ce9
- scopevariable, meta.definition.variablesettingsforeground#ababab
- scopestringsettingsforeground#43b48b
- scopeconstant.numeric, constant.languagesettingsforeground#b98beb
- scopeentity.name.type, support.typesettingsforeground#62b9e2
- scopepunctuationsettingsforeground#ababab
- scopekeyword.operatorsettingsforeground#b48ce9
- scopeconstant.character.escapesettingsforeground#43b48b
- scopeentity.name.tagsettingsforeground#a4afee
- scopeentity.other.attribute-namesettingsforeground#66bae1
- scopeinvalidsettingsforeground#e88484
- scopemarkup.heading, markup.boldsettingsforeground#a4afeefontStylebold
- scopemarkup.italicsettingsforeground#62b9e2fontStyleitalic
- scopemeta.diff, meta.diff.headersettingsforeground#b48ce9
- scopesupport.class, support.constantsettingsforeground#62b9e2
-
-
-
diff --git a/DreamcoderCodexApp/Dreamcoder-Night.codex-theme.json b/DreamcoderCodexApp/Dreamcoder-Night.codex-theme.json
deleted file mode 100644
index f665e799..00000000
--- a/DreamcoderCodexApp/Dreamcoder-Night.codex-theme.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
- "$schema": "https://opencode.ai/theme.json",
- "defs": {
- "dreamBackground": "#000000",
- "dreamPanel": "#090909",
- "dreamElement": "#090909",
- "dreamText": "#c6c6c6",
- "dreamMuted": "#ababab",
- "dreamCocoa": "#b48ce9",
- "dreamLucuma": "#a4afee",
- "dreamDiagnostic": "#62b9e2",
- "dreamSage": "#43b48b",
- "dreamViolet": "#b48ce9",
- "dreamMauve": "#b98beb",
- "dreamCoral": "#e88484",
- "dreamWarning": "#d1a126"
- },
- "theme": {
- "background": "#000000",
- "backgroundPanel": "#0b0b0d",
- "backgroundElement": "#0b0b0d",
- "backgroundHover": "#323232",
- "backgroundSelected": "#323232",
- "textSelected": "#c6c6c6",
- "backgroundCode": "#0b0b0d",
- "backgroundSearch": "#795d16",
- "backgroundLine": "#0b0b0d",
- "backgroundAssistant": "#0c161b",
- "backgroundUser": "#14151d",
- "backgroundTool": "#16111c",
- "text": "#c6c6c6",
- "textMuted": "#ababab",
- "textSubtle": "#959595",
- "textPlaceholder": "#b3b3b3",
- "textAssistant": "#74bbdd",
- "textUser": "#a9b2e8",
- "textTool": "#62b9e2",
- "primary": "#a4afee",
- "secondary": "#b48ce9",
- "accent": "#a4afee",
- "accentMuted": "#7b83b2",
- "error": "#e88484",
- "warning": "#d1a126",
- "success": "#43b48b",
- "info": "#62b9e2",
- "border": "#797979",
- "borderActive": "#a4afee",
- "borderSubtle": "#5c5c5c",
- "borderFocus": "#3a76d6",
- "shadow": "#000000",
- "diffAdded": "#43b48b",
- "diffRemoved": "#e88484",
- "diffContext": "#ababab",
- "diffHunkHeader": "#b48ce9",
- "diffHighlightAdded": "#43b48b",
- "diffHighlightRemoved": "#e88484",
- "diffAddedBg": "#284a3e",
- "diffRemovedBg": "#5a3c3c",
- "diffContextBg": "#000000",
- "diffLineNumber": "#959595",
- "diffAddedLineNumberBg": "#284a3e",
- "diffRemovedLineNumberBg": "#5a3c3c",
- "diffHunkHeaderBg": "#514561",
- "diffFold": "#b3b3b3",
- "diffFoldBg": "#0b0b0d",
- "markdownText": "#c6c6c6",
- "markdownHeading": "#a4afee",
- "markdownLink": "#62b9e2",
- "markdownLinkText": "#a4afee",
- "markdownCode": "#43b48b",
- "markdownBlockQuote": "#b48ce9",
- "markdownEmph": "#62b9e2",
- "markdownStrong": "#a4afee",
- "markdownHorizontalRule": "#797979",
- "markdownListItem": "#a4afee",
- "markdownListEnumeration": "#b48ce9",
- "markdownTableBorder": "#797979",
- "markdownTableHeader": "#b48ce9",
- "markdownImage": "#b98beb",
- "markdownImageText": "#c6c6c6",
- "markdownCodeBlock": "#c6c6c6",
- "markdownCodeBlockBg": "#0b0b0d",
- "markdownInlineCodeBg": "#5b9983",
- "syntaxComment": "#b3b3b3",
- "syntaxKeyword": "#a4afee",
- "syntaxFunction": "#b48ce9",
- "syntaxMethod": "#b48ce9",
- "syntaxVariable": "#ababab",
- "syntaxParameter": "#b58fe7",
- "syntaxProperty": "#66bae1",
- "syntaxField": "#4ab58e",
- "syntaxString": "#43b48b",
- "syntaxNumber": "#ad9fed",
- "syntaxBoolean": "#b98beb",
- "syntaxConstant": "#b98beb",
- "syntaxType": "#62b9e2",
- "syntaxClass": "#6eb7e4",
- "syntaxInterface": "#62b9e2",
- "syntaxEnum": "#5cb8d2",
- "syntaxOperator": "#b48ce9",
- "syntaxPunctuation": "#ababab",
- "syntaxTag": "#a4afee",
- "syntaxAttribute": "#66bae1",
- "syntaxRegexp": "#c689ce",
- "syntaxEscape": "#d1a126",
- "syntaxNamespace": "#62b9e2",
- "syntaxModule": "#62b9e2",
- "syntaxDecorator": "#b48ce9",
- "syntaxBuiltin": "#b98beb",
- "syntaxSpecial": "#d1a126",
- "syntaxTodo": "#d0a539",
- "syntaxDeprecated": "#d78f8f",
- "terminalBlack": "#777777",
- "terminalRed": "#e88484",
- "terminalGreen": "#43b48b",
- "terminalYellow": "#d1a126",
- "terminalBlue": "#62b9e2",
- "terminalMagenta": "#b98beb",
- "terminalCyan": "#74bbdd",
- "terminalWhite": "#c6c6c6",
- "terminalBrightBlack": "#959595",
- "terminalBrightRed": "#e29090",
- "terminalBrightGreen": "#5bb796",
- "terminalBrightYellow": "#cfa740",
- "terminalBrightBlue": "#74bbdd",
- "terminalBrightMagenta": "#bb96e4",
- "terminalBrightCyan": "#b796e3",
- "terminalBrightWhite": "#c6c6c6"
- }
-}
diff --git a/DreamcoderCodexCLI/Dreamcoder-Night.tmTheme b/DreamcoderCodexCLI/Dreamcoder-Night.tmTheme
deleted file mode 100644
index d43863aa..00000000
--- a/DreamcoderCodexCLI/Dreamcoder-Night.tmTheme
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
- nameDreamcoder
- settings
-
- settings
- background#000000
- foreground#c6c6c6
- caret#a4afee
- selection#323232
- selectionForeground#c6c6c6
- lineHighlight#0b0b0d
- gutter#1b1b1b
- gutterForeground#ababab
- invisibles#b3b3b3
-
- scopecommentsettingsforeground#b3b3b3fontStyleitalic
- scopekeyword, storagesettingsforeground#a4afeefontStylebold
- scopeentity.name.function, support.functionsettingsforeground#b48ce9
- scopevariable, meta.definition.variablesettingsforeground#ababab
- scopestringsettingsforeground#43b48b
- scopeconstant.numeric, constant.languagesettingsforeground#b98beb
- scopeentity.name.type, support.typesettingsforeground#62b9e2
- scopepunctuationsettingsforeground#ababab
- scopekeyword.operatorsettingsforeground#b48ce9
- scopeconstant.character.escapesettingsforeground#43b48b
- scopeentity.name.tagsettingsforeground#a4afee
- scopeentity.other.attribute-namesettingsforeground#66bae1
- scopeinvalidsettingsforeground#e88484
- scopemarkup.heading, markup.boldsettingsforeground#a4afeefontStylebold
- scopemarkup.italicsettingsforeground#62b9e2fontStyleitalic
- scopemeta.diff, meta.diff.headersettingsforeground#b48ce9
- scopesupport.class, support.constantsettingsforeground#62b9e2
-
-
-
diff --git a/DreamcoderFastfetch/.config/dreamcoder/settings.json b/DreamcoderFastfetch/.config/dreamcoder/settings.json
index 60947351..4fc4e027 100644
--- a/DreamcoderFastfetch/.config/dreamcoder/settings.json
+++ b/DreamcoderFastfetch/.config/dreamcoder/settings.json
@@ -1,8 +1,5 @@
{
"terminal": {
"default_mode": "dark"
- },
- "theme": {
- "render_profile": "standard"
}
}
diff --git a/DreamcoderGhostty/.config/ghostty/themes/dreamcoder-night b/DreamcoderGhostty/.config/ghostty/themes/dreamcoder-night
deleted file mode 100644
index c766ea77..00000000
--- a/DreamcoderGhostty/.config/ghostty/themes/dreamcoder-night
+++ /dev/null
@@ -1,27 +0,0 @@
-# Dreamcoder Dark Night
-background = #000000
-foreground = #c6c6c6
-cursor-color = #a4afee
-cursor-text = #000000
-selection-background = #323232
-selection-foreground = #c6c6c6
-background-opacity = 0.76
-background-blur = true
-minimum-contrast = 4.5
-
-palette = 0=#777777
-palette = 1=#e88484
-palette = 2=#43b48b
-palette = 3=#d1a126
-palette = 4=#62b9e2
-palette = 5=#b98beb
-palette = 6=#b48ce9
-palette = 7=#ababab
-palette = 8=#959595
-palette = 9=#e29090
-palette = 10=#5bb796
-palette = 11=#cfa843
-palette = 12=#74bbdd
-palette = 13=#bb96e4
-palette = 14=#5384d3
-palette = 15=#c6c6c6
diff --git a/DreamcoderHerdr/.config/herdr/dreamcoder/0.7.3/config.night.toml b/DreamcoderHerdr/.config/herdr/dreamcoder/0.7.3/config.night.toml
deleted file mode 100644
index 414e7494..00000000
--- a/DreamcoderHerdr/.config/herdr/dreamcoder/0.7.3/config.night.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-# Managed by Dreamcoder; repository variant only.
-[theme]
-name = "catppuccin"
-
-[theme.custom]
-accent = "#a4afee"
-panel_bg = "#000000"
-surface0 = "#090909"
-surface1 = "#0b0b0d"
-surface_dim = "#090909"
-overlay0 = "#797979"
-overlay1 = "#959595"
-text = "#c6c6c6"
-subtext0 = "#ababab"
-mauve = "#b98beb"
-green = "#43b48b"
-yellow = "#d1a126"
-red = "#e88484"
-blue = "#62b9e2"
-teal = "#3a76d6"
-peach = "#b48ce9"
-
-[ui]
-accent = "#6FA0AF"
-
-[keys]
-prefix = "ctrl+a"
-previous_agent = "prefix+alt+k"
-next_agent = "prefix+alt+j"
-focus_agent = "prefix+ctrl+1..9"
diff --git a/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.0/config.night.toml b/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.0/config.night.toml
deleted file mode 100644
index f4f6ac26..00000000
--- a/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.0/config.night.toml
+++ /dev/null
@@ -1,31 +0,0 @@
-# Managed by Dreamcoder; repository variant only.
-[theme]
-name = "catppuccin"
-
-[theme.custom]
-accent = "#a4afee"
-panel_bg = "#000000"
-surface0 = "#090909"
-surface1 = "#0b0b0d"
-surface_dim = "#090909"
-overlay0 = "#797979"
-overlay1 = "#959595"
-text = "#c6c6c6"
-subtext0 = "#ababab"
-mauve = "#b98beb"
-green = "#43b48b"
-yellow = "#d1a126"
-red = "#e88484"
-blue = "#62b9e2"
-teal = "#3a76d6"
-peach = "#b48ce9"
-
-[ui]
-accent = "#6FA0AF"
-pane_scrollbars = false
-
-[keys]
-prefix = "ctrl+a"
-previous_agent = "prefix+alt+k"
-next_agent = "prefix+alt+j"
-focus_agent = "prefix+ctrl+1..9"
diff --git a/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.2/config.night.toml b/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.2/config.night.toml
deleted file mode 100644
index 57e50f0b..00000000
--- a/DreamcoderHerdr/.config/herdr/dreamcoder/0.8.2/config.night.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-# Managed by Dreamcoder; repository variant only.
-[theme]
-name = "catppuccin"
-
-[theme.custom]
-accent = "#a4afee"
-panel_bg = "#000000"
-surface0 = "#090909"
-surface1 = "#0b0b0d"
-surface_dim = "#090909"
-overlay0 = "#797979"
-overlay1 = "#959595"
-text = "#c6c6c6"
-subtext0 = "#ababab"
-mauve = "#b98beb"
-green = "#43b48b"
-yellow = "#d1a126"
-red = "#e88484"
-blue = "#62b9e2"
-teal = "#3a76d6"
-peach = "#b48ce9"
-sidebar_bg = "#000000"
-active_row_bg = "#090909"
-selection_bg = "#323232"
-
-[ui]
-accent = "#6FA0AF"
-pane_scrollbars = false
-
-[keys]
-prefix = "ctrl+a"
-previous_agent = "prefix+alt+k"
-next_agent = "prefix+alt+j"
-focus_agent = "prefix+ctrl+1..9"
diff --git a/DreamcoderHerdr/.config/herdr/dreamcoder/0.9.1/config.night.toml b/DreamcoderHerdr/.config/herdr/dreamcoder/0.9.1/config.night.toml
deleted file mode 100644
index 57e50f0b..00000000
--- a/DreamcoderHerdr/.config/herdr/dreamcoder/0.9.1/config.night.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-# Managed by Dreamcoder; repository variant only.
-[theme]
-name = "catppuccin"
-
-[theme.custom]
-accent = "#a4afee"
-panel_bg = "#000000"
-surface0 = "#090909"
-surface1 = "#0b0b0d"
-surface_dim = "#090909"
-overlay0 = "#797979"
-overlay1 = "#959595"
-text = "#c6c6c6"
-subtext0 = "#ababab"
-mauve = "#b98beb"
-green = "#43b48b"
-yellow = "#d1a126"
-red = "#e88484"
-blue = "#62b9e2"
-teal = "#3a76d6"
-peach = "#b48ce9"
-sidebar_bg = "#000000"
-active_row_bg = "#090909"
-selection_bg = "#323232"
-
-[ui]
-accent = "#6FA0AF"
-pane_scrollbars = false
-
-[keys]
-prefix = "ctrl+a"
-previous_agent = "prefix+alt+k"
-next_agent = "prefix+alt+j"
-focus_agent = "prefix+ctrl+1..9"
diff --git a/DreamcoderKitty/.config/kitty/colors-dreamcoder-night.conf b/DreamcoderKitty/.config/kitty/colors-dreamcoder-night.conf
deleted file mode 100644
index 57d4fd45..00000000
--- a/DreamcoderKitty/.config/kitty/colors-dreamcoder-night.conf
+++ /dev/null
@@ -1,52 +0,0 @@
-# ==========================================================
-# Dreamcoder Dark Night
-# ==========================================================
-# Color-only theme layer. Keep ML4W/Gentleman behavior elsewhere.
-
-foreground #c6c6c6
-background #000000
-selection_foreground #c6c6c6
-selection_background #323232
-url_color #a4afee
-
-cursor #a4afee
-cursor_text_color #000000
-cursor_shape block
-cursor_blink_interval 0.5
-cursor_stop_blinking_after 15.0
-
-active_tab_foreground #000000
-active_tab_background #a4afee
-inactive_tab_foreground #ababab
-inactive_tab_background #000000
-tab_bar_background #000000
-
-active_border_color #a4afee
-inactive_border_color #5c5c5c
-bell_border_color #e88484
-
-color0 #777777
-color1 #e88484
-color2 #43b48b
-color3 #d1a126
-color4 #62b9e2
-color5 #b98beb
-color6 #b48ce9
-color7 #ababab
-color8 #959595
-color9 #e29090
-color10 #5bb796
-color11 #cfa843
-color12 #74bbdd
-color13 #bb96e4
-color14 #5384d3
-color15 #c6c6c6
-color16 #b48ce9
-color17 #e88484
-
-mark1_foreground #000000
-mark1_background #a4afee
-mark2_foreground #000000
-mark2_background #62b9e2
-mark3_foreground #000000
-mark3_background #b98beb
diff --git a/DreamcoderKitty/.config/kitty/dreamcoder-ui-night.conf b/DreamcoderKitty/.config/kitty/dreamcoder-ui-night.conf
deleted file mode 100644
index 45e53f9f..00000000
--- a/DreamcoderKitty/.config/kitty/dreamcoder-ui-night.conf
+++ /dev/null
@@ -1,51 +0,0 @@
-# Dreamcoder Kitty UI parity layer
-# Loaded last so ML4W can keep behavior while Dreamcoder owns readability.
-# Dark mode: glass blur (24px) with 76% opacity for depth.
-# Light mode: opaque paper (96%) for maximum text legibility.
-# text_composition_strategy:
-# gamma (1st val): thickness of dark text on light bg (default 1.0 on Linux).
-# contrast (2nd val): extra contrast %, 0-100 (default 0 on Linux).
-# Higher gamma = thicker text on light themes; contrast helps both modes.
-
-font_family JetBrainsMono Nerd Font
-bold_font auto
-italic_font auto
-bold_italic_font auto
-font_size 14
-disable_ligatures cursor
-symbol_map U+E000-U+F8FF,U+F0000-U+10FFFF Symbols Nerd Font
-box_drawing_scale 0.001, 1, 1.5, 2
-# gamma 1.0 + 15% contrast = thicker, sharper text matching Ghostty
-text_composition_strategy 1.0 15
-shell fish --login
-
-window_padding_width 20 18 20 18
-single_window_padding_width -1
-placement_strategy center
-initial_window_width 1180
-initial_window_height 780
-background_opacity 0.76
-dynamic_background_opacity no
-background_blur 24
-
-tab_bar_edge top
-tab_bar_style fade
-tab_bar_min_tabs 2
-tab_bar_margin_color none
-active_tab_font_style bold
-inactive_tab_font_style normal
-
-shell_integration enabled no-cursor
-mouse_hide_wait 2.0
-copy_on_select clipboard
-dim_opacity 0.65
-inactive_text_alpha 0.92
-
-# Dreamcoder terminal shortcuts.
-map ctrl+l clear_terminal reset active
-map ctrl+shift+l clear_terminal scrollback active
-map ctrl+backspace send_text all \x17
-map ctrl+delete send_text all \ed
-
-# Dreamcoder Motion: ML4W-inspired cursor trail, kept local and portable.
-cursor_trail 1
diff --git a/DreamcoderLazygit/.config/lazygit/config.night.yml b/DreamcoderLazygit/.config/lazygit/config.night.yml
deleted file mode 100644
index c5644330..00000000
--- a/DreamcoderLazygit/.config/lazygit/config.night.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-# Dreamcoder Dark Night — Lazygit theme
-# https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md
-
-gui:
- theme:
- activeBorderColor:
- - "#a4afee" # accent
- - "bold"
- inactiveBorderColor:
- - "#5c5c5c" # border
- searchingActiveBorderColor:
- - "#b48ce9" # accent-2
- - "bold"
- optionsTextColor:
- - "#62b9e2" # diagnostic
- selectedLineBgColor:
- - "#323232" # selection
- inactiveViewSelectedLineBgColor:
- - "#1b1b1b" # surface-2
- cherryPickedCommitFgColor:
- - "#62b9e2"
- cherryPickedCommitBgColor:
- - "#b48ce9"
- markedBaseCommitFgColor:
- - "#62b9e2"
- markedBaseCommitBgColor:
- - "#a4afee"
- unstagedChangesColor:
- - "#e88484" # error
- defaultFgColor:
- - "#c6c6c6" # text
- authorColors:
- "#a4afee": "#000000"
- "#b48ce9": "#000000"
- "#62b9e2": "#000000"
- "#43b48b": "#000000"
- "#b98beb": "#000000"
- showFileTree: true
- showRandomTip: false
- nerdFontsVersion: "3"
- sidePanelWidth: 0.3
- expandFocusedSidePanel: true
- mainPanelSplitMode: "flexible"
- language: "en"
-git:
- diffRenderers:
- - colorArg: always
- command: delta --syntax-theme "Catppuccin Mocha" --paging=never
- merging:
- manualCommit: false
- skipHookPrefix: WIP
- autoFetch: true
- autoRefresh: true
- branchLogCmd: "git log --graph --pretty=format:'%C(#a4afee)%h%Creset -%C(#b48ce9)%d%Creset %s %C(#b3b3b3)(%cr) %C(#62b9e2)<%an>%Creset' --date=relative"
-update:
- method: never
diff --git a/DreamcoderNvim/.config/nvim/colors/dreamcoder-night.lua b/DreamcoderNvim/.config/nvim/colors/dreamcoder-night.lua
deleted file mode 100644
index 8b6d6876..00000000
--- a/DreamcoderNvim/.config/nvim/colors/dreamcoder-night.lua
+++ /dev/null
@@ -1,1263 +0,0 @@
--- ========================================================
--- Dreamcoder Dark Night — generated by Dreamcoder sync
--- ========================================================
--- Loaded by the dreamcoder dispatcher (colors/dreamcoder.lua).
--- Can also be loaded directly: vim.cmd.colorscheme("dreamcoder-night")
--- ========================================================
-
-vim.g.colors_name = "dreamcoder-night"
-
-local c = {
- bg = "#000000",
- surface0 = "#090909",
- surface1 = "#0b0b0d",
- surface2 = "#1b1b1b",
- text = "#c6c6c6",
- muted = "#ababab",
- subtle = "#959595",
- comment = "#b3b3b3",
- accent = "#a4afee",
- accent2 = "#b48ce9",
- diagnostic = "#62b9e2",
- sage = "#43b48b",
- lavender = "#b48ce9",
- mauve = "#b98beb",
- error = "#e88484",
- warning = "#d1a126",
- border = "#5c5c5c",
- border_ui = "#797979",
- selection = "#323232",
-}
-
-local function h(name, opts)
- vim.api.nvim_set_hl(0, name, opts)
-end
-
--- ── Editor UI ────────────────────────────────────────────────
--- Normal background is opaque in light so dark terminal transparency cannot hide text.
--- Dreamcoder Dark keeps transparent Normal for the glass effect.
-
- vim.api.nvim_set_hl(0, "Normal", {
- fg = "#c6c6c6",
- bg = "none"
- })
-
- vim.api.nvim_set_hl(0, "NormalFloat", {
- fg = "#c6c6c6",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "FloatBorder", {
- fg = "#797979",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "FloatTitle", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "NonText", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "SpecialKey", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "Whitespace", {
- fg = "#797979"
- })
-
- vim.api.nvim_set_hl(0, "EndOfBuffer", {
- fg = "none"
- })
-
- vim.api.nvim_set_hl(0, "Cursor", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "lCursor", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "CursorLine", {
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "CursorLineNr", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "LineNr", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "CursorColumn", {
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "ColorColumn", {
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "SignColumn", {
- bg = "none"
- })
-
-
- -- ── Selection & Search ───────────────────────────────
- vim.api.nvim_set_hl(0, "Visual", {
- fg = "#c6c6c6",
- bg = "#323232"
- })
-
- vim.api.nvim_set_hl(0, "VisualNOS", {
- fg = "#c6c6c6",
- bg = "#323232"
- })
-
- vim.api.nvim_set_hl(0, "Search", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "IncSearch", {
- fg = "#000000",
- bg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CurSearch", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Substitute", {
- fg = "#000000",
- bg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "MatchParen", {
- fg = "#b48ce9",
- bold = true
- })
-
-
- -- ── Popup Menu ────────────────────────────────────────
- vim.api.nvim_set_hl(0, "Pmenu", {
- fg = "#ababab",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "PmenuSel", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "PmenuSbar", {
- bg = "#1b1b1b"
- })
-
- vim.api.nvim_set_hl(0, "PmenuThumb", {
- bg = "#ababab"
- })
-
-
- -- ── Statusline & Winbar ───────────────────────────────
- vim.api.nvim_set_hl(0, "StatusLine", {
- fg = "#c6c6c6",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "StatusLineNC", {
- fg = "#ababab",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "StatusLineTerm", {
- fg = "#c6c6c6",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "StatusLineTermNC", {
- fg = "#ababab",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "WinBar", {
- fg = "#c6c6c6",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "WinBarNC", {
- fg = "#ababab",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "WinSeparator", {
- fg = "#797979"
- })
-
-
- -- ── Tabline ────────────────────────────────────────────
- vim.api.nvim_set_hl(0, "TabLine", {
- fg = "#ababab",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "TabLineSel", {
- fg = "#c6c6c6",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "TabLineFill", {
- bg = "#000000"
- })
-
-
- -- ── Folds ─────────────────────────────────────────────
- vim.api.nvim_set_hl(0, "Folded", {
- fg = "#ababab",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "FoldColumn", {
- fg = "#ababab"
- })
-
-
- -- ── Diff ──────────────────────────────────────────────
- vim.api.nvim_set_hl(0, "DiffAdd", {
- bg = "#1a3c30"
- })
-
- vim.api.nvim_set_hl(0, "DiffChange", {
- bg = "#453712"
- })
-
- vim.api.nvim_set_hl(0, "DiffDelete", {
- bg = "#4c2e2e"
- })
-
- vim.api.nvim_set_hl(0, "DiffText", {
- bg = "#1a2f51"
- })
-
-
- -- ── Spell ─────────────────────────────────────────────
- vim.api.nvim_set_hl(0, "SpellBad", {
- sp = "#e88484",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "SpellCap", {
- sp = "#d1a126",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "SpellLocal", {
- sp = "#62b9e2",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "SpellRare", {
- sp = "#b98beb",
- undercurl = true
- })
-
-
- -- ── Messages ──────────────────────────────────────────
- vim.api.nvim_set_hl(0, "MsgArea", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "ModeMsg", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "WarningMsg", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "ErrorMsg", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "Question", {
- fg = "#62b9e2"
- })
-
-
- -- ── Misc ──────────────────────────────────────────────
- vim.api.nvim_set_hl(0, "Title", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "Directory", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "Conceal", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "TermCursor", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
-
- -- ── Classic Syntax ────────────────────────────────────
- vim.api.nvim_set_hl(0, "Comment", {
- fg = "#b3b3b3",
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "Constant", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "String", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "Character", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "Number", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "Boolean", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "Float", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "Identifier", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "Function", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "Statement", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "Conditional", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Repeat", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Label", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Operator", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "Keyword", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Exception", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "PreProc", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "Include", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "Define", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "PreCondit", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "Type", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "StorageClass", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "Structure", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "Typedef", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "Special", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "SpecialChar", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "Delimiter", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "SpecialComment", {
- fg = "#b3b3b3"
- })
-
- vim.api.nvim_set_hl(0, "Debug", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "Underlined", {
- fg = "#62b9e2",
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "Ignore", {
- fg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "Error", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "Todo", {
- fg = "#d1a126",
- bold = true
- })
-
-
- -- ── Treesitter ────────────────────────────────────────
- vim.api.nvim_set_hl(0, "@variable", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "@variable.builtin", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@variable.parameter", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "@variable.member", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "@constant", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "@constant.builtin", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@constant.macro", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@module", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@module.builtin", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@label", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@string", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "@string.documentation", {
- fg = "#43b48b",
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "@string.regex", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "@string.escape", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "@character", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "@character.special", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@number", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "@boolean", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@float", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "@function", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@function.builtin", {
- fg = "#b48ce9",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@function.call", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@function.macro", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@function.method", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@function.method.call", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@parameter", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "@method", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@field", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@property", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@constructor", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@conditional", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@repeat", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@debug", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "@include", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "@operator", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@keyword", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@keyword.function", {
- fg = "#a4afee",
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "@keyword.operator", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@keyword.return", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@exception", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "@type", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@type.builtin", {
- fg = "#62b9e2",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@type.definition", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@type.qualifier", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@storageclass", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@attribute", {
- fg = "#959595"
- })
-
- vim.api.nvim_set_hl(0, "@symbol", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@punctuation.delimiter", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "@punctuation.bracket", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "@punctuation.special", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@comment", {
- fg = "#b3b3b3",
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "@comment.error", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "@comment.warning", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "@comment.todo", {
- fg = "#d1a126",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@comment.note", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@markup.strong", {
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.italic", {
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.strikethrough", {
- strikethrough = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.underline", {
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.heading", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.quote", {
- fg = "#b3b3b3",
- italic = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.math", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "@markup.link", {
- fg = "#a4afee",
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.link.label", {
- fg = "#a4afee",
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.link.url", {
- fg = "#b48ce9",
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "@markup.raw", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "@markup.list", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@diff.plus", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "@diff.minus", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "@diff.delta", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "@tag", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "@tag.attribute", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "@tag.delimiter", {
- fg = "#ababab"
- })
-
-
- -- ── LSP / Diagnostics ────────────────────────────────
- vim.api.nvim_set_hl(0, "DiagnosticError", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticWarn", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticInfo", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticHint", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticOk", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticUnderlineError", {
- sp = "#e88484",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticUnderlineWarn", {
- sp = "#d1a126",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticUnderlineInfo", {
- sp = "#62b9e2",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticUnderlineHint", {
- sp = "#b48ce9",
- undercurl = true
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticVirtualTextError", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticVirtualTextWarn", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticVirtualTextInfo", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticVirtualTextHint", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticFloatingError", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticFloatingWarn", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticFloatingInfo", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticFloatingHint", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticSignError", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticSignWarn", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticSignInfo", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "DiagnosticSignHint", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "LspReferenceText", {
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "LspReferenceRead", {
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "LspReferenceWrite", {
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "LspInlayHint", {
- fg = "#b3b3b3",
- bg = "#090909"
- })
-
-
- -- ── Telescope ─────────────────────────────────────────
- vim.api.nvim_set_hl(0, "TelescopeNormal", {
- fg = "#c6c6c6",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeBorder", {
- fg = "#797979",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeTitle", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "TelescopePromptNormal", {
- fg = "#c6c6c6",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "TelescopePromptBorder", {
- fg = "#797979",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "TelescopePromptTitle", {
- fg = "#b48ce9",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "TelescopeSelection", {
- fg = "#c6c6c6",
- bg = "#323232"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeMultiSelection", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeMatching", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "TelescopePreviewNormal", {
- fg = "#c6c6c6",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "TelescopePreviewBorder", {
- fg = "#797979",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeResultsNormal", {
- fg = "#c6c6c6",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "TelescopeResultsBorder", {
- fg = "#797979",
- bg = "#000000"
- })
-
-
- -- ── NvimTree ─────────────────────────────────────────
- vim.api.nvim_set_hl(0, "NvimTreeNormal", {
- fg = "#c6c6c6",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeRootFolder", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeGitDirty", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeGitNew", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeGitDeleted", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeOpenedFile", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeSpecialFile", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeSymlink", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeFolderName", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeFolderIcon", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeEmptyFolderName", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeOpenedFolderName", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeImageFile", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeIndentMarker", {
- fg = "#797979"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeWinSeparator", {
- fg = "#797979",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "NvimTreeCursorLine", {
- bg = "#090909"
- })
-
-
- -- ── WhichKey ─────────────────────────────────────────
- vim.api.nvim_set_hl(0, "WhichKey", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "WhichKeyGroup", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeyDesc", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeySeperator", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeySeparator", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeyFloat", {
- fg = "#c6c6c6",
- bg = "#090909"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeyBorder", {
- fg = "#797979"
- })
-
- vim.api.nvim_set_hl(0, "WhichKeyValue", {
- fg = "#b98beb"
- })
-
-
- -- ── Lazy / Noice / Cmp ───────────────────────────────
- vim.api.nvim_set_hl(0, "LazyNormal", {
- fg = "#c6c6c6",
- bg = "#000000"
- })
-
- vim.api.nvim_set_hl(0, "LazyReasonPlugin", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "LazyReasonStart", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "LazyReasonSource", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "LazyReasonRt", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "LazyReasonCmd", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "LazyValue", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "LazyCommit", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "LazySpecial", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "LazyDir", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "LazyH1", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "LazyButton", {
- fg = "#c6c6c6",
- bg = "#0b0b0d"
- })
-
- vim.api.nvim_set_hl(0, "LazyButtonActive", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "NoiceMsg", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "NoiceCursor", {
- fg = "#000000",
- bg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "MiniCompletionActiveParameter", {
- underline = true
- })
-
- vim.api.nvim_set_hl(0, "CmpItemAbbr", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemAbbrMatch", {
- fg = "#a4afee",
- bold = true
- })
-
- vim.api.nvim_set_hl(0, "CmpItemAbbrMatchFuzzy", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKind", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemMenu", {
- fg = "#ababab"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindSnippet", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindKeyword", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindText", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindMethod", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindFunction", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindConstructor", {
- fg = "#a4afee"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindField", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindVariable", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindClass", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindInterface", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindModule", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindProperty", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindUnit", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindValue", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindEnum", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindEnumMember", {
- fg = "#b98beb"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindColor", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindFile", {
- fg = "#c6c6c6"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindReference", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindFolder", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindEvent", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindOperator", {
- fg = "#b48ce9"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindTypeParameter", {
- fg = "#62b9e2"
- })
-
- vim.api.nvim_set_hl(0, "CmpItemKindCopied", {
- fg = "#43b48b"
- })
-
-
- -- ── GitSigns ────────────────────────────────────────
- vim.api.nvim_set_hl(0, "GitSignsAdd", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsChange", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsDelete", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsAddNr", {
- fg = "#43b48b"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsChangeNr", {
- fg = "#d1a126"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsDeleteNr", {
- fg = "#e88484"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsAddLn", {
- bg = "#050e0b"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsChangeLn", {
- bg = "#110d03"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsDeleteLn", {
- bg = "#130b0b"
- })
-
- vim.api.nvim_set_hl(0, "GitSignsCurrentLineBlame", {
- fg = "#ababab"
- })
diff --git a/DreamcoderNvim/.config/nvim/colors/dreamcoder.lua b/DreamcoderNvim/.config/nvim/colors/dreamcoder.lua
index 24a4de39..85dd9418 100644
--- a/DreamcoderNvim/.config/nvim/colors/dreamcoder.lua
+++ b/DreamcoderNvim/.config/nvim/colors/dreamcoder.lua
@@ -3,12 +3,11 @@
-- ========================================================
-- Usage: vim.cmd.colorscheme("dreamcoder")
-- Auto-loads the correct variant based on:
--- 1. DREAMCODER_THEME_PROFILE env var (night) — before base mode
--- 2. DREAMCODER_THEME_MODE env var (set by `dreamcoder light`)
--- 3. ~/.cache/dreamcoder/cursor-cli.env (persisted by apply-theme-mode.sh)
--- 4. vim.o.background (Neovim's own setting)
--- 5. Fallback: dark
--- Direct variant access: colorscheme dreamcoder-dark / dreamcoder-light / dreamcoder-night
+-- 1. DREAMCODER_THEME_MODE env var (set by `dreamcoder light`)
+-- 2. ~/.cache/dreamcoder/cursor-cli.env (persisted by apply-theme-mode.sh)
+-- 3. vim.o.background (Neovim's own setting)
+-- 4. Fallback: dark
+-- Direct variant access: colorscheme dreamcoder-dark / dreamcoder-light
-- ========================================================
vim.g.colors_name = "dreamcoder"
@@ -21,29 +20,6 @@ vim.opt.pumblend = 10
local src = debug.getinfo(1, "S").source:match("@?(.*)")
local theme_dir = src:match("^(.*[/\\])") or "."
--- Resolve render profile first (night wins over base mode), then mode.
-local profile = vim.env.DREAMCODER_THEME_PROFILE
-
-if not profile then
- local cache_file = vim.fn.expand("~/.cache/dreamcoder/cursor-cli.env")
- local f = io.open(cache_file, "r")
- if f then
- for line in f:lines() do
- local m = line:match('^export DREAMCODER_THEME_PROFILE="(.-)"')
- if m then
- profile = m
- break
- end
- end
- f:close()
- end
-end
-
-if profile == "night" then
- dofile(theme_dir .. "dreamcoder-night.lua")
- return
-end
-
-- Resolve base mode: env var > cache file > vim.o.background > dark
local mode = vim.env.DREAMCODER_THEME_MODE
diff --git a/DreamcoderPi/.pi/agent/scripts/pi-theme.sh b/DreamcoderPi/.pi/agent/scripts/pi-theme.sh
index 39a26a09..6dbeb605 100755
--- a/DreamcoderPi/.pi/agent/scripts/pi-theme.sh
+++ b/DreamcoderPi/.pi/agent/scripts/pi-theme.sh
@@ -38,27 +38,11 @@ detect() {
}
mode="$(detect)"
-profile="${DREAMCODER_THEME_PROFILE:-standard}"
-case "${profile}" in
-standard | night) ;;
-*)
- echo "✗ invalid render profile: ${profile} (expected standard|night)" >&2
- exit 1
- ;;
-esac
-# Night is an orthogonal render profile on top of the base mode: the Pi
-# selector picks the generated *-night sibling while the mode stays dark.
-variant="${mode}"
-[[ "${profile}" == "night" ]] && variant="night"
-src="$DC/DreamcoderPi/.pi/agent/themes/dreamcoder-$variant.json"
+src="$DC/DreamcoderPi/.pi/agent/themes/dreamcoder-$mode.json"
[[ -f "$src" ]] || {
echo "✗ $src not found" >&2
exit 1
}
mkdir -p "$(dirname "$LN")"
ln -sf "$src" "$LN"
-if [[ "${profile}" == "night" ]]; then
- echo "→ pi-theme: $mode (night)"
-else
- echo "→ pi-theme: $mode"
-fi
+echo "→ pi-theme: $mode"
diff --git a/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json b/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json
deleted file mode 100644
index 2c31c190..00000000
--- a/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
- "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
- "name": "dreamcoder",
- "vars": {
- "cocoa": "#b48ce9",
- "lucuma": "#a4afee",
- "diagnostic": "#62b9e2",
- "sage": "#43b48b",
- "mauve": "#b98beb",
- "coral": "#e88484",
- "warning": "#d1a126",
- "muted": "#ababab",
- "subtle": "#959595",
- "comment": "#b3b3b3",
- "borderUi": "#797979",
- "borderMuted": "#5c5c5c"
- },
- "colors": {
- "accent": "lucuma",
- "border": "borderUi",
- "borderAccent": "diagnostic",
- "borderMuted": "borderMuted",
- "success": "sage",
- "error": "coral",
- "warning": "warning",
- "muted": "muted",
- "dim": "subtle",
- "text": "",
- "thinkingText": "muted",
- "selectedBg": "#0b0b0d",
- "userMessageBg": "#07070a",
- "userMessageText": "",
- "customMessageBg": "#090909",
- "customMessageText": "",
- "customMessageLabel": "lucuma",
- "toolPendingBg": "#040404",
- "toolSuccessBg": "#0c2019",
- "toolErrorBg": "#251515",
- "toolTitle": "lucuma",
- "toolOutput": "",
- "mdHeading": "lucuma",
- "mdLink": "diagnostic",
- "mdLinkUrl": "muted",
- "mdCode": "sage",
- "mdCodeBlock": "",
- "mdCodeBlockBorder": "borderMuted",
- "mdQuote": "cocoa",
- "mdQuoteBorder": "borderMuted",
- "mdHr": "borderMuted",
- "mdListBullet": "lucuma",
- "toolDiffAdded": "sage",
- "toolDiffRemoved": "coral",
- "toolDiffContext": "muted",
- "syntaxComment": "comment",
- "syntaxKeyword": "#a4afee",
- "syntaxFunction": "#b48ce9",
- "syntaxVariable": "#ababab",
- "syntaxString": "#43b48b",
- "syntaxNumber": "#ad9fed",
- "syntaxType": "#62b9e2",
- "syntaxOperator": "#b48ce9",
- "syntaxPunctuation": "#ababab",
- "thinkingOff": "comment",
- "thinkingMinimal": "borderUi",
- "thinkingLow": "#6ebbdf",
- "thinkingMedium": "diagnostic",
- "thinkingHigh": "mauve",
- "thinkingXhigh": "coral",
- "bashMode": "lucuma"
- }
-}
diff --git a/DreamcoderProfiles/deploy/desktop-arch.json b/DreamcoderProfiles/deploy/desktop-arch.json
index f62c9a9d..6bafbc4e 100644
--- a/DreamcoderProfiles/deploy/desktop-arch.json
+++ b/DreamcoderProfiles/deploy/desktop-arch.json
@@ -1,9 +1,9 @@
{
"kind": "deployment",
"name": "desktop-arch",
- "description": "Desktop Arch Linux deployment of Dreamcoder with full day/night theme support. Repository-safe: contains no device address, username, key, or host runtime state.",
+ "description": "Desktop Arch Linux deployment of Dreamcoder with full light/dark theme support. Repository-safe: contains no device address, username, key, or host runtime state.",
"platform": "arch-desktop",
"os": "Arch Linux",
"terminal_default_mode": "light",
- "notes": "Day/night switching is handled by systemd timers via scripts/theme-auto.sh. Herdr [ui] pane_scrollbars follows the Herdr default (false); the mobile profile pins the same setting explicitly."
+ "notes": "Light/dark switching is handled by systemd timers via scripts/theme-auto.sh. Herdr [ui] pane_scrollbars follows the Herdr default (false); the mobile profile pins the same setting explicitly."
}
diff --git a/DreamcoderShell/.config/starship-night.toml b/DreamcoderShell/.config/starship-night.toml
deleted file mode 100644
index 2363f9e9..00000000
--- a/DreamcoderShell/.config/starship-night.toml
+++ /dev/null
@@ -1,146 +0,0 @@
-# ========================================================
-# Dreamcoder Dark Night — Starship prompt
-# ========================================================
-# Modern two-line layout with powerline segments.
-# Line 1: context (directory, git), fill, cmd_duration, time
-# Line 2: input character only (clean)
-# Extra: status (exit code), AI session (hidden until active)
-
-add_newline = true
-palette = "dreamcoder-night"
-command_timeout = 500
-
-format = """
-[\uE0B6](fg:prompt_surface0)\
-$username\
-[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\
-$directory\
-[\uE0B0](bg:prompt_accent fg:prompt_surface1)\
-$git_branch\
-$git_status\
-[\uE0B4](fg:prompt_accent)\
-$fill\
-$cmd_duration\
-$time
-$character"""
-
-right_format = ""
-
-[palettes.dreamcoder-night]
-bg = "#000000"
-text = "#c6c6c6"
-muted = "#ababab"
-prompt_bg = "#000000"
-prompt_surface0 = "#090909"
-prompt_surface1 = "#0b0b0d"
-prompt_surface2 = "#1b1b1b"
-prompt_text = "#c6c6c6"
-prompt_muted = "#ababab"
-prompt_accent = "#a4afee"
-prompt_accent_2 = "#b48ce9"
-sage = "#43b48b"
-success = "#43b48b"
-diagnostic = "#62b9e2"
-info = "#62b9e2"
-lavender = "#b48ce9"
-mauve = "#b98beb"
-error = "#e88484"
-warning = "#d1a126"
-border = "#797979"
-focus = "#3a76d6"
-link = "#a4afee"
-
-[username]
-show_always = true
-style_user = "bg:prompt_surface0 fg:prompt_text bold"
-style_root = "bg:prompt_surface0 fg:error bold"
-format = "[ $user ]($style)"
-
-[directory]
-style = "bg:prompt_surface1 fg:prompt_text bold"
-format = "[ $path ]($style)"
-truncation_length = 2
-truncate_to_repo = true
-home_symbol = ""
-
-[git_branch]
-symbol = "\uf418"
-style = "bg:prompt_accent fg:prompt_bg bold"
-format = "[ $symbol $branch ]($style)"
-
-[git_status]
-style = "bg:prompt_accent fg:prompt_bg bold"
-format = "[$all_status$ahead_behind ]($style)"
-conflicted = "\ue727${count} "
-ahead = "\u21E1${count} "
-behind = "\u21E3${count} "
-diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} "
-untracked = "?${count} "
-stashed = "\uf0CF${count} "
-modified = "~${count} "
-staged = "+${count} "
-renamed = "\u00BB${count} "
-deleted = "\u2718${count} "
-
-[status]
-disabled = false
-format = "[$symbol]($style)"
-symbol = "\u2717"
-style = "bg:error fg:prompt_bg bold"
-pipestatus = false
-
-[fill]
-symbol = " "
-
-[cmd_duration]
-min_time = 2500
-style = "fg:prompt_muted"
-format = "[ \uf552 $duration ]($style)"
-
-[time]
-disabled = false
-format = "[ $time ]($style)"
-style = "fg:prompt_muted"
-
-[character]
-success_symbol = "[\u276F](bold fg:prompt_accent)"
-error_symbol = "[\u276F](bold fg:error)"
-vimcmd_symbol = "[\u276E](bold fg:sage)"
-
-# Runtime versions - show only when relevant, keep compact
-[bun]
-symbol = "\uF5EF"
-style = "fg:prompt_accent bold"
-format = "[ $symbol $version]($style)"
-
-[nodejs]
-symbol = "\uE718"
-style = "fg:sage bold"
-format = "[ $symbol $version]($style)"
-
-[python]
-symbol = "\uE73C"
-style = "fg:diag bold"
-format = "[ $symbol $version]($style)"
-
-[golang]
-symbol = "\uE627"
-style = "fg:diag bold"
-format = "[ $symbol $version]($style)"
-
-[rust]
-symbol = "\uE7A8"
-style = "fg:mauve bold"
-format = "[ $symbol $version]($style)"
-
-[custom.ai_session]
-command = "cat ~/.cache/dreamcoder/ai-session.state 2>/dev/null || echo ''"
-when = """test -f ~/.cache/dreamcoder/ai-session.state"""
-format = "[\uf5B5 $output]($style)"
-style = "fg:diag bold"
-
-[docker_context]
-symbol = "\uf308"
-style = "fg:diag bold"
-format = "[ $symbol $context]($style)"
-only_with_files = true
diff --git a/DreamcoderSystemd/.config/systemd/user/dreamcoder-theme-auto.timer b/DreamcoderSystemd/.config/systemd/user/dreamcoder-theme-auto.timer
index eabf8b45..c790383a 100644
--- a/DreamcoderSystemd/.config/systemd/user/dreamcoder-theme-auto.timer
+++ b/DreamcoderSystemd/.config/systemd/user/dreamcoder-theme-auto.timer
@@ -1,10 +1,9 @@
[Unit]
-Description=Run Dreamcoder day/night theme automation
+Description=Run Dreamcoder light/dark theme automation
[Timer]
OnBootSec=2min
OnCalendar=*-*-* 07:00:00
-OnCalendar=*-*-* 16:00:00
OnCalendar=*-*-* 18:00:00
Persistent=true
AccuracySec=1min
diff --git a/DreamcoderThemes/dreamcoder/README.md b/DreamcoderThemes/dreamcoder/README.md
index cca1b8ce..7e9c2707 100644
--- a/DreamcoderThemes/dreamcoder/README.md
+++ b/DreamcoderThemes/dreamcoder/README.md
@@ -6,7 +6,6 @@ This directory contains the Dreamcoder visual contract and generated color-only
- `tokens.schema.json`: machine-readable token contract.
- `*-dark.*`: Dreamcoder Dark mode with near-black bg, cool steel-blue accents, and muted slate surfaces.
- `*-light.*`: paper-like daytime mode with flat surface ladder and distinct semantic tokens.
-- `*-night.*`: Night/Dim rendering profile — deterministic brightness/saturation-reduced derivation of Dreamcoder Dark (user-controlled display profile, no medical claims).
## Color-only Snippets (Hooks)
Hook these into your app configs after ML4W/Gentleman files:
diff --git a/DreamcoderThemes/dreamcoder/bat-dreamcoder-night.sh b/DreamcoderThemes/dreamcoder/bat-dreamcoder-night.sh
deleted file mode 100644
index 65f82c79..00000000
--- a/DreamcoderThemes/dreamcoder/bat-dreamcoder-night.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-# Sourced snippet: never set shell options here.
-# Dreamcoder Dark Night — Bat theme; run 'bat cache --build' after installing the tmTheme.
-export BAT_THEME="Dreamcoder-Night"
-export BAT_STYLE="auto,changes,header,grid"
-export BAT_PAGER="less -RF"
-export BAT_TABS="4"
diff --git a/DreamcoderThemes/dreamcoder/btop-dreamcoder-night.theme b/DreamcoderThemes/dreamcoder/btop-dreamcoder-night.theme
deleted file mode 100644
index d7cc6eb5..00000000
--- a/DreamcoderThemes/dreamcoder/btop-dreamcoder-night.theme
+++ /dev/null
@@ -1,64 +0,0 @@
-# ========================================================
-# Dreamcoder Dark Night — Btop theme
-# ========================================================
-# Place in ~/.config/btop/themes/ and select from Btop UI.
-
-theme[main_bg]="#000000"
-theme[main_fg]="#c6c6c6"
-theme[title]="#a4afee"
-theme[hi_fg]="#a4afee"
-theme[selected_bg]="#0b0b0d"
-theme[selected_fg]="#c6c6c6"
-theme[inactive_fg]="#ababab"
-theme[graph_line]="#5c5c5c"
-theme[proc_misc]="#ababab"
-theme[cpu_core]="#62b9e2"
-theme[mem_free]="#43b48b"
-theme[mem_used]="#b48ce9"
-theme[mem_cached]="#62b9e2"
-theme[user_bg]="#090909"
-theme[user_fg]="#c6c6c6"
-theme[temp]="#d1a126"
-theme[disk]="#62b9e2"
-theme[process]="#b48ce9"
-theme[process_selected]="#a4afee"
-theme[core_bar]="#a4afee"
-theme[temp_bar]="#d1a126"
-theme[swap]="#b98beb"
-theme[div_line]="#5c5c5c"
-theme[process_bg]="#090909"
-theme[process_fg]="#c6c6c6"
-theme[bad]="#e88484"
-theme[good]="#43b48b"
-theme[widget_bg]="#090909"
-theme[widget_fg]="#c6c6c6"
-theme[widget_border]="#5c5c5c"
-theme[widget_selected]="#a4afee"
-theme[graph_bg]="#090909"
-theme[graph_fg]="#c6c6c6"
-theme[graph_high]="#b48ce9"
-theme[graph_low]="#62b9e2"
-theme[graph_med]="#a4afee"
-theme[proc_bg]="#090909"
-theme[proc_fg]="#c6c6c6"
-theme[process_border]="#5c5c5c"
-theme[widget_title]="#a4afee"
-theme[box_border]="#5c5c5c"
-theme[box_bg]="#090909"
-theme[box_fg]="#c6c6c6"
-theme[box_selected]="#a4afee"
-theme[os_bg]="#090909"
-theme[os_fg]="#c6c6c6"
-theme[clock_bg]="#090909"
-theme[clock_fg]="#a4afee"
-theme[bat_high]="#43b48b"
-theme[bat_med]="#d1a126"
-theme[bat_low]="#e88484"
-theme[sensor_bg]="#090909"
-theme[sensor_fg]="#c6c6c6"
-theme[sensor_bar_bg]="#0b0b0d"
-theme[sensor_bar_fg]="#a4afee"
-theme[net_bg]="#090909"
-theme[net_fg]="#c6c6c6"
-theme[net_download]="#43b48b"
-theme[net_upload]="#b48ce9"
diff --git a/DreamcoderThemes/dreamcoder/cava-dreamcoder-night.config b/DreamcoderThemes/dreamcoder/cava-dreamcoder-night.config
deleted file mode 100644
index c9876824..00000000
--- a/DreamcoderThemes/dreamcoder/cava-dreamcoder-night.config
+++ /dev/null
@@ -1,26 +0,0 @@
-# ========================================================
-# Dreamcoder Dark Night — Cava theme
-# ========================================================
-# Include from ~/.config/cava/config or place in
-# ~/.config/cava/dreamcoder-cava.config.
-
-[color]
-# Background
-background = '#000000'
-
-# Gradient mode for smooth transitions
-gradient = 1
-gradient_count = 8
-
-# Gradient colors (low to high frequency) — raw palette, muted at lower end
-gradient_color_1 = '#274a5a'
-gradient_color_2 = '#3b6f88'
-gradient_color_3 = '#525877'
-gradient_color_4 = '#737aa7'
-gradient_color_5 = '#a4afee'
-gradient_color_6 = '#b48ce9'
-gradient_color_7 = '#a25c5c'
-gradient_color_8 = '#e88484'
-
-# Mono color (used when gradient = 0)
-foreground = '#a4afee'
diff --git a/DreamcoderThemes/dreamcoder/delta-dreamcoder-night.gitconfig b/DreamcoderThemes/dreamcoder/delta-dreamcoder-night.gitconfig
deleted file mode 100644
index b7b32ad1..00000000
--- a/DreamcoderThemes/dreamcoder/delta-dreamcoder-night.gitconfig
+++ /dev/null
@@ -1,45 +0,0 @@
-# ========================================================
-# Dreamcoder Dark Night — Git Delta theme
-# ========================================================
-# Include from ~/.config/git/config:
-# [include]
-# path = ~/.config/git/delta-dreamcoder.gitconfig
-
-[delta]
- # Syntax highlighting theme for diff content
- syntax-theme = Dreamcoder-Night
-
- # Line colors
- plus-color = "#2a6852"
- minus-color = "#854e4e"
- plus-emph-color = "#43b48b"
- minus-emph-color = "#e88484"
-
- # Diff UI
- file-style = "#a4afee"
- file-decoration-style = "bold yellow box ul"
- hunk-header-style = "file line-number syntax"
- hunk-header-decoration-style = "yellow box"
- hunk-header-file-style = "#a4afee"
- hunk-header-line-number-style = "#ababab"
- hunk-header-color = "#737374"
-
- # Commit decorations
- commit-style = "#a4afee bold"
- commit-decoration-style = "bold yellow box ul"
-
- # Line numbers
- line-numbers = true
- line-numbers-left-style = "#ababab"
- line-numbers-right-style = "#ababab"
- line-numbers-minus-style = "#e88484"
- line-numbers-plus-style = "#43b48b"
-
- # Side-by-side
- side-by-side = true
-
- # Whitespace highlighting
- whitespace-error-style = "#d1a126"
-
- # Navigation
- navigate = true
diff --git a/DreamcoderThemes/dreamcoder/design-system.json b/DreamcoderThemes/dreamcoder/design-system.json
index 73dc160a..73b67059 100644
--- a/DreamcoderThemes/dreamcoder/design-system.json
+++ b/DreamcoderThemes/dreamcoder/design-system.json
@@ -519,7 +519,7 @@
"source": "DreamcoderThemes/dreamcoder/tokens.json",
"generator": "sync.py::sync_repo_snippets",
"path": ".opencode/themes/dreamcoder.json",
- "mode_policy": "base-modes+derived-night",
+ "mode_policy": "base-modes",
"discovery_root": ".opencode/themes",
"pattern": "*.json",
"validation": "exact-bytes"
diff --git a/DreamcoderThemes/dreamcoder/dunst-dreamcoder-night.conf b/DreamcoderThemes/dreamcoder/dunst-dreamcoder-night.conf
deleted file mode 100644
index 97507f32..00000000
--- a/DreamcoderThemes/dreamcoder/dunst-dreamcoder-night.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# ========================================================
-# Dreamcoder Dark Night — Dunst theme
-# ========================================================
-# Include from dunstrc:
-# [include] dreamcoder-dunst.conf
-
-[urgency_low]
- background = "#000000"
- foreground = "#c6c6c6"
- highlight = "#a4afee"
- frame_color = "#5c5c5c"
-
-[urgency_normal]
- background = "#090909"
- foreground = "#c6c6c6"
- highlight = "#a4afee"
- frame_color = "#797979"
-
-[urgency_critical]
- background = "#462828"
- foreground = "#c6c6c6"
- highlight = "#e88484"
- frame_color = "#e88484"
diff --git a/DreamcoderThemes/dreamcoder/firefox-dreamcoder-night.css b/DreamcoderThemes/dreamcoder/firefox-dreamcoder-night.css
deleted file mode 100644
index c5390f5d..00000000
--- a/DreamcoderThemes/dreamcoder/firefox-dreamcoder-night.css
+++ /dev/null
@@ -1,138 +0,0 @@
-/* ========================================================
- Dreamcoder Dark Night — Firefox userChrome.css
- ========================================================
- Place in ~/.mozilla/firefox/*.default-release/chrome/userChrome.css
- Requires toolkit.legacyUserProfileCustomizations.stylesheets = true
- in about:config. */
-
-:root {
- --dreamcoder-bg: #000000;
- --dreamcoder-surface: #090909;
- --dreamcoder-surface-1: #0b0b0d;
- --dreamcoder-text: #c6c6c6;
- --dreamcoder-text-dim: #ababab;
- --dreamcoder-accent: #a4afee;
- --dreamcoder-accent-2: #b48ce9;
- --dreamcoder-border: #5c5c5c;
- --dreamcoder-border-ui: #797979;
- --dreamcoder-error: #e88484;
- --dreamcoder-warning: #d1a126;
- --dreamcoder-sage: #43b48b;
- --dreamcoder-input-bg: #090909;
- --dreamcoder-toolbar-bg: #090909;
- --dreamcoder-hover: #191a24;
- --dreamcoder-active: #292c3c;
-}
-
-/* Main window */
-#main-window,
-#navigator-toolbox {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-/* Toolbar & URL bar */
-#nav-bar,
-#nav-bar toolbarbutton,
-#urlbar,
-#urlbar-background {
- background-color: var(--dreamcoder-toolbar-bg) !important;
- color: var(--dreamcoder-text) !important;
- border-color: var(--dreamcoder-border) !important;
-}
-
-#urlbar[focused="true"] > #urlbar-background {
- border-color: var(--dreamcoder-accent) !important;
-}
-
-/* Sidebar (bookmarks, history) */
-#sidebar-box,
-#sidebar {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-.sidebar-placesTreechildren {
- color: var(--dreamcoder-text) !important;
-}
-
-/* Tab bar */
-#TabsToolbar,
-#tabbrowser-tabs,
-.tab-background {
- background-color: var(--dreamcoder-bg) !important;
-}
-
-.tabbrowser-tab:not([selected]) .tab-background {
- background-color: var(--dreamcoder-surface) !important;
-}
-
-.tabbrowser-tab[selected] .tab-background {
- background-color: var(--dreamcoder-toolbar-bg) !important;
- border-color: var(--dreamcoder-accent) !important;
-}
-
-.tabbrowser-tab .tab-label {
- color: var(--dreamcoder-text-dim) !important;
-}
-
-.tabbrowser-tab[selected] .tab-label {
- color: var(--dreamcoder-text) !important;
-}
-
-/* Context menus */
-menupopup,
-popup {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-menuitem {
- color: var(--dreamcoder-text) !important;
-}
-
-menuitem:hover {
- background-color: var(--dreamcoder-hover) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-/* Status panel */
-#statuspanel-label {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text-dim) !important;
- border-color: var(--dreamcoder-border) !important;
-}
-
-/* Find bar */
-#findbar {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-#findbar input {
- background-color: var(--dreamcoder-input-bg) !important;
- color: var(--dreamcoder-text) !important;
- border-color: var(--dreamcoder-border) !important;
-}
-
-/* Private browsing indicators */
-#private-browsing-indicator-with-label {
- color: var(--dreamcoder-accent-2) !important;
-}
-
-/* Downloads panel */
-#downloadsPanel,
-#downloadsListBox {
- background-color: var(--dreamcoder-bg) !important;
- color: var(--dreamcoder-text) !important;
-}
-
-/* Scrollbar styling */
-:root {
- scrollbar-color: var(--dreamcoder-border) var(--dreamcoder-bg) !important;
-}
-
-* {
- scrollbar-width: thin !important;
- scrollbar-color: var(--dreamcoder-border) var(--dreamcoder-bg) !important;
-}
diff --git a/DreamcoderThemes/dreamcoder/fzf-dreamcoder-night.sh b/DreamcoderThemes/dreamcoder/fzf-dreamcoder-night.sh
deleted file mode 100644
index 30b55ca8..00000000
--- a/DreamcoderThemes/dreamcoder/fzf-dreamcoder-night.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env bash
-# Sourced snippet: never set shell options here.
-# Dreamcoder Dark Night — fzf
-export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS:-} --color=bg:#000000,bg+:#0b0b0d,fg:#c6c6c6,fg+:#c6c6c6,hl:#a4afee,hl+:#a4afee,info:#62b9e2,marker:#43b48b,prompt:#a4afee,spinner:#b48ce9,pointer:#b48ce9,header:#ababab,border:#5c5c5c,label:#ababab,query:#c6c6c6,gutter:#0b0b0d,scrollbar:#5c5c5c,separator:#5c5c5c,preview-bg:#0b0b0d,preview-border:#5c5c5c"
diff --git a/DreamcoderThemes/dreamcoder/hypr-colors-night.conf b/DreamcoderThemes/dreamcoder/hypr-colors-night.conf
deleted file mode 100644
index 47cac532..00000000
--- a/DreamcoderThemes/dreamcoder/hypr-colors-night.conf
+++ /dev/null
@@ -1,50 +0,0 @@
-$background = rgba(000000ff)
-$error = rgba(e88484ff)
-$error_container = rgba(090909ff)
-$inverse_on_surface = rgba(090909ff)
-$inverse_primary = rgba(a4afeeff)
-$inverse_surface = rgba(c6c6c6ff)
-$on_background = rgba(c6c6c6ff)
-$on_error = rgba(000000ff)
-$on_error_container = rgba(c6c6c6ff)
-$on_primary = rgba(000000ff)
-$on_primary_container = rgba(c6c6c6ff)
-$on_primary_fixed = rgba(000000ff)
-$on_primary_fixed_variant = rgba(a4afeeff)
-$on_secondary = rgba(000000ff)
-$on_secondary_container = rgba(c6c6c6ff)
-$on_secondary_fixed = rgba(000000ff)
-$on_secondary_fixed_variant = rgba(b48ce9ff)
-$on_surface = rgba(c6c6c6ff)
-$on_surface_variant = rgba(abababff)
-$on_tertiary = rgba(000000ff)
-$on_tertiary_container = rgba(c6c6c6ff)
-$on_tertiary_fixed = rgba(000000ff)
-$on_tertiary_fixed_variant = rgba(3a76d6ff)
-$outline = rgba(5c5c5cff)
-$outline_variant = rgba(797979ff)
-$primary = rgba(a4afeeff)
-$primary_container = rgba(0b0b0dff)
-$primary_fixed = rgba(a4afeeff)
-$primary_fixed_dim = rgba(b48ce9ff)
-$scrim = rgba(000000ff)
-$secondary = rgba(b48ce9ff)
-$secondary_container = rgba(0b0b0dff)
-$secondary_fixed = rgba(b48ce9ff)
-$secondary_fixed_dim = rgba(b48ce9ff)
-$shadow = rgba(000000ff)
-$source_color = rgba(a4afeeff)
-$surface = rgba(000000ff)
-$surface_bright = rgba(090909ff)
-$surface_container = rgba(0b0b0dff)
-$surface_container_high = rgba(0b0b0dff)
-$surface_container_highest = rgba(1b1b1bff)
-$surface_container_low = rgba(090909ff)
-$surface_container_lowest = rgba(090909ff)
-$surface_dim = rgba(090909ff)
-$surface_tint = rgba(a4afeeff)
-$surface_variant = rgba(0b0b0dff)
-$tertiary = rgba(62b9e2ff)
-$tertiary_container = rgba(090909ff)
-$tertiary_fixed = rgba(62b9e2ff)
-$tertiary_fixed_dim = rgba(62b9e2ff)
diff --git a/DreamcoderThemes/dreamcoder/hypr-colors-night.lua b/DreamcoderThemes/dreamcoder/hypr-colors-night.lua
deleted file mode 100644
index 82f07c29..00000000
--- a/DreamcoderThemes/dreamcoder/hypr-colors-night.lua
+++ /dev/null
@@ -1,50 +0,0 @@
-background = "rgba(000000ff)"
-error = "rgba(e88484ff)"
-error_container = "rgba(090909ff)"
-inverse_on_surface = "rgba(090909ff)"
-inverse_primary = "rgba(a4afeeff)"
-inverse_surface = "rgba(c6c6c6ff)"
-on_background = "rgba(c6c6c6ff)"
-on_error = "rgba(000000ff)"
-on_error_container = "rgba(c6c6c6ff)"
-on_primary = "rgba(000000ff)"
-on_primary_container = "rgba(c6c6c6ff)"
-on_primary_fixed = "rgba(000000ff)"
-on_primary_fixed_variant = "rgba(a4afeeff)"
-on_secondary = "rgba(000000ff)"
-on_secondary_container = "rgba(c6c6c6ff)"
-on_secondary_fixed = "rgba(000000ff)"
-on_secondary_fixed_variant = "rgba(b48ce9ff)"
-on_surface = "rgba(c6c6c6ff)"
-on_surface_variant = "rgba(abababff)"
-on_tertiary = "rgba(000000ff)"
-on_tertiary_container = "rgba(c6c6c6ff)"
-on_tertiary_fixed = "rgba(000000ff)"
-on_tertiary_fixed_variant = "rgba(3a76d6ff)"
-outline = "rgba(5c5c5cff)"
-outline_variant = "rgba(797979ff)"
-primary = "rgba(a4afeeff)"
-primary_container = "rgba(0b0b0dff)"
-primary_fixed = "rgba(a4afeeff)"
-primary_fixed_dim = "rgba(b48ce9ff)"
-scrim = "rgba(000000ff)"
-secondary = "rgba(b48ce9ff)"
-secondary_container = "rgba(0b0b0dff)"
-secondary_fixed = "rgba(b48ce9ff)"
-secondary_fixed_dim = "rgba(b48ce9ff)"
-shadow = "rgba(000000ff)"
-source_color = "rgba(a4afeeff)"
-surface = "rgba(000000ff)"
-surface_bright = "rgba(090909ff)"
-surface_container = "rgba(0b0b0dff)"
-surface_container_high = "rgba(0b0b0dff)"
-surface_container_highest = "rgba(1b1b1bff)"
-surface_container_low = "rgba(090909ff)"
-surface_container_lowest = "rgba(090909ff)"
-surface_dim = "rgba(090909ff)"
-surface_tint = "rgba(a4afeeff)"
-surface_variant = "rgba(0b0b0dff)"
-tertiary = "rgba(62b9e2ff)"
-tertiary_container = "rgba(090909ff)"
-tertiary_fixed = "rgba(62b9e2ff)"
-tertiary_fixed_dim = "rgba(62b9e2ff)"
diff --git a/DreamcoderThemes/dreamcoder/hyprland-night.conf b/DreamcoderThemes/dreamcoder/hyprland-night.conf
deleted file mode 100644
index 60e35c32..00000000
--- a/DreamcoderThemes/dreamcoder/hyprland-night.conf
+++ /dev/null
@@ -1,11 +0,0 @@
-# Dreamcoder color layer for Hyprland.
-# Import after ML4W/Gentleman defaults; this changes colors only.
-
-general {
- col.active_border = rgba(c6c6c6ff) rgba(abababff) 45deg
- col.inactive_border = rgba(282828d6)
-}
-
-misc {
- background_color = rgba(000000ff)
-}
diff --git a/DreamcoderThemes/dreamcoder/ls-colors-dreamcoder-night.sh b/DreamcoderThemes/dreamcoder/ls-colors-dreamcoder-night.sh
deleted file mode 100644
index 01a82b8b..00000000
--- a/DreamcoderThemes/dreamcoder/ls-colors-dreamcoder-night.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-# Sourced snippet: never set shell options here.
-# Dreamcoder Dark Night — LS_COLORS / eza
-export LS_COLORS='di=38;2;164;175;238:ex=38;2;180;140;233:ln=38;2;98;185;226:or=48;2;209;161;38;38;2;0;0;0:so=38;2;67;180;139:pi=38;2;209;161;38:bd=38;2;232;132;132:cd=38;2;232;132;132:su=48;2;0;0;0;38;2;180;140;233:sg=48;2;0;0;0;38;2;180;140;233:tw=38;2;164;175;238;48;2;9;9;9:ow=38;2;164;175;238;48;2;9;9;9:st=38;2;164;175;238;48;2;9;9;9:*.sh=38;2;180;140;233:*.bash=38;2;180;140;233:*.zsh=38;2;180;140;233:*.fish=38;2;180;140;233:*.py=38;2;180;140;233:*.rb=38;2;180;140;233:*.rs=38;2;180;140;233:*.go=38;2;180;140;233:*.ts=38;2;180;140;233:*.js=38;2;180;140;233:*.jpg=38;2;185;139;235:*.jpeg=38;2;185;139;235:*.png=38;2;185;139;235:*.gif=38;2;185;139;235:*.bmp=38;2;185;139;235:*.svg=38;2;185;139;235:*.webp=38;2;185;139;235:*.ico=38;2;185;139;235:*.mp3=38;2;185;139;235:*.wav=38;2;185;139;235:*.flac=38;2;185;139;235:*.ogg=38;2;185;139;235:*.m4a=38;2;185;139;235:*.mp4=38;2;185;139;235:*.mkv=38;2;185;139;235:*.webm=38;2;185;139;235:*.mov=38;2;185;139;235:*.pdf=38;2;171;171;171:*.doc=38;2;171;171;171:*.docx=38;2;171;171;171:*.odt=38;2;171;171;171:*.xls=38;2;171;171;171:*.xlsx=38;2;171;171;171:*.ppt=38;2;171;171;171:*.pptx=38;2;171;171;171:*.txt=38;2;171;171;171:*.cfg=38;2;171;171;171:*.conf=38;2;171;171;171:*.json=38;2;171;171;171:*.yaml=38;2;171;171;171:*.yml=38;2;171;171;171:*.toml=38;2;171;171;171:*.xml=38;2;171;171;171:*.css=38;2;171;171;171:*.html=38;2;171;171;171:*.c=38;2;171;171;171:*.h=38;2;171;171;171:*.cpp=38;2;171;171;171:*.hpp=38;2;171;171;171:*.swp=38;2;171;171;171:*.swo=38;2;171;171;171:*.bak=38;2;171;171;171:*.orig=38;2;171;171;171:*.md=#a4afee'
-export EZA_COLORS='di=38;2;164;175;238:ex=38;2;180;140;233:ln=38;2;98;185;226:so=38;2;67;180;139:pi=38;2;209;161;38:bd=38;2;232;132;132:cd=38;2;232;132;132:uw=38;2;209;161;38:ux=38;2;232;132;132:gwx=38;2;209;161;38:*.md=#a4afee'
diff --git a/DreamcoderThemes/dreamcoder/obsidian-dreamcoder-night.css b/DreamcoderThemes/dreamcoder/obsidian-dreamcoder-night.css
deleted file mode 100644
index 9c254f43..00000000
--- a/DreamcoderThemes/dreamcoder/obsidian-dreamcoder-night.css
+++ /dev/null
@@ -1,143 +0,0 @@
-/* ========================================================
- Dreamcoder Dark Night — Obsidian CSS snippet
- ========================================================
- Place in your vault's .obsidian/snippets/ folder.
- Enable in Settings → Appearance → CSS snippets. */
-
-.theme-dark {
- /* Base — raw palette colors for backgrounds and surfaces */
- --background-primary: #000000;
- --background-primary-alt: #090909;
- --background-secondary: #090909;
- --background-secondary-alt: #0b0b0d;
- --background-modifier-border: #5c5c5c;
- --background-modifier-border-hover: #797979;
- --background-modifier-form-field: #090909;
- --background-modifier-success: #112d23;
- --background-modifier-error: #3a2121;
- --background-modifier-message: #0b0b0d;
-
- /* Text — guarded against background for accessibility */
- --text-normal: #c6c6c6;
- --text-muted: #ababab;
- --text-faint: #959595;
- --text-accent: #a4afee;
- --text-accent-hover: #b48ce9;
- --text-error: #e88484;
- --text-warning: #d1a126;
- --text-success: #43b48b;
- --text-selection: #323232;
- --text-on-accent: #000000;
-
- /* Interactive */
- --interactive-normal: #0b0b0d;
- --interactive-hover: #323232;
- --interactive-accent: #a4afee;
- --interactive-accent-hover: #b48ce9;
- --interactive-success: #43b48b;
-
- /* Scrollbar */
- --scrollbar-bg: transparent;
- --scrollbar-thumb-bg: #5c5c5c;
- --scrollbar-active-thumb-bg: #797979;
-
- /* Code — syntax highlighting colors */
- --code-normal: #c6c6c6;
- --code-comment: #b3b3b3;
- --code-punctuation: #ababab;
- --code-keyword: #a4afee;
- --code-operator: #b48ce9;
- --code-function: #b48ce9;
- --code-string: #43b48b;
- --code-number: #b98beb;
- --code-tag: #a4afee;
- --code-important: #e88484;
- --code-background: #090909;
-
- /* Heading */
- --h1-color: #d3d3d3;
- --h2-color: #d3d3d3;
- --h3-color: #b48ce9;
- --h4-color: #62b9e2;
- --h5-color: #ababab;
- --h6-color: #959595;
-
- /* Link */
- --link-color: #a4afee;
- --link-color-hover: #b48ce9;
- --link-external-color: #62b9e2;
- --link-external-color-hover: #b48ce9;
-
- /* Checkbox */
- --checkbox-color: #a4afee;
- --checkbox-color-hover: #b48ce9;
- --checkbox-border-color: #5c5c5c;
- --checkbox-mark-color: #000000;
-
- /* Table */
- --table-header-background: #0b0b0d;
- --table-header-background-hover: #1b1b1b;
- --table-row-background-hover: #090909;
- --table-border-color: #5c5c5c;
-
- /* Graph */
- --graph-line: #5c5c5c;
- --graph-node: #ababab;
- --graph-node-focused: #a4afee;
- --graph-node-tag: #62b9e2;
- --graph-node-attachment: #43b48b;
-}
-
-/* Headings */
-.markdown-rendered h1 { color: var(--h1-color); }
-.markdown-rendered h2 { color: var(--h2-color); }
-.markdown-rendered h3 { color: var(--h3-color); }
-.markdown-rendered h4 { color: var(--h4-color); }
-.markdown-rendered h5 { color: var(--h5-color); }
-.markdown-rendered h6 { color: var(--h6-color); }
-
-/* Tags */
-.tag {
- background-color: #191a24;
- color: #a4afee;
- border-radius: 4px;
- padding: 0 6px;
-}
-
-/* Blockquotes */
-blockquote {
- border-color: #a4afee !important;
-}
-
-/* Search highlights */
-.search-result-file-matched-text,
-.is-selected .search-result-file-matched-text,
-mark {
- background-color: #212330 !important;
- color: #c6c6c6 !important;
-}
-
-/* Active line in edit mode */
-.cm-active {
- background-color: #090909 !important;
-}
-
-/* Selection */
-::selection {
- background-color: #060607 !important;
-}
-
-/* Tooltip */
-.tooltip {
- background-color: #0b0b0d !important;
- color: #c6c6c6 !important;
-}
-
-/* Menu */
-.menu {
- background-color: #090909 !important;
-}
-
-.menu-item:hover {
- background-color: #0b0b0d !important;
-}
diff --git a/DreamcoderThemes/dreamcoder/rofi-night.rasi b/DreamcoderThemes/dreamcoder/rofi-night.rasi
deleted file mode 100644
index 466f85fd..00000000
--- a/DreamcoderThemes/dreamcoder/rofi-night.rasi
+++ /dev/null
@@ -1,38 +0,0 @@
-/* Dreamcoder color layer for Rofi. */
-* {
- background: rgba(11, 11, 13, 0.92);
- background-alt: #090909;
- foreground: #c6c6c6;
- muted: #ababab;
- selected: rgba(81, 84, 212, 0.28);
- active: #a4afee;
- border-ui: #797979;
- focus: #3a76d6;
- urgent: #e88484;
- border-color: #797979;
-}
-
-window {
- background-color: @background;
- border: 1px;
- border-color: @border-ui;
- border-radius: 18px;
-}
-
-entry {
- background-color: @selected;
- text-color: @foreground;
- border: 1px;
- border-color: @focus;
- border-radius: 12px;
-}
-
-element selected {
- background-color: @selected;
- text-color: @foreground;
- border: 0 0 0 3px;
- border-color: @focus;
-}
-
-element-text { text-color: @muted; }
-element selected element-text { text-color: @foreground; }
diff --git a/DreamcoderThemes/dreamcoder/targets.json b/DreamcoderThemes/dreamcoder/targets.json
index 79b9a6ca..260004d2 100644
--- a/DreamcoderThemes/dreamcoder/targets.json
+++ b/DreamcoderThemes/dreamcoder/targets.json
@@ -2,42 +2,42 @@
"$schema":"./targets.schema.json",
"schema":"dreamcoder.theme-targets.v1",
"targets":[
- {"id":"antigravity","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-antigravity","destinations":["$XDG_CONFIG_HOME/dreamcoder/antigravity"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/antigravity","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"antigravity","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/antigravity-dark","light":"DreamcoderThemes/dreamcoder/generated/antigravity-light"}}},
- {"id":"bat","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-bat","destinations":["$XDG_CONFIG_HOME/dreamcoder/bat"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/bat","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"bat","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/bat-dark","light":"DreamcoderThemes/dreamcoder/generated/bat-light"}}},
- {"id":"btop","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-btop","destinations":["$XDG_CONFIG_HOME/dreamcoder/btop"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/btop","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"btop","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/btop-dark","light":"DreamcoderThemes/dreamcoder/generated/btop-light"}}},
- {"id":"cava","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-cava","destinations":["$XDG_CONFIG_HOME/dreamcoder/cava"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/cava","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"cava","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/cava-dark","light":"DreamcoderThemes/dreamcoder/generated/cava-light"}}},
- {"id":"codex-cli-settings","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-codex-cli-settings","destinations":["$XDG_CONFIG_HOME/dreamcoder/codex-cli-settings"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/codex-cli-settings","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"codex_cli_settings","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/codex-cli-settings-dark","light":"DreamcoderThemes/dreamcoder/generated/codex-cli-settings-light"}}},
- {"id":"codex-textmate","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-codex-textmate","destinations":["$XDG_CONFIG_HOME/dreamcoder/codex-textmate"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/codex-textmate","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"codex_textmate","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/codex-textmate-dark","light":"DreamcoderThemes/dreamcoder/generated/codex-textmate-light"}}},
- {"id":"delta","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-delta","destinations":["$XDG_CONFIG_HOME/dreamcoder/delta"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/delta","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"delta","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/delta-dark","light":"DreamcoderThemes/dreamcoder/generated/delta-light"}}},
+ {"id":"antigravity","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-antigravity","destinations":["$XDG_CONFIG_HOME/dreamcoder/antigravity"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/antigravity","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"antigravity","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/antigravity-dark","light":"DreamcoderThemes/dreamcoder/generated/antigravity-light"}}},
+ {"id":"bat","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-bat","destinations":["$XDG_CONFIG_HOME/dreamcoder/bat"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/bat","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"bat","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/bat-dark","light":"DreamcoderThemes/dreamcoder/generated/bat-light"}}},
+ {"id":"btop","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-btop","destinations":["$XDG_CONFIG_HOME/dreamcoder/btop"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/btop","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"btop","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/btop-dark","light":"DreamcoderThemes/dreamcoder/generated/btop-light"}}},
+ {"id":"cava","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-cava","destinations":["$XDG_CONFIG_HOME/dreamcoder/cava"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/cava","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"cava","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/cava-dark","light":"DreamcoderThemes/dreamcoder/generated/cava-light"}}},
+ {"id":"codex-cli-settings","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-codex-cli-settings","destinations":["$XDG_CONFIG_HOME/dreamcoder/codex-cli-settings"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/codex-cli-settings","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"codex_cli_settings","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/codex-cli-settings-dark","light":"DreamcoderThemes/dreamcoder/generated/codex-cli-settings-light"}}},
+ {"id":"codex-textmate","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-codex-textmate","destinations":["$XDG_CONFIG_HOME/dreamcoder/codex-textmate"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/codex-textmate","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"codex_textmate","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/codex-textmate-dark","light":"DreamcoderThemes/dreamcoder/generated/codex-textmate-light"}}},
+ {"id":"delta","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-delta","destinations":["$XDG_CONFIG_HOME/dreamcoder/delta"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/delta","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"delta","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/delta-dark","light":"DreamcoderThemes/dreamcoder/generated/delta-light"}}},
{"id":"doctor-maintenance","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-doctor-maintenance","destinations":["$XDG_CONFIG_HOME/dreamcoder/doctor-maintenance"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/doctor-maintenance","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true},
- {"id":"dunst","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-dunst","destinations":["$XDG_CONFIG_HOME/dreamcoder/dunst"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/dunst","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"dunst","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/dunst-dark","light":"DreamcoderThemes/dreamcoder/generated/dunst-light"}}},
+ {"id":"dunst","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-dunst","destinations":["$XDG_CONFIG_HOME/dreamcoder/dunst"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/dunst","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"dunst","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/dunst-dark","light":"DreamcoderThemes/dreamcoder/generated/dunst-light"}}},
{"id":"dusk-runtime","classification":"excluded","reason":"Runtime activation is prohibited; dusk remains design-system-only.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-dusk-runtime","destinations":["$XDG_CONFIG_HOME/dreamcoder/dusk-runtime"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/dusk-runtime","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true,"excluded_behavior":"reject-before-mutation"},
- {"id":"firefox","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-firefox","destinations":["$XDG_CONFIG_HOME/dreamcoder/firefox"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/firefox","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"firefox","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/firefox-dark","light":"DreamcoderThemes/dreamcoder/generated/firefox-light"}}},
- {"id":"fzf","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-fzf","destinations":["$XDG_CONFIG_HOME/dreamcoder/fzf"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/fzf","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"fzf","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/fzf-dark","light":"DreamcoderThemes/dreamcoder/generated/fzf-light"}}},
+ {"id":"firefox","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-firefox","destinations":["$XDG_CONFIG_HOME/dreamcoder/firefox"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/firefox","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"firefox","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/firefox-dark","light":"DreamcoderThemes/dreamcoder/generated/firefox-light"}}},
+ {"id":"fzf","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-fzf","destinations":["$XDG_CONFIG_HOME/dreamcoder/fzf"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/fzf","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"fzf","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/fzf-dark","light":"DreamcoderThemes/dreamcoder/generated/fzf-light"}}},
{"id":"generated-repository-contract","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-generated-repository-contract","destinations":["$XDG_CONFIG_HOME/dreamcoder/generated-repository-contract"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/generated-repository-contract","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true},
{"id":"gentleman-module-plan","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-gentleman-module-plan","destinations":["$XDG_CONFIG_HOME/dreamcoder/gentleman-module-plan"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/gentleman-module-plan","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true},
- {"id":"ghostty","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-ghostty","destinations":["$XDG_CONFIG_HOME/dreamcoder/ghostty"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ghostty","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"ghostty","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/ghostty-dark","light":"DreamcoderThemes/dreamcoder/generated/ghostty-light"}}},
+ {"id":"ghostty","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-ghostty","destinations":["$XDG_CONFIG_HOME/dreamcoder/ghostty"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ghostty","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"ghostty","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/ghostty-dark","light":"DreamcoderThemes/dreamcoder/generated/ghostty-light"}}},
{"id":"herdr","classification":"excluded","reason":"gated: unsupported-contract until version-bound schema and reload evidence exists.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-herdr","destinations":["$XDG_CONFIG_HOME/dreamcoder/herdr"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/herdr","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true,"excluded_behavior":"report-unsupported-contract"},
- {"id":"hyprland-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-hyprland-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/hyprland-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/hyprland-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"hyprland_colors","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/hyprland-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/hyprland-colors-light"}}},
- {"id":"kitty","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-kitty","destinations":["$XDG_CONFIG_HOME/dreamcoder/kitty"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/kitty","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"kitty","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/kitty-dark","light":"DreamcoderThemes/dreamcoder/generated/kitty-light"}}},
- {"id":"ls-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-ls-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/ls-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ls-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"ls_colors","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/ls-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/ls-colors-light"}}},
+ {"id":"hyprland-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-hyprland-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/hyprland-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/hyprland-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"hyprland_colors","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/hyprland-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/hyprland-colors-light"}}},
+ {"id":"kitty","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-kitty","destinations":["$XDG_CONFIG_HOME/dreamcoder/kitty"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/kitty","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"kitty","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/kitty-dark","light":"DreamcoderThemes/dreamcoder/generated/kitty-light"}}},
+ {"id":"ls-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-ls-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/ls-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ls-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"ls_colors","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/ls-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/ls-colors-light"}}},
{"id":"ml4w-hook-plan","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-ml4w-hook-plan","destinations":["$XDG_CONFIG_HOME/dreamcoder/ml4w-hook-plan"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ml4w-hook-plan","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true},
{"id":"ml4w-structure","classification":"excluded","reason":"ML4W layout and lifecycle remain outside Dreamcoder ownership.","owner":"ml4w","availability":{"kind":"command"},"install":{"module":"Dreamcoder-ml4w-structure","destinations":["$XDG_CONFIG_HOME/dreamcoder/ml4w-structure"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/ml4w-structure","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true,"excluded_behavior":"report-ml4w-owned"},
- {"id":"neovim","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-neovim","destinations":["$XDG_CONFIG_HOME/dreamcoder/neovim"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/neovim","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"neovim","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/neovim-dark","light":"DreamcoderThemes/dreamcoder/generated/neovim-light"}}},
- {"id":"obsidian","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-obsidian","destinations":["$XDG_CONFIG_HOME/dreamcoder/obsidian"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/obsidian","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"obsidian","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/obsidian-dark","light":"DreamcoderThemes/dreamcoder/generated/obsidian-light"}}},
- {"id":"opencode-theme","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-opencode-theme","destinations":["$XDG_CONFIG_HOME/dreamcoder/opencode-theme"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/opencode-theme","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"opencode_theme","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/opencode-theme-dark","light":"DreamcoderThemes/dreamcoder/generated/opencode-theme-light"}}},
- {"id":"opencode-tui-selection","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-opencode-tui-selection","destinations":["$XDG_CONFIG_HOME/dreamcoder/opencode-tui-selection"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/opencode-tui-selection","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"opencode_tui_selection","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/opencode-tui-selection-dark","light":"DreamcoderThemes/dreamcoder/generated/opencode-tui-selection-light"}}},
- {"id":"pi","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-pi","destinations":["$XDG_CONFIG_HOME/dreamcoder/pi"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/pi","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"pi","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/pi-dark","light":"DreamcoderThemes/dreamcoder/generated/pi-light"}}},
- {"id":"rofi-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-rofi-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/rofi-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/rofi-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"rofi_colors","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/rofi-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/rofi-colors-light"}}},
- {"id":"shell-syntax","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-shell-syntax","destinations":["$XDG_CONFIG_HOME/dreamcoder/shell-syntax"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/shell-syntax","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"shell_syntax","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/shell-syntax-dark","light":"DreamcoderThemes/dreamcoder/generated/shell-syntax-light"}}},
- {"id":"starship","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-starship","destinations":["$XDG_CONFIG_HOME/dreamcoder/starship"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/starship","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"starship","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/starship-dark","light":"DreamcoderThemes/dreamcoder/generated/starship-light"}}},
+ {"id":"neovim","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-neovim","destinations":["$XDG_CONFIG_HOME/dreamcoder/neovim"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/neovim","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"neovim","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/neovim-dark","light":"DreamcoderThemes/dreamcoder/generated/neovim-light"}}},
+ {"id":"obsidian","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-obsidian","destinations":["$XDG_CONFIG_HOME/dreamcoder/obsidian"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/obsidian","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"obsidian","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/obsidian-dark","light":"DreamcoderThemes/dreamcoder/generated/obsidian-light"}}},
+ {"id":"opencode-theme","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-opencode-theme","destinations":["$XDG_CONFIG_HOME/dreamcoder/opencode-theme"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/opencode-theme","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"opencode_theme","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/opencode-theme-dark","light":"DreamcoderThemes/dreamcoder/generated/opencode-theme-light"}}},
+ {"id":"opencode-tui-selection","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-opencode-tui-selection","destinations":["$XDG_CONFIG_HOME/dreamcoder/opencode-tui-selection"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/opencode-tui-selection","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"opencode_tui_selection","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/opencode-tui-selection-dark","light":"DreamcoderThemes/dreamcoder/generated/opencode-tui-selection-light"}}},
+ {"id":"pi","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-pi","destinations":["$XDG_CONFIG_HOME/dreamcoder/pi"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/pi","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"pi","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/pi-dark","light":"DreamcoderThemes/dreamcoder/generated/pi-light"}}},
+ {"id":"rofi-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-rofi-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/rofi-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/rofi-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"rofi_colors","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/rofi-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/rofi-colors-light"}}},
+ {"id":"shell-syntax","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-shell-syntax","destinations":["$XDG_CONFIG_HOME/dreamcoder/shell-syntax"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/shell-syntax","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"shell_syntax","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/shell-syntax-dark","light":"DreamcoderThemes/dreamcoder/generated/shell-syntax-light"}}},
+ {"id":"starship","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-starship","destinations":["$XDG_CONFIG_HOME/dreamcoder/starship"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/starship","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"starship","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/starship-dark","light":"DreamcoderThemes/dreamcoder/generated/starship-light"}}},
{"id":"theme-scheduler","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-theme-scheduler","destinations":["$XDG_CONFIG_HOME/dreamcoder/theme-scheduler"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/theme-scheduler","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true},
- {"id":"tmux","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-tmux","destinations":["$XDG_CONFIG_HOME/dreamcoder/tmux"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/tmux","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"tmux","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/tmux-dark","light":"DreamcoderThemes/dreamcoder/generated/tmux-light"}}},
- {"id":"tmux-kanagawa-bridge","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-tmux-kanagawa-bridge","destinations":["$XDG_CONFIG_HOME/dreamcoder/tmux-kanagawa-bridge"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/tmux-kanagawa-bridge","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"tmux_kanagawa_bridge","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/tmux-kanagawa-bridge-dark","light":"DreamcoderThemes/dreamcoder/generated/tmux-kanagawa-bridge-light"}}},
+ {"id":"tmux","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-tmux","destinations":["$XDG_CONFIG_HOME/dreamcoder/tmux"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/tmux","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"tmux","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/tmux-dark","light":"DreamcoderThemes/dreamcoder/generated/tmux-light"}}},
+ {"id":"tmux-kanagawa-bridge","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-tmux-kanagawa-bridge","destinations":["$XDG_CONFIG_HOME/dreamcoder/tmux-kanagawa-bridge"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/tmux-kanagawa-bridge","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"tmux_kanagawa_bridge","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/tmux-kanagawa-bridge-dark","light":"DreamcoderThemes/dreamcoder/generated/tmux-kanagawa-bridge-light"}}},
{"id":"unrelated-application-settings","classification":"excluded","reason":"Non-theme application settings are not rollout artifacts.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-unrelated-application-settings","destinations":["$XDG_CONFIG_HOME/dreamcoder/unrelated-application-settings"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/unrelated-application-settings","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"selector_only":true,"excluded_behavior":"report-out-of-scope"},
- {"id":"wallpaper-matugen-hook","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-wallpaper-matugen-hook","destinations":["$XDG_CONFIG_HOME/dreamcoder/wallpaper-matugen-hook"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/wallpaper-matugen-hook","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"wallpaper_matugen_hook","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/wallpaper-matugen-hook-dark","light":"DreamcoderThemes/dreamcoder/generated/wallpaper-matugen-hook-light"}}},
- {"id":"warp","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-warp","destinations":["$XDG_CONFIG_HOME/dreamcoder/warp"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/warp","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"warp","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/warp-dark","light":"DreamcoderThemes/dreamcoder/generated/warp-light"}}},
- {"id":"waybar-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-waybar-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/waybar-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/waybar-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"waybar_colors","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/waybar-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/waybar-colors-light"}}},
- {"id":"zellij","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-zellij","destinations":["$XDG_CONFIG_HOME/dreamcoder/zellij"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/zellij","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"zellij","modes":["dark","light","night"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/zellij-dark","light":"DreamcoderThemes/dreamcoder/generated/zellij-light"}}}
+ {"id":"wallpaper-matugen-hook","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-wallpaper-matugen-hook","destinations":["$XDG_CONFIG_HOME/dreamcoder/wallpaper-matugen-hook"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/wallpaper-matugen-hook","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"wallpaper_matugen_hook","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/wallpaper-matugen-hook-dark","light":"DreamcoderThemes/dreamcoder/generated/wallpaper-matugen-hook-light"}}},
+ {"id":"warp","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-warp","destinations":["$XDG_CONFIG_HOME/dreamcoder/warp"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/warp","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"warp","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/warp-dark","light":"DreamcoderThemes/dreamcoder/generated/warp-light"}}},
+ {"id":"waybar-colors","classification":"required","reason":"Core Dreamcoder rollout contract.","owner":"dreamcoder","availability":{"kind":"always"},"install":{"module":"Dreamcoder-waybar-colors","destinations":["$XDG_CONFIG_HOME/dreamcoder/waybar-colors"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/waybar-colors","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"waybar_colors","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/waybar-colors-dark","light":"DreamcoderThemes/dreamcoder/generated/waybar-colors-light"}}},
+ {"id":"zellij","classification":"optional","reason":"External integration; unavailable installations receive actionable reporting.","owner":"dreamcoder","availability":{"kind":"command"},"install":{"module":"Dreamcoder-zellij","destinations":["$XDG_CONFIG_HOME/dreamcoder/zellij"],"allowed_ownership":["missing","managed"]},"activation":{"selector":"managed-selectors/zellij","validate":{"kind":"anchor"},"reload":{"kind":"none","observable":false},"rollback":"selector"},"version":{"constraint":null},"render":{"renderer":"zellij","modes":["dark","light"],"repository_outputs":{"dark":"DreamcoderThemes/dreamcoder/generated/zellij-dark","light":"DreamcoderThemes/dreamcoder/generated/zellij-light"}}}
]
}
diff --git a/DreamcoderThemes/dreamcoder/targets.schema.json b/DreamcoderThemes/dreamcoder/targets.schema.json
index b003b159..89cb3d4f 100644
--- a/DreamcoderThemes/dreamcoder/targets.schema.json
+++ b/DreamcoderThemes/dreamcoder/targets.schema.json
@@ -1 +1 @@
-{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","required":["$schema","schema","targets"],"properties":{"$schema":{"const":"./targets.schema.json"},"schema":{"const":"dreamcoder.theme-targets.v1"},"targets":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/target"}}},"additionalProperties":false,"$defs":{"target":{"type":"object","required":["id","classification","reason","owner","availability","install","activation","version"],"properties":{"id":{"type":"string","minLength":1},"classification":{"enum":["required","optional","excluded"]},"reason":{"type":"string","minLength":1},"owner":{"type":"string","minLength":1},"availability":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string"}},"additionalProperties":false},"render":{"type":"object","required":["renderer","modes","repository_outputs"],"properties":{"renderer":{"type":"string"},"modes":{"type":"array","minItems":1,"items":{"type":"string","description":"Runtime render mode: dark, light, or night. dusk is design-system-only and rejected by the package loader."}},"repository_outputs":{"type":"object","required":["dark","light"],"properties":{"dark":{"type":"string"},"light":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false},"selector_only":{"const":true},"install":{"type":"object","required":["module","destinations","allowed_ownership"],"properties":{"module":{"type":"string"},"destinations":{"type":"array","items":{"type":"string"}},"allowed_ownership":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"activation":{"type":"object","required":["selector","validate","reload","rollback"],"properties":{"selector":{"type":"string"},"validate":{"type":"object"},"reload":{"type":"object","required":["kind","observable"],"properties":{"kind":{"type":"string"},"observable":{"type":"boolean"}},"additionalProperties":false},"rollback":{"type":"string"}},"additionalProperties":false},"version":{"type":"object","required":["constraint"],"properties":{"constraint":{"type":["string","null"]}},"additionalProperties":false},"excluded_behavior":{"type":"string","minLength":1}},"allOf":[{"anyOf":[{"required":["render"]},{"required":["selector_only"]}]},{"if":{"properties":{"classification":{"const":"excluded"}}},"then":{"required":["excluded_behavior"]}}],"additionalProperties":false}}}
+{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","required":["$schema","schema","targets"],"properties":{"$schema":{"const":"./targets.schema.json"},"schema":{"const":"dreamcoder.theme-targets.v1"},"targets":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/target"}}},"additionalProperties":false,"$defs":{"target":{"type":"object","required":["id","classification","reason","owner","availability","install","activation","version"],"properties":{"id":{"type":"string","minLength":1},"classification":{"enum":["required","optional","excluded"]},"reason":{"type":"string","minLength":1},"owner":{"type":"string","minLength":1},"availability":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string"}},"additionalProperties":false},"render":{"type":"object","required":["renderer","modes","repository_outputs"],"properties":{"renderer":{"type":"string"},"modes":{"type":"array","minItems":1,"items":{"type":"string","description":"Runtime render mode: dark or light. dusk is design-system-only and rejected by the package loader."}},"repository_outputs":{"type":"object","required":["dark","light"],"properties":{"dark":{"type":"string"},"light":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false},"selector_only":{"const":true},"install":{"type":"object","required":["module","destinations","allowed_ownership"],"properties":{"module":{"type":"string"},"destinations":{"type":"array","items":{"type":"string"}},"allowed_ownership":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"activation":{"type":"object","required":["selector","validate","reload","rollback"],"properties":{"selector":{"type":"string"},"validate":{"type":"object"},"reload":{"type":"object","required":["kind","observable"],"properties":{"kind":{"type":"string"},"observable":{"type":"boolean"}},"additionalProperties":false},"rollback":{"type":"string"}},"additionalProperties":false},"version":{"type":"object","required":["constraint"],"properties":{"constraint":{"type":["string","null"]}},"additionalProperties":false},"excluded_behavior":{"type":"string","minLength":1}},"allOf":[{"anyOf":[{"required":["render"]},{"required":["selector_only"]}]},{"if":{"properties":{"classification":{"const":"excluded"}}},"then":{"required":["excluded_behavior"]}}],"additionalProperties":false}}}
diff --git a/DreamcoderThemes/dreamcoder/tmux-dreamcoder-night.conf b/DreamcoderThemes/dreamcoder/tmux-dreamcoder-night.conf
deleted file mode 100644
index 0dc431d4..00000000
--- a/DreamcoderThemes/dreamcoder/tmux-dreamcoder-night.conf
+++ /dev/null
@@ -1,47 +0,0 @@
-# Dreamcoder Dark Night — tmux dark theme
-# Text-only minimal hierarchy: no blocks, no pills, no powerline.
-# Source: tmux source-file ~/.config/tmux/tmux-dreamcoder-dark.conf
-
-# Default terminal colours (truecolor)
-set -g default-terminal "tmux-256color"
-set -ga terminal-overrides ",*256col*:Tc"
-set -ga terminal-overrides '*:Ss=\E[%p1%d q:Se=\E[2 q'
-
-# Panes
-set -g pane-border-style "fg=#1b1b1b,bg=default"
-set -g pane-active-border-style "fg=#3a76d6,bg=default"
-set -g display-panes-colour "#a4afee"
-set -g display-panes-active-colour "#a4afee"
-# Semantic token for status integrations that report successful commands.
-set -g @dreamcoder-success-colour "#43b48b"
-
-# ─── Minimal status bar (text-only hierarchy) ─────────
-set -g status-style "bg=#090909"
-set -g status-position top
-set -g status-justify left
-set -g status-interval 5
-set -g status-left-length 60
-set -g status-right-length 120
-
-# Left: session name bold + middot separator
-set -g status-left "#[fg=#c6c6c6,bold] #S #[fg=#ababab]· "
-
-# Window tabs — text-only, muted / accent active
-set -g window-status-style "fg=#ababab"
-setw -g window-status-format "#I:#W "
-set -g window-status-current-style "fg=#a4afee,bold"
-setw -g window-status-current-format "#I:#W "
-setw -g window-status-separator ""
-
-# Right: path muted + time bold
-set -g status-right "#[fg=#ababab]#{b:pane_current_path} #[fg=#c6c6c6,bold]%H:%M "
-
-# Message
-set -g message-style "fg=#c6c6c6,bg=#090909"
-set -g message-command-style "fg=#c6c6c6,bg=#090909"
-
-# Mode (copy mode)
-setw -g mode-style "fg=#000000,bg=#3a76d6"
-
-# Bell
-setw -g window-status-bell-style "fg=#000000,bg=#e88484,bold"
diff --git a/DreamcoderThemes/dreamcoder/tokens.json b/DreamcoderThemes/dreamcoder/tokens.json
index 3f18682d..4aed1acb 100644
--- a/DreamcoderThemes/dreamcoder/tokens.json
+++ b/DreamcoderThemes/dreamcoder/tokens.json
@@ -198,14 +198,6 @@
"scrim": "rgba(26, 18, 12, 0.42)"
}
},
- "render_profiles": {
- "night": {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02
- }
- },
"guardrails": {
"minimum_text_contrast": 4.5,
"preferred_main_text_contrast": 7.0,
diff --git a/DreamcoderThemes/dreamcoder/tokens.schema.json b/DreamcoderThemes/dreamcoder/tokens.schema.json
index 43f9a5e3..f6d69168 100644
--- a/DreamcoderThemes/dreamcoder/tokens.schema.json
+++ b/DreamcoderThemes/dreamcoder/tokens.schema.json
@@ -2,49 +2,12 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Dreamcoder OS design tokens",
"type": "object",
- "required": ["name", "version", "modes", "render_profiles", "guardrails"],
+ "required": ["name", "version", "modes", "guardrails"],
"properties": {
"$schema": { "type": "string" },
"name": { "const": "Dreamcoder OS" },
"version": { "type": "integer", "minimum": 1 },
"description": { "type": "string" },
- "render_profiles": {
- "type": "object",
- "required": ["night"],
- "properties": {
- "night": {
- "type": "object",
- "required": [
- "brightness_factor",
- "saturation_factor",
- "maximum_corrective_delta",
- "corrective_step"
- ],
- "properties": {
- "brightness_factor": {
- "type": "number",
- "exclusiveMinimum": 0,
- "maximum": 1
- },
- "saturation_factor": {
- "type": "number",
- "exclusiveMinimum": 0,
- "maximum": 1
- },
- "maximum_corrective_delta": {
- "type": "number",
- "minimum": 0,
- "maximum": 0.2
- },
- "corrective_step": {
- "type": "number",
- "exclusiveMinimum": 0,
- "maximum": 0.2
- }
- }
- }
- }
- },
"modes": {
"type": "object",
"required": ["dark", "light", "dusk"],
diff --git a/DreamcoderThemes/dreamcoder/waybar-night.css b/DreamcoderThemes/dreamcoder/waybar-night.css
deleted file mode 100644
index 072a737e..00000000
--- a/DreamcoderThemes/dreamcoder/waybar-night.css
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Dreamcoder color layer for Waybar. */
-@define-color bg #000000;
-@define-color bg-soft #090909;
-@define-color surface #090909;
-@define-color surface-2 #1b1b1b;
-@define-color surface-3 #282828;
-@define-color text #c6c6c6;
-@define-color text-heading #d3d3d3;
-@define-color muted #ababab;
-@define-color subtle #959595;
-@define-color comment #b3b3b3;
-@define-color border #5c5c5c;
-@define-color border-ui #797979;
-@define-color focus #3a76d6;
-@define-color accent #a4afee;
-@define-color accent-2 #b48ce9;
-@define-color diagnostic #62b9e2;
-@define-color lavender #b48ce9;
-@define-color error #e88484;
-@define-color success #43b48b;
-@define-color warning #d1a126;
-@define-color link #a4afee;
-@define-color link-hover #b48ce9;
-
-window#waybar {
- background: rgba(11, 11, 13, 0.92);
- color: @text;
- border-bottom: 1px solid alpha(@accent, 0.26);
-}
-
-#workspaces button {
- color: @muted;
- background: transparent;
- border: 1px solid transparent;
-}
-
-#workspaces button.active {
- color: @accent;
- background: rgba(81, 84, 212, 0.28);
- border-color: alpha(@focus, 0.82);
-}
-
-#clock,
-#battery,
-#network,
-#pulseaudio,
-#cpu,
-#memory,
-#tray {
- background: rgba(198, 198, 198, 0.08);
- color: @text;
- border: 1px solid alpha(@border-ui, 0.78);
-}
-
-#battery.warning { color: @warning; }
-#battery.critical { color: @error; }
-#network.disconnected { color: @error; }
diff --git a/DreamcoderThemes/dreamcoder/zsh-syntax-highlighting-dreamcoder-night.zsh b/DreamcoderThemes/dreamcoder/zsh-syntax-highlighting-dreamcoder-night.zsh
deleted file mode 100644
index c318c4d0..00000000
--- a/DreamcoderThemes/dreamcoder/zsh-syntax-highlighting-dreamcoder-night.zsh
+++ /dev/null
@@ -1,12 +0,0 @@
-# Dreamcoder Dark Night — zsh-syntax-highlighting
-typeset -A ZSH_HIGHLIGHT_STYLES
-ZSH_HIGHLIGHT_STYLES[default]='fg=#c6c6c6'; ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#e88484'; ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#a4afee,bold'; ZSH_HIGHLIGHT_STYLES[alias]='fg=#b48ce9'
-ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=#b48ce9'; ZSH_HIGHLIGHT_STYLES[builtin]='fg=#a4afee'; ZSH_HIGHLIGHT_STYLES[function]='fg=#b48ce9'; ZSH_HIGHLIGHT_STYLES[command]='fg=#a4afee'
-ZSH_HIGHLIGHT_STYLES[precommand]='fg=#a4afee,italic'; ZSH_HIGHLIGHT_STYLES[commandseparator]='fg=#ababab'; ZSH_HIGHLIGHT_STYLES[hashed-command]='fg=#a4afee'; ZSH_HIGHLIGHT_STYLES[path]='fg=#62b9e2'
-ZSH_HIGHLIGHT_STYLES[path_pathseparator]='fg=#a4afee'; ZSH_HIGHLIGHT_STYLES[path_prefix]='fg=#62b9e2,underline'; ZSH_HIGHLIGHT_STYLES[path_approx]='fg=#d1a126,underline'; ZSH_HIGHLIGHT_STYLES[globbing]='fg=#b48ce9'
-ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#b48ce9'; ZSH_HIGHLIGHT_STYLES[single-hyphen-option]='fg=#62b9e2'; ZSH_HIGHLIGHT_STYLES[double-hyphen-option]='fg=#62b9e2'; ZSH_HIGHLIGHT_STYLES[back-quoted-argument]='fg=#43b48b'
-ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#43b48b'; ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#43b48b'; ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#43b48b'; ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=#b98beb'
-ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=#b98beb'; ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=#b98beb'; ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=#b98beb'; ZSH_HIGHLIGHT_STYLES[assign]='fg=#c6c6c6'
-ZSH_HIGHLIGHT_STYLES[redirection]='fg=#b48ce9'; ZSH_HIGHLIGHT_STYLES[comment]='fg=#b3b3b3,italic'; ZSH_HIGHLIGHT_STYLES[variable]='fg=#b98beb'; ZSH_HIGHLIGHT_STYLES[mathvar]='fg=#b98beb'
-ZSH_HIGHLIGHT_STYLES[null]='fg=#ababab'; ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=#a4afee'; ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=#62b9e2'; ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=#43b48b'
-ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=#b48ce9'; ZSH_HIGHLIGHT_STYLES[cursor-matchingbracket]='fg=#000000,bg=#a4afee'
diff --git a/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Night.yaml b/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Night.yaml
deleted file mode 100644
index 7a01c62e..00000000
--- a/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Night.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Dreamcoder Dark Night
-accent: '#a4afee'
-cursor: '#a4afee'
-background: '#000000'
-foreground: '#c6c6c6'
-details: darker
-terminal_colors:
- normal:
- black: '#777777'
- red: '#e88484'
- green: '#43b48b'
- yellow: '#d1a126'
- blue: '#62b9e2'
- magenta: '#b98beb'
- cyan: '#b48ce9'
- white: '#ababab'
- bright:
- black: '#959595'
- red: '#e29090'
- green: '#5bb796'
- yellow: '#cfa843'
- blue: '#74bbdd'
- magenta: '#bb96e4'
- cyan: '#5384d3'
- white: '#c6c6c6'
diff --git a/DreamcoderZellij/.config/zellij/dreamcoder-night.kdl b/DreamcoderZellij/.config/zellij/dreamcoder-night.kdl
deleted file mode 100644
index 4f61a6a6..00000000
--- a/DreamcoderZellij/.config/zellij/dreamcoder-night.kdl
+++ /dev/null
@@ -1,39 +0,0 @@
-// Dreamcoder Dark Night — Zellij theme (dreamcoder-night)
-// Auto-generated from tokens.json — do not edit manually
-
-themes {
- dreamcoder-night {
- bg "#000000"
- fg "#c6c6c6"
- black "#000000"
- red "#e88484"
- green "#43b48b"
- yellow "#d1a126"
- blue "#62b9e2"
- magenta "#b98beb"
- cyan "#62b9e2"
- white "#c6c6c6"
- orange "#d1a126"
- gray "#ababab"
-
- // UI elements
- border "#5c5c5c"
- border_active "#a4afee"
- border_unfocused "#5c5c5c"
-
- // Tab bar
- tab_bar_bg "#0b0b0d"
- tab_active_bg "#5c5c5c"
- tab_active_fg "#a4afee"
- tab_inactive_bg "#0b0b0d"
- tab_inactive_fg "#ababab"
-
- // Pane frames
- pane_frame_bg "#0b0b0d"
- pane_frame_fg "#5c5c5c"
-
- // Selection
- selection_bg "#a4afee"
- selection_fg "#000000"
- }
-}
diff --git a/INSTALL.md b/INSTALL.md
index ad36a9d6..8ba2fc96 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -142,8 +142,7 @@ systemctl --user status dreamcoder-theme-auto.timer
The timer switches modes by schedule:
-- **07:00-16:00** → Light
-- **16:00-18:00** → Night
+- **07:00-18:00** → Light
- **18:00-07:00** → Dark
---
diff --git a/docs/DREAMCODER_DESIGN_SYSTEM.md b/docs/DREAMCODER_DESIGN_SYSTEM.md
index 7b19490c..d6624faf 100644
--- a/docs/DREAMCODER_DESIGN_SYSTEM.md
+++ b/docs/DREAMCODER_DESIGN_SYSTEM.md
@@ -21,7 +21,7 @@ The system competes as a **developer OS design system**, not as a web component
Required contract:
- **Versioned token schema**: `DreamcoderThemes/dreamcoder/tokens.schema.json` defines the public token shape.
-- **Mode parity**: `light`, `dusk`, and `dark` must expose equivalent semantic roles; the derived `night` render profile preserves those roles from Dreamcoder Dark.
+- **Mode parity**: `light`, `dusk`, and `dark` must expose equivalent semantic roles.
- **Semantic separation**: `accent`, `accent_2`, `diagnostic`, `warning`, `error`, `comment`, `subtle`, `focus`, `border_ui`, and `border_hi` must remain distinct.
- **Generator ownership**: app-specific files are outputs; renderer modules own translation from tokens to target syntax.
- **No silent drift**: regenerated artifacts must be checked into the repo or intentionally ignored.
@@ -62,16 +62,14 @@ Minimums:
- On-accent text: Lc 60
- Heading text: Lc 60 light / 45 dark
-See `DreamcoderThemes/dreamcoder/tokens.json` guardrails for current values. `scripts/verify-theme-health.py` validates Light, Dark, and the derived Night candidate, and any below-floor pair blocks the command.
+See `DreamcoderThemes/dreamcoder/tokens.json` guardrails for current values. `scripts/verify-theme-health.py` validates Light, Dark, and Dusk, and any below-floor pair blocks the command.
## Health verification policy
-`python scripts/verify-theme-health.py` is the blocking health command for Dreamcoder Workbench theme changes. It validates the in-memory design-system contract matrix for `dark`, `light`, and `night`; any contract finding fails health verification.
+`python scripts/verify-theme-health.py` is the blocking health command for Dreamcoder Workbench theme changes. It validates the in-memory design-system contract matrix for `dark`, `light`, and `dusk`; any contract finding fails health verification.
OpenCode theme ownership is limited to `.opencode/themes/`. The health check requires exactly `dreamcoder.json` there. Application configuration under `DreamcoderOpenCode/.config/opencode/`, including `opencode.json`, is not a theme artifact and is intentionally excluded.
-`night` is the low-light render profile derived from Dreamcoder Dark (reduced brightness and saturation via the canonical `render_profiles`). The health check validates it, and runtime activation is supported through `dreamcoder night`.
-
## Governance
Dreamcoder Workbench changes follow this governance model:
@@ -83,7 +81,7 @@ Dreamcoder Workbench changes follow this governance model:
5. **Release notes**: user-visible theme, CLI, repair, or governance changes need a changelog entry.
6. **Compatibility check**: repair/install flows must remain safe after ML4W, Gentleman, Waypaper, or Hyprland updates.
-⚠️ **Known token gaps:** the corrected dual gate surfaces pre-existing debt that Phase 2 corrected in tokens (dark `subtle` → Lc 44.0, `border_ui` WCAG ≥ 4.5, light `disabled` WCAG ≥ 4.5, light/night `success` APCA ≥ 75). The gate is now passing on all canonical palettes; any future below-floor pair blocks health verification.
+⚠️ **Known token gaps:** the corrected dual gate surfaces pre-existing debt that Phase 2 corrected in tokens (dark `subtle` → Lc 44.0, `border_ui` WCAG ≥ 4.5, light `disabled` WCAG ≥ 4.5, light `success` APCA ≥ 75). The gate is now passing on all canonical palettes; any future below-floor pair blocks health verification.
## Release readiness checklist
diff --git a/docs/configuration/editor-config.md b/docs/configuration/editor-config.md
index 31d62621..23140519 100644
--- a/docs/configuration/editor-config.md
+++ b/docs/configuration/editor-config.md
@@ -22,7 +22,7 @@ Dreamcoder colorscheme generated from `tokens.json`:
return {
"dreamcoder.nvim",
opts = {
- variant = "dark", -- or "light" or "night"
+ variant = "dark", -- or "light"
},
}
```
diff --git a/docs/configuration/ml4w.md b/docs/configuration/ml4w.md
index e17f97d5..e5d30f36 100644
--- a/docs/configuration/ml4w.md
+++ b/docs/configuration/ml4w.md
@@ -149,9 +149,7 @@ one of those files silently disappears on the next upgrade. The rule:
restarted with `~/.config/ml4w/listeners.sh --restart gtk-theme-switcher`
(it keeps the body it parsed at start). A listener without a Matugen call is
left untouched with a warning; a missing listener is skipped. Override the
- paths with `ML4W_GTK_LISTENER` and `ML4W_LISTENERS_SCRIPT`. Known gap: with
- the Night render profile persisted, `sync` only regenerates repository
- artifacts, so the block does not restore live Night colours.
+ paths with `ML4W_GTK_LISTENER` and `ML4W_LISTENERS_SCRIPT`.
- **waypaper is optional.** ML4W 2.16 no longer installs it; its
`post_command` is hooked only when `~/.config/waypaper/config.ini` exists.
- **Colour files may be regular files.** ML4W 2.16 ships
diff --git a/docs/configuration/terminal-config.md b/docs/configuration/terminal-config.md
index c2c29608..2d399db5 100644
--- a/docs/configuration/terminal-config.md
+++ b/docs/configuration/terminal-config.md
@@ -14,7 +14,7 @@
Terminals auto-detect `DREAMCODER_THEME_MODE` env var:
```bash
-export DREAMCODER_THEME_MODE="dark" # or "light" or "night"
+export DREAMCODER_THEME_MODE="dark" # or "light"
```
## Font Configuration
diff --git a/docs/configuration/theme-system.md b/docs/configuration/theme-system.md
index a2654551..30d862ce 100644
--- a/docs/configuration/theme-system.md
+++ b/docs/configuration/theme-system.md
@@ -20,8 +20,7 @@ tokens.json → generate-palette-tokens.py → palette_tokens.py
Single source of truth: [`DreamcoderThemes/dreamcoder/tokens.json`](../../DreamcoderThemes/dreamcoder/tokens.json)
The canonical modes are exactly **Dreamcoder Dark**, **Dreamcoder Light**, and
-**Dreamcoder Dusk**. Night is a render profile derived from Dark, not a fourth
-mode. Dark's `surface_policy` permits pure black only for the canvas; functional
+**Dreamcoder Dusk**; only Dark and Light are user-facing. Dark's `surface_policy` permits pure black only for the canvas; functional
and scrollable surfaces use the near-black ladder (`surface0`–`surface3`) to
reduce OLED smear.
diff --git a/docs/generated/DREAMCODER_OPERATOR_REPORT.md b/docs/generated/DREAMCODER_OPERATOR_REPORT.md
index de3c4c16..d015b8a5 100644
--- a/docs/generated/DREAMCODER_OPERATOR_REPORT.md
+++ b/docs/generated/DREAMCODER_OPERATOR_REPORT.md
@@ -25,8 +25,6 @@ Generated from current machine state and Control Center contracts.
│ Active machine profile name. │
│ motion.active = balanced │
│ Active motion preset. (battery, balanced,│
-│ theme.render_profile = standard │
-│ Theme rendering profile (standard or nigh│
├─────────────────────────────────────────────┤
│ Apply: dreamcoder tui set │
│ Safe: add --dry-run --json before applying │
diff --git a/docs/generated/dreamcoder-theme-preview.md b/docs/generated/dreamcoder-theme-preview.md
index dfd4808d..19d97d0c 100644
--- a/docs/generated/dreamcoder-theme-preview.md
+++ b/docs/generated/dreamcoder-theme-preview.md
@@ -240,44 +240,6 @@ Semantic tokens are intentionally distinct:
| `border_hi` | 8.10:1 | PASS |
| `focus` | 4.92:1 | PASS |
-### Night (render profile derived from Dreamcoder Dark) contrast (WCAG 2)
-
-| Token | Ratio vs bg | Target |
-| --- | ---: | --- |
-| `text` | 12.29:1 | AAA |
-| `muted` | 9.14:1 | AA |
-| `comment` | 10.02:1 | AA |
-| `accent` | 9.95:1 | AA |
-| `accent_2` | 7.87:1 | AA |
-| `diagnostic` | 9.56:1 | AA |
-| `sage` | 8.14:1 | AA |
-| `error` | 8.06:1 | AA |
-| `warning` | 8.84:1 | AA |
-
-### Night (render profile derived from Dreamcoder Dark) APCA
-
-| Token | Lc vs bg | Target |
-| --- | ---: | --- |
-| `text` | 72.1 | ≥75 (FAIL) |
-| `muted` | 56.8 | ≥75 (FAIL) |
-| `comment` | 61.2 | ≥75 (FAIL) |
-| `accent` | 60.9 | ≥75 (FAIL) |
-| `accent_2` | 50.1 | ≥75 (FAIL) |
-| `diagnostic` | 59.1 | ≥75 (FAIL) |
-| `sage` | 51.8 | ≥75 (FAIL) |
-| `error` | 51.4 | ≥75 (FAIL) |
-| `warning` | 55.4 | ≥75 (FAIL) |
-| `border_ui` | 31.5 | ≥60 (FAIL) |
-| `focus` | 31.3 | ≥60 (FAIL) |
-
-### Night (render profile derived from Dreamcoder Dark) UI affordance contrast
-
-| Token | Ratio vs bg | Target |
-| --- | ---: | --- |
-| `border_ui` | 4.82:1 | PASS |
-| `border_hi` | 7.01:1 | PASS |
-| `focus` | 4.74:1 | PASS |
-
## Usage
```bash
diff --git a/docs/herdr.md b/docs/herdr.md
index 92ca36fd..cf3f431a 100644
--- a/docs/herdr.md
+++ b/docs/herdr.md
@@ -87,16 +87,12 @@ drift is detectable:
DreamcoderHerdr/.config/herdr/dreamcoder/
0.7.3/config.dark.toml
0.7.3/config.light.toml
- 0.7.3/config.night.toml
0.8.0/config.dark.toml
0.8.0/config.light.toml
- 0.8.0/config.night.toml
0.8.2/config.dark.toml
0.8.2/config.light.toml
- 0.8.2/config.night.toml
0.9.1/config.dark.toml
0.9.1/config.light.toml
- 0.9.1/config.night.toml
```
- Each variant carries the header `# Managed by Dreamcoder; repository variant only.`
@@ -114,7 +110,7 @@ DreamcoderHerdr/.config/herdr/dreamcoder/
`DreamcoderProfiles/deploy/` holds repository-safe deployment profiles:
- `desktop-arch.json` — desktop Arch Linux deployment; supports the full
- day/night cycle via systemd timers. Herdr `[ui] pane_scrollbars` follows the
+ light/dark schedule via systemd timers. Herdr `[ui] pane_scrollbars` follows the
Herdr default (`false`).
- `mobile-termux.json` — mobile Termux/Moshi deployment; selects Dreamcoder
Light and disables Herdr pane scrollbars for narrow screens. It contains
@@ -140,7 +136,7 @@ safely.
## Live switching
`scripts/herdr-theme-switch.sh` detects the installed Herdr version, selects
-the matching generated dark, light, or night variant, and requests a live config
+the matching generated dark or light variant, and requests a live config
reload. It resolves the selector from `HERDR_CONFIG_PATH` first, then
`XDG_CONFIG_HOME`, then `~/.config/herdr/config.toml`.
diff --git a/docs/installation/linux.md b/docs/installation/linux.md
index 43181723..0dfaa726 100644
--- a/docs/installation/linux.md
+++ b/docs/installation/linux.md
@@ -98,7 +98,7 @@ dreamcoder light # → Cocoa/Lúcuma
dreamcoder status # → System overview
# Automatic (via systemd timer)
-# Switches at 07:00 (light), 16:00 (night), 18:00 (dark)
+# Switches at 07:00 (light) and 18:00 (dark)
systemctl --user status dreamcoder-theme-auto.timer
```
diff --git a/docs/installation/termux.md b/docs/installation/termux.md
index 2cf0217a..74cfbded 100644
--- a/docs/installation/termux.md
+++ b/docs/installation/termux.md
@@ -38,7 +38,7 @@ dreamcoder-theme sync
## Limitations
-- No systemd (day/night automation unavailable)
+- No systemd (light/dark automation unavailable)
- No GUI apps (Kitty/Ghostty unavailable)
- Limited shader support
diff --git a/docs/migration/from-gentleman.md b/docs/migration/from-gentleman.md
index 7b3b7a02..944bd5e5 100644
--- a/docs/migration/from-gentleman.md
+++ b/docs/migration/from-gentleman.md
@@ -67,5 +67,5 @@ The installer writes a backup manifest under `~/.local/share/dreamcoder/backups/
| Theme | Hardcoded | Token-based |
| AI layer | gentle-ai | gentle-ai + agent themes (see [AI Integration](../ai-integration.md)) |
| Vim trainer | ✓ | ✓ (improved) |
-| Day/Night | — | ✓ Systemd timer |
+| Light/Dark schedule | — | ✓ Systemd timer |
| Health checks | — | ✓ WCAG/APCA |
diff --git a/docs/migration/from-ml4w.md b/docs/migration/from-ml4w.md
index 56e2fbb4..b4597500 100644
--- a/docs/migration/from-ml4w.md
+++ b/docs/migration/from-ml4w.md
@@ -59,5 +59,5 @@ The installer writes a backup manifest under `~/.local/share/dreamcoder/backups/
| Feature | ML4W | Dreamcoder Workbench |
| --- | --- | --- |
| Theme source | Wallpaper (Material You) | tokens.json |
-| Day/Night | Manual | Systemd timer |
+| Light/Dark schedule | Manual | Systemd timer |
| Health checks | None | WCAG/APCA |
diff --git a/odd/tasks/remove-night-profile.md b/odd/tasks/remove-night-profile.md
new file mode 100644
index 00000000..974b1b90
--- /dev/null
+++ b/odd/tasks/remove-night-profile.md
@@ -0,0 +1,113 @@
+# Remove the Night render profile
+
+## Objective
+
+The Dreamcoder design system has exactly two modes: Dreamcoder Light and
+Dreamcoder Dark. Remove the Night render profile (derived from Dark) end to end.
+
+## Problem
+
+Night survives as an opt-in profile (`dreamcoder night`, `theme.render_profile`,
+`night_palette()`, 33 generated `*-night*` artifacts, Herdr night variants, docs).
+The scheduler never activates it, and the 16:00 timer trigger only re-applies Light.
+
+## Why
+
+User decision (2026-09-28): "solo debe tener dreamcoder light y dreamcoder dark".
+
+## Scope
+
+- In: runtime code (palette, settings, settings_store, sync, cli_handlers,
+ cli_parser, herdr_switch, renderer_contract, targets, renderers, writers, doctor,
+ repair_engine), scripts (apply-theme-mode.sh, dreamcoder dispatcher, theme-auto.sh,
+ verify-theme-health.py, generate-theme-preview.py), data files (tokens.json,
+ tokens.schema.json, design-system.json, targets.json, targets.schema.json),
+ generated `*-night*` artifacts, tests, live docs, systemd timer (drop 16:00),
+ live-machine cleanup (settings key, dangling night symlinks).
+- Out: the legacy Dusk token set (still used; separate decision); archived openspec,
+ CHANGELOG history and docs/superpowers history are not rewritten.
+
+## Constraints
+
+- Light and Dark output must stay byte-identical except where Night logic lived.
+- Keep the Light/Dark contrast gate (`validate_palette`) intact.
+- `targets.json`, `targets.schema.json` and `renderer_contract` change in the same
+ commit (`targets.py` requires modes to match `ALL_RENDER_VARIANTS`).
+- Unrelated working-tree edits (timer-rewritten theme files, user's fish prompt,
+ warp, apply-system-mode.sh) are never staged.
+- TDD: off (no project/session TDD configuration). Runners: `python -m pytest tests/`,
+ `bats tests/shell/ tests/ml4w/`.
+- Delivery: work-unit commits on `refactor/remove-night-profile` (stacked on
+ `chore/ml4w-2.16-gentleman-sync`); forecast ~1,200–1,500 authored lines, mostly
+ deletions. Push/PR slicing is the user's decision.
+
+## Tasks
+
+- [x] N1 — Core model: remove Night from palette/settings/settings_store, renderer
+ contract, targets manifest + schema, tokens + schema, design-system mode_policy,
+ every renderer's `modes` and Night name-sniffing; update affected tests. Route:
+ delegated writer. Scope widened to keep the commit green: `sync.py` and the CLI
+ activation (`cli_handlers`, `cli_parser`, `herdr_switch`) plus the Python scripts
+ that imported `night_palette` (`verify-theme-health.py`, `generate-theme-preview.py`)
+ moved here from N2 because they import the removed palette/settings API. COVERAGE
+ stays as the 33-consumer declaration keyed by the Dark artifact; Codex app and
+ Antigravity coverage now render exactly what `sync_repo_snippets()` writes (active
+ palette / Dark-pinned). A persisted legacy `theme.render_profile` is ignored as an
+ unknown setting (warning, preserved). Repo-only regeneration: every Light/Dark
+ artifact byte-identical; only the generated README, nvim dispatcher and preview
+ docs lost their Night lines. Commit: 319ffdd.
+- [x] N2 — Scripts + shell tests: `apply-theme-mode.sh` drops the profile argument,
+ the Night artifact gate, the kanagawa Night case and `DREAMCODER_THEME_PROFILE`
+ (cursor-cli.env no longer writes it; tmux unsets the stale variable); the
+ `dreamcoder` dispatcher rejects `night` as an unknown command; `theme-auto.sh`
+ stops passing `standard`; `pi-theme.sh` selects by mode only. Bats: Night tests
+ (including the failing kanagawa one) deleted and replaced with Light/Dark and
+ `night`-rejection coverage. Route: delegated writer. Python parts of the original
+ N2 landed in N1 (see above). Commit: 72c173b.
+- [x] N3 — Artifacts: deleted the 33 tracked `*-night*` generated artifacts (Herdr
+ `config.night.toml` for 0.7.3/0.8.0/0.8.2/0.9.1 included) and the CI workflow's
+ Kitty night symlink. Repo-only regeneration afterwards reports 0 changes, so sync
+ no longer recreates them. Light/Dark byte-identity vs the pre-N1 baseline: every
+ tracked `*dark*`/`*light*` artifact unchanged. The characterization fixtures
+ (`tests/fixtures/legacy_*.json`) were intentionally NOT regenerated: they are frozen
+ Phase-0 evidence of the legacy 32-consumer sync (no test reads them), a rerun would
+ replace the legacy hashes with today's palette and bake machine-specific live
+ paths; the harness itself was updated in N1 so it still runs. Route: delegated
+ writer. Commit: e9b75fd.
+- [x] N4 — Docs + timer: live docs (CLAUDE.md, README, INSTALL, AGENTS, CONTRIBUTING,
+ COMPARISON, design system, theme-system/ml4w/editor/terminal/termux, migration,
+ herdr + Herdr evidence, palette-token skill), live openspec specs (eye-comfort
+ rewritten around Light/Dark + legacy-key tolerance, ml4w-keybindings-waybar,
+ renderer-registry), `desktop-arch.json` wording, repo `settings.json` drops
+ `theme.render_profile`, and the timer drops the 16:00 trigger ("light/dark"
+ wording; schedule is 07:00 light, 18:00 dark). Not touched: archived and
+ in-flight `openspec/changes/*`, CHANGELOG, docs/superpowers, the Dusk
+ `light_hours_default`/`dusk_hours_default` token metadata. Route: delegated
+ writer. Commit: ad41d7b.
+- [x] N5 — Live cleanup (2026-09-29): live `settings.json` already had no
+ `theme.render_profile`; the timer is a repo symlink and already fires only 07:00 and
+ 18:00; removed the two dangling links `~/.config/starship-night.toml` and Warp
+ `Dreamcoder-Night.yaml`; `systemctl --user daemon-reload`; `dreamcoder sync`;
+ real `dreamcoder dark` → `dreamcoder light` toggle leaves Starship, Hyprland and
+ Waybar in Light; `verify-ml4w-setup.sh` 20 passed / 0 failed; `dreamcoder doctor`
+ guardrails passed; `dreamcoder night` is rejected as an unknown command. Route: inline.
+
+## Acceptance criteria
+
+- `git ls-files | grep -i night` returns nothing outside history docs.
+- `dreamcoder night` is an unknown command; `dreamcoder light|dark` work.
+- pytest and bats green (the kanagawa Night bats test is removed with Night).
+- `verify-ml4w-setup.sh` and `dreamcoder doctor` pass after N5.
+
+## Progress
+
+- Verification after N4: `python -m pytest tests/ -q` exit 0; `bats tests/shell/
+ tests/ml4w/` 143/143 ok; `verify-theme-health.py` and `verify-repo-sync.py` OK;
+ shellcheck/bash -n clean on modified scripts; `git ls-files | grep -i night`
+ lists only this feature document.
+- Branch created from `chore/ml4w-2.16-gentleman-sync`; shell readability fixes
+ (c9cf723, 32f9f6f, ffc5359, 754d352) landed here before the Night work started.
+
+## Next step
+
+Feature complete. Pending user decisions: push / PR slicing of the two stacked branches.
diff --git a/openspec/specs/eye-comfort/spec.md b/openspec/specs/eye-comfort/spec.md
index f129c013..d88a9591 100644
--- a/openspec/specs/eye-comfort/spec.md
+++ b/openspec/specs/eye-comfort/spec.md
@@ -2,7 +2,7 @@
## Purpose
-Define the Night/Dim eye-comfort rendering profile for Dreamcoder OS: one canonical APCA contrast implementation, independent blocking WCAG 2.2 + APCA gates, a deterministic brightness/saturation-reduced derivation of the dark Anthracite Steel palette, persistent `theme.render_profile` settings with `dreamcoder night` CLI activation, all-32-active-target coverage, and fail-closed validation before any write — without medical claims, automatic scheduling, palette redesign, or renderer-interface changes.
+Define the eye-comfort contract for Dreamcoder OS: one canonical APCA contrast implementation, independent blocking WCAG 2.2 + APCA gates, exactly two user-facing modes (Dreamcoder Light and Dreamcoder Dark) activated through the Dreamcoder CLI, declared coverage of every active color consumer, and fail-closed validation before any write — without medical claims, palette redesign, or renderer-interface changes.
## Requirements
@@ -52,141 +52,73 @@ All enforced text and affordance pairs MUST satisfy BOTH the WCAG 2.2 minimum co
#### Scenario: Class floors apply per content and mode
-- GIVEN heading (Lc 60 light / Lc 45 dark), quiet (Lc 44), UI (Lc 60 light / Lc 28 dark), and on-accent (Lc 60) pairs across Light, Dark, and Night
+- GIVEN heading (Lc 60 light / Lc 45 dark), quiet (Lc 44), UI (Lc 60 light / Lc 28 dark), and on-accent (Lc 60) pairs across Light, Dark, and Dusk
- WHEN health validation runs
- THEN each pair MUST be measured against its declared class and mode floor, and any below-floor pair MUST block
-### Requirement: Deterministic Night/Dim palette transformation
-
-The system MUST derive the Night/Dim palette in `src/dreamcoder_theme/palette.py` by applying a brightness- and saturation-reduced transform to the dark Anthracite Steel palette, after `adaptive_palette()` and before any renderer runs. The transform MUST use explicit, bounded profile parameters represented in the canonical token contract or its schema, and MUST NOT hand-tune colors inside individual renderers. The transform MUST preserve token keys, semantic relationships, pure-black/white avoidance, alpha syntax, and the `dict[str, str]` renderer input shape. For identical canonical tokens, wallpaper/adaptive input, and profile settings, the transform MUST produce byte-identical output.
-
-#### Scenario: Night derives from dark Anthracite Steel
-
-- GIVEN the standard dark Anthracite Steel palette
-- WHEN the Night/Dim transform is applied after `adaptive_palette()` and before any renderer runs
-- THEN every output token is a brightness/saturation-reduced derivation of the corresponding dark token with the same semantic role, and no individual renderer adjusts colors
-
-#### Scenario: Identical inputs produce identical output
-
-- GIVEN identical canonical tokens, adaptive input, and profile settings
-- WHEN the Night transform runs twice
-- THEN both runs MUST produce byte-identical palettes
-
-#### Scenario: Keys, alpha syntax, and input shape are preserved
-
-- GIVEN a transformed Night palette
-- WHEN it is passed to renderers
-- THEN it contains exactly the same token keys and alpha syntax as the standard palette and consumes the same `dict[str, str]` input shape
-
### Requirement: Pre-write validation and fail-closed write behavior
-The final palette — including the transformed Night palette — MUST be validated by `validate_palette()` before the first writer runs, and validation MUST finish before `sync_active_targets()` and `sync_repo_snippets()` perform any write. If dimming causes any WCAG or APCA pair to miss its floor, generation MUST stop before writes, the command MUST exit non-zero, and no partial cross-target profile MAY be left applied. The transform MAY make a narrowly bounded corrective adjustment to restore a floor, but MUST NOT weaken a threshold and MUST NOT silently fall back to the standard dark palette. `write_if_changed()` semantics MUST be preserved.
+The final Light or Dark palette MUST be validated by `validate_palette()` before the first writer runs, and validation MUST finish before `sync_active_targets()` and `sync_repo_snippets()` perform any write. If any WCAG or APCA pair misses its floor, generation MUST stop before writes, the command MUST exit non-zero, and no partial cross-target state MAY be left applied. `write_if_changed()` semantics MUST be preserved.
#### Scenario: A failed gate blocks all writes
-- GIVEN a Night palette whose transformed output misses one APCA floor
-- WHEN the validated sync runs
-- THEN no target or active output is written or selected, the command exits non-zero, and the prior standard profile remains active
-
-#### Scenario: Bounded corrective adjustment restores a floor
-
-- GIVEN dimming pushes one pair below its declared floor
-- WHEN the transform applies a narrowly bounded corrective adjustment
-- THEN the adjustment restores the floor without weakening any threshold, and the final palette passes validation before any write
-
-#### Scenario: No silent standard-dark fallback
-
-- GIVEN the transform cannot meet a floor after bounded correction
-- WHEN the Night sync runs
-- THEN it MUST fail closed with a diagnostic and MUST NOT emit standard dark output while reporting Night
-
-### Requirement: All-active-target Night coverage
-
-Night MUST apply to every consumer output in the union of `sync_active_targets()` and `sync_repo_snippets()` — the 32 active color consumers — and MUST NOT silently substitute standard dark for any of them. Each active consumer MUST have a deterministic Night artifact or a Night-selected active output where the target format supports named variants. Silent omission, standard-dark substitution, and partial success MUST be forbidden: no target MAY receive standard dark while the command reports Night. `targets.json` and its schema MUST be updated only as required to represent Night render coverage for the existing active color targets; `dusk-runtime` MUST remain excluded, and no new application targets beyond the 32 active consumers MAY be added.
-
-#### Scenario: All 32 active targets receive Night output
-
-- GIVEN the Night profile is active
+- GIVEN a palette that misses one APCA floor
- WHEN the validated sync runs
-- THEN every one of the 32 consumers in the union of `sync_active_targets()` and `sync_repo_snippets()` receives Night output or a Night-selected active variant, and the coverage report lists each target
-
-#### Scenario: Standard-dark substitution is forbidden
-
-- GIVEN a consumer whose format supports named variants
-- WHEN Night sync runs
-- THEN that consumer MUST NOT be written with or left selecting standard dark while the command reports Night, and any such substitution MUST fail the command non-zero
-
-#### Scenario: Partial success fails closed
-
-- GIVEN Night generation succeeds for 31 consumers but fails for one
-- WHEN the sync completes
-- THEN the command exits non-zero and identifies the failed target, and MUST NOT report a silently partial profile as success
+- THEN no target or active output is written or selected, the command exits non-zero, and the prior mode remains active
-### Requirement: Persistent render profile setting
+### Requirement: Declared coverage of every active color consumer
-The system MUST add a typed setting `theme.render_profile` to `SETTINGS_SCHEMA` in `src/dreamcoder_theme/settings_store.py` with the closed values `standard` and `night`, and MUST preserve unknown settings for forward compatibility. A profile resolver in `src/dreamcoder_theme/settings.py` MUST read the persisted setting for sync, with an explicit environment override for isolated generation and tests. `theme_mode()` MUST remain responsible for the Light/Dark base and MUST NOT reinterpret Night as Dusk.
+Every consumer output in the union of `sync_active_targets()` and `sync_repo_snippets()` MUST be declared in the sync coverage table and rendered in memory during preparation; an undeclared or unrendered consumer MUST fail the command before any write. `targets.json` render modes MUST be exactly `dark` and `light`; `dusk-runtime` MUST remain excluded.
-#### Scenario: Persisted profile is resolved
+#### Scenario: An undeclared consumer blocks preparation
-- GIVEN `theme.render_profile=night` is persisted
-- WHEN sync resolves the rendering profile
-- THEN the resolver returns `night` absent an environment override, and sync uses the Night transform with the dark base
+- GIVEN a consumer written by sync but missing from the coverage declaration
+- WHEN preparation runs
+- THEN the command fails closed before any write and names the consumer
-#### Scenario: Environment override wins without mutation
+### Requirement: Dreamcoder CLI Light/Dark activation
-- GIVEN `theme.render_profile=night` is persisted
-- WHEN an explicit environment override for the profile is set for an invocation
-- THEN the resolver MUST return the override value for that invocation and MUST NOT mutate the persisted setting
+`dreamcoder light` and `dreamcoder dark` MUST persist `terminal.default_mode`, run the validated sync, and return non-zero without changing active outputs when validation fails. `night` MUST be rejected as an unknown command, and `dreamcoder theme apply` MUST accept only `light` and `dark`. A settings file that still carries the retired `theme.render_profile` key MUST load without error: the key is reported as an unknown setting, preserved, and never read. `scripts/theme-auto.sh` MUST keep the Light/Dark schedule.
-#### Scenario: Invalid profile value is rejected
+#### Scenario: Light or Dark activation succeeds
-- GIVEN `theme.render_profile` is set to a value other than `standard` or `night`
-- WHEN settings validation runs
-- THEN the value MUST be rejected or defaulted to `standard`, and MUST NOT be interpreted as a runtime profile
-
-### Requirement: Dreamcoder CLI activation and profile exit
-
-`dreamcoder night` MUST persist the Night profile, select the dark Anthracite Steel base, run the validated sync, and return non-zero without changing active outputs when validation fails. `dreamcoder light` and `dreamcoder dark` MUST remain backward compatible and MUST each explicitly persist `theme.render_profile=standard` before applying their base mode, exiting Night and regenerating the standard identity across the same target inventory. `dreamcoder settings get/set theme.render_profile` MUST remain available through the existing generic settings interface. The system MUST NOT add automatic time-based activation; `scripts/theme-auto.sh` MUST keep its current Light/Dark schedule.
-
-#### Scenario: Night activation succeeds
-
-- GIVEN the standard profile is active
-- WHEN the user runs `dreamcoder night`
-- THEN the Night profile is persisted, the dark base is selected, the Night transform is validated, and all 32 active targets receive Night output
+- GIVEN either mode is active
+- WHEN the user runs `dreamcoder light` or `dreamcoder dark`
+- THEN the mode is persisted, the palette is validated, and every declared consumer receives that mode's output
-#### Scenario: Night with a failing gate changes nothing
+#### Scenario: A failing gate changes nothing
-- GIVEN a transformed Night palette that fails a floor
-- WHEN the user runs `dreamcoder night`
-- THEN the command exits non-zero, no active output is changed, and the prior standard setting remains in effect
+- GIVEN a palette that fails a floor
+- WHEN the user runs `dreamcoder light` or `dreamcoder dark`
+- THEN the command exits non-zero, no active output is changed, and the prior setting remains in effect
-#### Scenario: Light and Dark exit Night
+#### Scenario: Night is not a mode
-- GIVEN `theme.render_profile=night` is active
-- WHEN the user runs `dreamcoder light` or `dreamcoder dark`
-- THEN `theme.render_profile=standard` is persisted before the base mode is applied, the standard identity is regenerated across the same target inventory, and subsequent sync uses the standard profile
+- GIVEN the Night render profile was removed
+- WHEN the user runs `dreamcoder night` or `dreamcoder theme apply night`
+- THEN the command is rejected as unknown, exits non-zero, and changes nothing
-#### Scenario: No automatic time-based activation
+#### Scenario: Legacy render profile setting is tolerated
-- GIVEN the Night profile is not persisted
-- WHEN the theme scheduler runs at any time of day
-- THEN it MUST NOT activate Night automatically and MUST keep the existing Light/Dark schedule
+- GIVEN a persisted `theme.render_profile` from an older version
+- WHEN settings are loaded, validated, or updated
+- THEN loading succeeds with an unknown-setting warning and the key is preserved without affecting the mode
### Requirement: Blocking health verification
-`scripts/verify-theme-health.py` MUST import canonical `contrast()` and `apca_lc()` from the package and MUST remove the `check_apca_or_warn()` advisory path for declared guardrail pairs. Every below-floor pair MUST terminate the command non-zero and report mode/profile, token or state pair, measured WCAG/APCA value, and required threshold. The command MUST validate standard Light, standard Dark, design-system Dusk, and derived Night deterministically, and MUST check Night before any generated artifact is accepted. It MUST declare generation/selection coverage for all 32 active consumers, and any consumer without declared coverage MUST block. `scripts/generate-theme-preview.py` MUST use the same canonical math and include Night measurements without creating screenshot baselines.
+`scripts/verify-theme-health.py` MUST import canonical `contrast()` and `apca_lc()` from the package and MUST remove the `check_apca_or_warn()` advisory path for declared guardrail pairs. Every below-floor pair MUST terminate the command non-zero and report mode, token or state pair, measured WCAG/APCA value, and required threshold. The command MUST validate Light, Dark, and design-system Dusk deterministically. It MUST declare generation/selection coverage for every active consumer, and any consumer without declared coverage MUST block. `scripts/generate-theme-preview.py` MUST use the same canonical math without creating screenshot baselines.
#### Scenario: Below-floor pair blocks with actionable diagnostics
-- GIVEN a quiet-text pair measuring below Lc 44 in Night
+- GIVEN a quiet-text pair measuring below Lc 44 in Dark
- WHEN `verify-theme-health.py` runs
-- THEN the command exits non-zero and reports profile Night, the pair, the measured Lc value, and the required threshold
+- THEN the command exits non-zero and reports mode dark, the pair, the measured Lc value, and the required threshold
-#### Scenario: All four profiles are validated
+#### Scenario: All canonical palettes are validated
-- GIVEN standard Light, standard Dark, design-system Dusk, and derived Night palettes
+- GIVEN the Light, Dark, and design-system Dusk palettes
- WHEN health validation runs
-- THEN each profile is measured deterministically, and any below-floor pair in any profile blocks the command
+- THEN each palette is measured deterministically, and any below-floor pair in any palette blocks the command
#### Scenario: Advisory warnings become blocking
@@ -196,13 +128,13 @@ The system MUST add a typed setting `theme.render_profile` to `SETTINGS_SCHEMA`
#### Scenario: All-target coverage is declared
-- GIVEN the Night profile
+- GIVEN the sync coverage declaration
- WHEN health validation runs
-- THEN the report declares generation/selection coverage for all 32 active consumers, and any consumer without declared coverage blocks the command
+- THEN it declares generation/selection coverage for every active consumer, and any consumer without declared coverage blocks the command
### Requirement: Focused regression coverage for the eye-comfort contract
-The automated test suite MUST cover APCA known vectors, threshold boundaries, polarity, Night determinism, transform bounds, failed-transform no-write behavior, `theme.render_profile` setting validation, CLI activation, and all-target coverage. Advisory assertions and comments in `tests/test_dreamcoder_global_design_system.py` MUST be replaced with blocking checks. `tests/test_apca_implementation.py` MUST cross-validate the package `apca_lc()` against known vectors. Local and CI behavior MUST align around `python scripts/verify-theme-health.py` and the existing pytest suite.
+The automated test suite MUST cover APCA known vectors, threshold boundaries, polarity, failed-gate no-write behavior, legacy `theme.render_profile` tolerance, Light/Dark CLI activation, and all-target coverage. Advisory assertions and comments in `tests/test_dreamcoder_global_design_system.py` MUST be replaced with blocking checks. `tests/test_apca_implementation.py` MUST cross-validate the package `apca_lc()` against known vectors. Local and CI behavior MUST align around `python scripts/verify-theme-health.py` and the existing pytest suite.
#### Scenario: Known vectors cross-validate the package implementation
@@ -216,21 +148,21 @@ The automated test suite MUST cover APCA known vectors, threshold boundaries, po
- WHEN the focused tests run
- THEN at-floor pairs pass and below-floor pairs fail with the correct metric and polarity-aware measurement
-#### Scenario: Failed transform performs no writes
+#### Scenario: Failed gate performs no writes
-- GIVEN a test that forces the Night transform below a floor
+- GIVEN a test that forces the dual gate to fail
- WHEN the sync path runs under test
- THEN no writes occur and the failure is asserted
### Requirement: Evidence and claims boundaries
-Night MUST be documented as a user-controlled display profile, not a medical treatment. The system MUST NOT make blue-light-treatment, disease-prevention, eye-strain-cure, or sleep-improvement claims, MUST NOT add automatic warmth/color-temperature filtering, and MUST NOT replace the existing Hyprland 4000K keybindings, which remain a separate external display filter. `docs/DREAMCODER_DESIGN_SYSTEM.md` and generated preview policy MUST document WCAG 2.2 and APCA as independent blocking gates, and previously documented APCA exceptions MUST be corrected or explicitly removed rather than remaining accepted warnings.
+The Light/Dark modes MUST be documented as display modes, not a medical treatment. The system MUST NOT make blue-light-treatment, disease-prevention, eye-strain-cure, or sleep-improvement claims, MUST NOT add automatic warmth/color-temperature filtering, and MUST NOT replace the existing Hyprland 4000K keybindings, which remain a separate external display filter. `docs/DREAMCODER_DESIGN_SYSTEM.md` and generated preview policy MUST document WCAG 2.2 and APCA as independent blocking gates, and previously documented APCA exceptions MUST be corrected or explicitly removed rather than remaining accepted warnings.
-#### Scenario: Night is documented without treatment claims
+#### Scenario: Modes are documented without treatment claims
-- GIVEN the Night profile and its documentation
+- GIVEN the Light/Dark documentation
- WHEN the documentation and generated preview policy are inspected
-- THEN Night is described as a validated luminance/chroma display profile with no medical-treatment, blue-light-filtering, or sleep claims, and no automatic warmth is introduced
+- THEN the modes are described without medical-treatment, blue-light-filtering, or sleep claims, and no automatic warmth is introduced
#### Scenario: Advisory APCA exceptions are removed
diff --git a/openspec/specs/ml4w-keybindings-waybar/spec.md b/openspec/specs/ml4w-keybindings-waybar/spec.md
index 95419cc5..f7924400 100644
--- a/openspec/specs/ml4w-keybindings-waybar/spec.md
+++ b/openspec/specs/ml4w-keybindings-waybar/spec.md
@@ -17,7 +17,7 @@ in this change makes Waybar run with it. The active-workspace accent highlight
is also not reachable in the shipped chain — `style.css` imports only
`colors.css`, which declares variables and no accent rule, while the
`#workspaces button.active` accent rule lives in the engine-generated
-`DreamcoderThemes/dreamcoder/waybar-{dark,light,night}.css`, which `style.css`
+`DreamcoderThemes/dreamcoder/waybar-{dark,light}.css`, which `style.css`
does not import. The delivered Waybar behavior is therefore the taskbar module
plus `window-rewrite` icon mappings only. Both limitations, plus two further
ones, are recorded non-normatively in `## Known Gaps` and MUST NOT be read as
@@ -218,7 +218,7 @@ modules MUST carry its own configuration block.
`DreamcoderWaybar/.config/waybar/style.css` MUST import exactly one stylesheet,
`colors.css`, which is the Dreamcoder color bridge that selects the active
-light/dark/night variable set written by the theme sync. The shipped import is
+light/dark variable set written by the theme sync. The shipped import is
`colors.css`; the `waybar-light.css` / `waybar-dark.css` files named by an
earlier revision of this requirement were reconciled to the shipped import on
2026-09-11. The layout stylesheet MUST NOT define colors itself (no
@@ -300,7 +300,7 @@ the layout config (see FR2.4).
`scripts/apply-theme-mode.sh`
- WHEN the changed paths and the Waybar section of that script are inspected
- THEN no file under the theme engine package is present, and
- `DreamcoderThemes/dreamcoder/waybar-{dark,light,night}.css` remain
+ `DreamcoderThemes/dreamcoder/waybar-{dark,light}.css` remain
engine-generated and unedited
- AND the mode switch still repoints `~/.config/waybar/colors.css` to the active
mode variant before the sync and restarts Waybar
@@ -375,7 +375,7 @@ satisfied requirements.
`DreamcoderWaybar/.config/waybar/style.css` imports only `colors.css`, which
declares variables and no accent rule; the `#workspaces button.active` accent
rule lives in the engine-generated
-`DreamcoderThemes/dreamcoder/waybar-{dark,light,night}.css`, which `style.css`
+`DreamcoderThemes/dreamcoder/waybar-{dark,light}.css`, which `style.css`
does not import. Delivered behavior is the module plus `window-rewrite` only.
Follow-up: wire the mode CSS import so the accent rule reaches the live chain.
diff --git a/openspec/specs/renderer-registry/spec.md b/openspec/specs/renderer-registry/spec.md
index 75f89081..a4aa962e 100644
--- a/openspec/specs/renderer-registry/spec.md
+++ b/openspec/specs/renderer-registry/spec.md
@@ -94,7 +94,7 @@ registration would falsely assert that no active output exists.
The `zellij` registration's `RepositoryStrategy.MODE_VARIANTS` claim MUST be
resolved against one design-recorded decision: either (a) correct the claim
-to match that only Night is generated today (orphaned `.kdl` status
+to match the variants actually generated (orphaned `.kdl` status
documented as a follow-up, never silently deleted), or (b) wire real
dark/light `.kdl` generation so the existing claim becomes true.
@@ -115,7 +115,7 @@ dark/light `.kdl` generation so the existing claim becomes true.
### Requirement: herdr registration represents all 3 supported profiles
The `herdr` registration(s) MUST represent that
-`sync_herdr_repo_variants()` writes dark/light(+night) variants for every
+`sync_herdr_repo_variants()` writes dark/light variants for every
`is_complete` profile in `SUPPORTED_PROFILES` (`herdr-0.7.3`, `-0.8.0`,
`-0.8.2`), each under its own versioned directory. A single hardcoded
profile/mode entry MUST NOT stand in for this fan-out.
diff --git a/scripts/apply-theme-mode.sh b/scripts/apply-theme-mode.sh
index 687e18e3..74ee196a 100755
--- a/scripts/apply-theme-mode.sh
+++ b/scripts/apply-theme-mode.sh
@@ -16,30 +16,11 @@ ML4W_CACHE_DIR="${ML4W_CACHE_DIR:-${CACHE_HOME}/ml4w/hyprland-dotfiles}"
MODE="${1:-light}"
WALLPAPER="${2:-${DREAMCODER_WALLPAPER:-}}"
-PROFILE="${3:-${DREAMCODER_THEME_PROFILE:-standard}}"
ML4W_WALLPAPER="${ML4W_CACHE_DIR}/current_wallpaper"
[[ "${MODE}" == "light" || "${MODE}" == "dark" ]] || {
printf 'Invalid mode: %s\n' "${MODE}" >&2
exit 1
}
-case "${PROFILE}" in
-standard | night) ;;
-*)
- printf 'Invalid render profile: %s (expected standard|night)\n' "${PROFILE}" >&2
- exit 1
- ;;
-esac
-# Night is an orthogonal render profile on top of the base mode: while
-# DREAMCODER_THEME_MODE stays "dark", DREAMCODER_THEME_PROFILE=night selects
-# the generated *-night artifacts instead of the standard dark ones.
-VARIANT="${MODE}"
-[[ "${PROFILE}" == "night" ]] && VARIANT="night"
-# Night always resolves the Dreamcoder Dark base (ADR-003): a light
-# base with profile=night is a conflict, never a silent coercion.
-if [[ "${PROFILE}" == "night" && "${MODE}" != "dark" ]]; then
- printf 'Invalid combination: render profile night requires base mode dark (got %s)\n' "${MODE}" >&2
- exit 1
-fi
if [[ -z "${WALLPAPER}" && -f "${ML4W_WALLPAPER}" ]]; then WALLPAPER="$(cat "${ML4W_WALLPAPER}")"; fi
CURSOR_CLI_ENV="${CACHE_HOME:-${HOME}/.cache}/dreamcoder/cursor-cli.env"
@@ -48,37 +29,6 @@ light) CLI_COLORFGBG="0;15" ;;
dark) CLI_COLORFGBG="15;0" ;;
esac
mkdir -p "$(dirname "${CURSOR_CLI_ENV}")"
-# Preparation gate: never mutate symlinks or system mode until the Night
-# plan is ready. For profile=night every repo-generated *-night artifact
-# referenced by the selectors below must already exist (Phase 3 generation
-# plus the Python validation-first gate are the other halves); a missing
-# artifact aborts with ZERO mutations.
-if [[ "${PROFILE}" == "night" ]]; then
- REQUIRED_NIGHT_ARTIFACTS=(
- "${DREAMCODER_DOTS_DIR}/DreamcoderKitty/.config/kitty/colors-dreamcoder-night.conf"
- "${DREAMCODER_DOTS_DIR}/DreamcoderKitty/.config/kitty/dreamcoder-ui-night.conf"
- "${DREAMCODER_DOTS_DIR}/DreamcoderGhostty/.config/ghostty/themes/dreamcoder-night"
- "${DREAMCODER_DOTS_DIR}/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Night.yaml"
- "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json"
- "${DREAMCODER_DOTS_DIR}/DreamcoderZellij/.config/zellij/dreamcoder-night.kdl"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/hyprland-night.conf"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/hypr-colors-night.lua"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/hypr-colors-night.conf"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/waybar-night.css"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/rofi-night.rasi"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/btop-dreamcoder-night.theme"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/delta-dreamcoder-night.gitconfig"
- "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/tmux-dreamcoder-night.conf"
- "${DREAMCODER_DOTS_DIR}/DreamcoderLazygit/.config/lazygit/config.night.yml"
- )
- for _artifact in "${REQUIRED_NIGHT_ARTIFACTS[@]}"; do
- if [[ ! -f "${_artifact}" ]]; then
- printf '✗ Night preparation failed: missing artifact %s\n' "${_artifact}" >&2
- printf ' Run repository Night generation first (DREAMCODER_THEME_PROFILE=night sync).\n' >&2
- exit 1
- fi
- done
-fi
# Bounded preparation + settings persistence (control path). Runs only
# when this script is the entry point (theme-auto / manual). When
# invoked by the control transaction (DREAMCODER_SYNC_DONE=1),
@@ -87,16 +37,13 @@ fi
# (design §7/§8): system mode, symlink flips, and reloads run only
# after preparation and settings persistence succeed.
if [[ "${DREAMCODER_SYNC_DONE:-0}" != "1" ]]; then
- _CONTROL_CHOICE="${MODE}"
- [[ "${PROFILE}" == "night" ]] && _CONTROL_CHOICE="night"
DREAMCODER_SYNC_DONE=1 \
DREAMCODER_THEME_MODE="${MODE}" \
- DREAMCODER_THEME_PROFILE="${PROFILE}" \
DREAMCODER_WALLPAPER="${WALLPAPER}" \
PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" \
- python3 -m dreamcoder_theme.control theme apply "${_CONTROL_CHOICE}" --json >/dev/null
+ python3 -m dreamcoder_theme.control theme apply "${MODE}" --json >/dev/null
fi
-printf 'export COLORFGBG="%s"\nexport DREAMCODER_THEME_MODE="%s"\nexport DREAMCODER_THEME_PROFILE="%s"\nexport COLORTERM="truecolor"\nexport FORCE_COLOR="3"\nexport CLICOLOR_FORCE="1"\nunset NO_COLOR\n' "${CLI_COLORFGBG}" "${MODE}" "${PROFILE}" >"${CURSOR_CLI_ENV}"
+printf 'export COLORFGBG="%s"\nexport DREAMCODER_THEME_MODE="%s"\nexport COLORTERM="truecolor"\nexport FORCE_COLOR="3"\nexport CLICOLOR_FORCE="1"\nunset NO_COLOR\n' "${CLI_COLORFGBG}" "${MODE}" >"${CURSOR_CLI_ENV}"
"${DREAMCODER_DOTS_DIR}/scripts/apply-system-mode.sh" "${MODE}"
if [[ -n "${WALLPAPER}" && -f "${WALLPAPER}" ]] && optional_command matugen; then
@@ -109,26 +56,26 @@ fi
# instead of overwriting the wrong one through a stale symlink.
WAYBAR_COLORS="${HOME}/.config/waybar/colors.css"
if [[ -L "${WAYBAR_COLORS}" ]]; then
- ln -sf "colors-${VARIANT}.css" "${WAYBAR_COLORS}"
+ ln -sf "colors-${MODE}.css" "${WAYBAR_COLORS}"
fi
# --- Rofi: same treatment for colors.rasi symlink ---
ROFI_COLORS="${HOME}/.config/rofi/colors.rasi"
if [[ -L "${ROFI_COLORS}" ]]; then
- ln -sf "colors-${VARIANT}.rasi" "${ROFI_COLORS}"
+ ln -sf "colors-${MODE}.rasi" "${ROFI_COLORS}"
fi
# --- Hyprland: flip colors.lua and colors.conf symlinks ---
HYPR_LUA="${HOME}/.config/hypr/colors.lua"
HYPR_CONF="${HOME}/.config/hypr/colors.conf"
if [[ -L "${HYPR_LUA}" ]]; then
- ln -sf "colors-${VARIANT}.lua" "${HYPR_LUA}"
+ ln -sf "colors-${MODE}.lua" "${HYPR_LUA}"
fi
if [[ -L "${HYPR_CONF}" ]]; then
- ln -sf "colors-${VARIANT}.conf" "${HYPR_CONF}"
+ ln -sf "colors-${MODE}.conf" "${HYPR_CONF}"
fi
# --- Hyprland: flip dreamcoder-colors.lua ---
HYPR_DC_LUA="${HOME}/.config/hypr/dreamcoder-colors.lua"
if [[ -L "${HYPR_DC_LUA}" ]]; then
- ln -sf "hypr-colors-${VARIANT}.lua" "${HYPR_DC_LUA}"
+ ln -sf "hypr-colors-${MODE}.lua" "${HYPR_DC_LUA}"
fi
# --- Kitty: flip colors-dreamcoder.conf and dreamcoder-ui.conf symlinks BEFORE sync ---
# This ensures the Python sync writes to the correct variant file
@@ -138,7 +85,7 @@ if [[ -d "${KITTY_DIR}" ]]; then
for _link in colors-dreamcoder.conf dreamcoder-ui.conf; do
_target="${KITTY_DIR}/${_link}"
if [[ -L "${_target}" ]]; then
- ln -sf "${_link%.conf}-${VARIANT}.conf" "${_target}"
+ ln -sf "${_link%.conf}-${MODE}.conf" "${_target}"
fi
done
fi
@@ -147,8 +94,8 @@ fi
# --- Pi CLI: flip theme symlink ---
PI_SCRIPT="${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh"
if [[ -f "${PI_SCRIPT}" ]]; then
- DREAMCODER_THEME_MODE="${MODE}" DREAMCODER_THEME_PROFILE="${PROFILE}" bash "${PI_SCRIPT}" &>/dev/null || true
- printf ' pi theme switched to %s mode (profile: %s)\n' "${MODE}" "${PROFILE}"
+ DREAMCODER_THEME_MODE="${MODE}" bash "${PI_SCRIPT}" &>/dev/null || true
+ printf ' pi theme switched to %s mode\n' "${MODE}"
fi
# The Python preparation + validation + settings persistence + commit ran
@@ -170,15 +117,15 @@ if command -v tmux >/dev/null 2>&1; then
fi
# Update global environment so NEW panes/windows inherit the right vars
tmux set-environment -g DREAMCODER_THEME_MODE "${MODE}" 2>/dev/null || true
- tmux set-environment -g DREAMCODER_THEME_PROFILE "${PROFILE}" 2>/dev/null || true
+ # Drop the retired render-profile variable from long-lived servers.
+ tmux set-environment -gu DREAMCODER_THEME_PROFILE 2>/dev/null || true
tmux set-environment -g COLORFGBG "${CLI_COLORFGBG}" 2>/dev/null || true
- # Switch tmux-kanagawa theme variant to match Dreamcoder mode/profile
+ # Switch tmux-kanagawa theme variant to match the Dreamcoder mode
# SOURCE OF TRUTH: colors MUST match DreamcoderThemes/dreamcoder/tokens.json
- # When updating tokens, update BOTH light AND dark sections here (and
- # night, derived through the canonical night transform in the package).
+ # When updating tokens, update BOTH light AND dark sections here.
if [[ -d "${KANAGAWA_DIR}" ]]; then
- case "${VARIANT}" in
+ case "${MODE}" in
light)
KANAGAWA_VARIANT="lotus"
# Dreamcoder Light palette — source: tokens.json modes.light.{text,accent,error,...}
@@ -217,26 +164,6 @@ if command -v tmux >/dev/null 2>&1; then
tmux set-option -g @ukiyo-cpu-usage-colors "notice bg_bar" 2>/dev/null || true
tmux set-option -g @ukiyo-ram-usage-colors "info bg_bar" 2>/dev/null || true
;;
- night)
- KANAGAWA_VARIANT="dragon"
- # Dreamcoder Night profile — derived from Dreamcoder Dark in tokens.json modes.dark
- # via the canonical night transform (brightness 0.86 / saturation 0.72).
- tmux set-option -g @ukiyo-color-text "#c6c6c6" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-bar "#0b0b0d" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-pane "#090909" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-accent "#a4afee" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-info "#62b9e2" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-notice "#ababab" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-muted "#ababab" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-error "#e88484" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-alert "#d1a126" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-highlight "#3a76d6" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-selection "#323232" 2>/dev/null || true
- # Swap plugin color order (same as dark: fg=bg_bar sobre bg=color)
- tmux set-option -g @ukiyo-git-colors "accent bg_bar" 2>/dev/null || true
- tmux set-option -g @ukiyo-cpu-usage-colors "notice bg_bar" 2>/dev/null || true
- tmux set-option -g @ukiyo-ram-usage-colors "info bg_bar" 2>/dev/null || true
- ;;
esac
tmux set-option -g @ukiyo-theme "kanagawa/${KANAGAWA_VARIANT}" 2>/dev/null || true
# Reload plugin to apply new theme colors immediately
@@ -261,11 +188,11 @@ fi
# --- Herdr: switch config symlink + reload ---
HERDR_SCRIPT="${DREAMCODER_DOTS_DIR}/scripts/herdr-theme-switch.sh"
if [[ -f "${HERDR_SCRIPT}" ]]; then
- bash "${HERDR_SCRIPT}" "${VARIANT}"
+ bash "${HERDR_SCRIPT}" "${MODE}"
fi
# --- /Herdr ---
-printf '✓ Dreamcoder %s mode applied (profile: %s)\n' "${MODE}" "${PROFILE}"
+printf '✓ Dreamcoder %s mode applied\n' "${MODE}"
# --- Post-sync: fix any stale symlinks ---
# Waybar colors.css, Rofi colors.rasi, and Hyprland colors.lua/colors.conf
@@ -275,12 +202,12 @@ printf '✓ Dreamcoder %s mode applied (profile: %s)\n' "${MODE}" "${PROFILE}"
DOTS_DIR="${DREAMCODER_DOTS_DIR:-${HOME}/Documents/PROYECTOS/dreamcoder-dots}"
DUNST_CONF="${HOME}/.config/dunst/dreamcoder-dunst.conf"
WARP_THEME="${HOME}/.local/share/warp-terminal/themes/Dreamcoder.yaml"
-WARP_VARIANT="${DOTS_DIR}/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-${VARIANT^}.yaml"
+WARP_VARIANT="${DOTS_DIR}/DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-${MODE^}.yaml"
if [[ -L "${DUNST_CONF}" ]]; then
target=$(readlink "${DUNST_CONF}")
case "${target}" in
- *dunst-dreamcoder-dark.conf | *dunst-dreamcoder-light.conf | *dunst-dreamcoder-night.conf)
+ *dunst-dreamcoder-dark.conf | *dunst-dreamcoder-light.conf)
ln -sf "${DOTS_DIR}/DreamcoderThemes/dreamcoder/dunst-dreamcoder.conf" "${DUNST_CONF}"
;;
esac
@@ -292,7 +219,7 @@ fi
# Btop: flip theme symlink to variant-specific artifact
BTOP_THEME="${HOME}/.config/btop/themes/dreamcoder.theme"
if [[ -L "${BTOP_THEME}" ]]; then
- ln -sf "dreamcoder-${VARIANT}.theme" "${BTOP_THEME}"
+ ln -sf "dreamcoder-${MODE}.theme" "${BTOP_THEME}"
fi
# btop.conf is ML4W-owned and upgrades reset color_theme (to "matugen"), so
# re-select the Dreamcoder theme on every apply; a no-op when already set.
@@ -304,12 +231,12 @@ fi
# Zellij: update theme in config.kdl
ZELLIJ_CONF="${HOME}/.config/zellij/config.kdl"
if [[ -f "${ZELLIJ_CONF}" ]]; then
- sed -i "s/^theme \".*\"/theme \"dreamcoder-${VARIANT}\"/" "${ZELLIJ_CONF}"
+ sed -i "s/^theme \".*\"/theme \"dreamcoder-${MODE}\"/" "${ZELLIJ_CONF}"
fi
# Delta: flip git diff theme symlink to variant-specific artifact
DELTA_LINK="${HOME}/.config/git/delta-dreamcoder.gitconfig"
-DELTA_VARIANT="${DOTS_DIR}/DreamcoderThemes/dreamcoder/delta-dreamcoder-${VARIANT}.gitconfig"
+DELTA_VARIANT="${DOTS_DIR}/DreamcoderThemes/dreamcoder/delta-dreamcoder-${MODE}.gitconfig"
if [[ -f "${DELTA_VARIANT}" ]]; then
ln -sf "${DELTA_VARIANT}" "${DELTA_LINK}"
fi
@@ -320,7 +247,7 @@ fi
# repo keeps the generated active config.yml (mode-tracking) for COPY-style
# installs. ln -sf replaces the link atomically.
LAZYGIT_LINK="${HOME}/.config/lazygit/config.yml"
-LAZYGIT_VARIANT="${DOTS_DIR}/DreamcoderLazygit/.config/lazygit/config.${VARIANT}.yml"
+LAZYGIT_VARIANT="${DOTS_DIR}/DreamcoderLazygit/.config/lazygit/config.${MODE}.yml"
if [[ -f "${LAZYGIT_VARIANT}" ]]; then
ln -sf "${LAZYGIT_VARIANT}" "${LAZYGIT_LINK}"
fi
diff --git a/scripts/dreamcoder b/scripts/dreamcoder
index 3ad26059..8957fa6e 100755
--- a/scripts/dreamcoder
+++ b/scripts/dreamcoder
@@ -37,14 +37,13 @@ case "${CMD}" in
upstream-diff) exec "${DREAMCODER_DOTS_DIR}/scripts/upstream-diff.py" "$@" ;;
preview) exec "${DREAMCODER_DOTS_DIR}/scripts/generate-theme-preview.py" "$@" ;;
auto) exec "${DREAMCODER_DOTS_DIR}/scripts/theme-auto.sh" "$@" ;;
- # Phase 5 (R7): light/dark/night route through the control activation
- # path (prepare -> validate -> persist -> commit -> reload adapter); the
- # generic settings interface continues to serve theme.render_profile.
- light|dark|night) CONTROL theme apply "${CMD}" "$@" ;;
+ # Phase 5 (R7): light/dark route through the control activation path
+ # (prepare -> validate -> persist -> commit -> reload adapter).
+ light|dark) CONTROL theme apply "${CMD}" "$@" ;;
theme) CONTROL theme "$@" ;;
sync) exec "${DREAMCODER_DOTS_DIR}/scripts/sync-dreamcoder-theme.py" "$@" ;;
*)
- printf 'Usage: dreamcoder {install|repair|doctor|doctor-json|dashboard|tui|docs|audit|control|settings|profile|motion|backup|visual|installer|status|verify|upstream-diff|preview|auto|light|dark|night|theme|sync}\n' >&2
+ printf 'Usage: dreamcoder {install|repair|doctor|doctor-json|dashboard|tui|docs|audit|control|settings|profile|motion|backup|visual|installer|status|verify|upstream-diff|preview|auto|light|dark|theme|sync}\n' >&2
exit 2
;;
esac
diff --git a/scripts/generate-theme-preview.py b/scripts/generate-theme-preview.py
index 8603fb47..bd7aeafe 100755
--- a/scripts/generate-theme-preview.py
+++ b/scripts/generate-theme-preview.py
@@ -9,7 +9,6 @@
sys.path.insert(0, str(SRC))
from dreamcoder_theme._math import apca_lc # noqa: E402
-from dreamcoder_theme.palette import night_palette # noqa: E402
TOKENS = ROOT / "DreamcoderThemes/dreamcoder/tokens.json"
OUT = ROOT / "docs/generated/dreamcoder-theme-preview.md"
@@ -178,21 +177,6 @@ def main():
parts.append("")
parts.append(ui_contrast_table(label, palette))
parts.append("")
- night_params = tokens.get("render_profiles", {}).get("night", {})
- try:
- guardrail_numbers: dict[str, float] = {
- k: float(v) for k, v in guardrails.items() if isinstance(v, (int, float))
- }
- night = night_palette(tokens["modes"]["dark"], night_params, guardrail_numbers)
- except (KeyError, TypeError, ValueError) as error:
- raise SystemExit(f"unable to derive Night render profile: {error}") from error
- night_label = "Night (render profile derived from Dreamcoder Dark)"
- parts.append(contrast_table(night_label, night))
- parts.append("")
- parts.append(apca_table(night_label, night, body_min, ui_min))
- parts.append("")
- parts.append(ui_contrast_table(night_label, night))
- parts.append("")
parts += [
"## Usage",
"",
diff --git a/scripts/theme-auto.sh b/scripts/theme-auto.sh
index 0962e5da..a0ea4a6c 100755
--- a/scripts/theme-auto.sh
+++ b/scripts/theme-auto.sh
@@ -11,8 +11,6 @@ DARK_START="${DREAMCODER_DARK_START:-18}"
HOUR="$(date +%H)"
MODE="dark"
if (( 10#${HOUR} >= LIGHT_START && 10#${HOUR} < DARK_START )); then MODE="light"; fi
-# The scheduler keeps its existing Light/Dark schedule and never activates
-# Night (R7, task 5.6): the render profile is pinned to standard regardless
-# of any ambient DREAMCODER_THEME_PROFILE, and the bounded adapter routes
-# through the control activation path.
-exec "${DREAMCODER_DOTS_DIR}/scripts/apply-theme-mode.sh" "${MODE}" "${DREAMCODER_WALLPAPER:-}" "standard"
+# Light/Dark schedule; the bounded adapter routes through the control
+# activation path.
+exec "${DREAMCODER_DOTS_DIR}/scripts/apply-theme-mode.sh" "${MODE}" "${DREAMCODER_WALLPAPER:-}"
diff --git a/scripts/verify-theme-health.py b/scripts/verify-theme-health.py
index 2905e391..78f45964 100755
--- a/scripts/verify-theme-health.py
+++ b/scripts/verify-theme-health.py
@@ -20,7 +20,7 @@
load_tokens,
)
from dreamcoder_theme.palette import ansi as terminal_ansi # noqa: E402
-from dreamcoder_theme.palette import load_guardrails, night_palette, validate_palette # noqa: E402
+from dreamcoder_theme.palette import validate_palette # noqa: E402
from dreamcoder_theme.renderers_opencode import opencode_content # noqa: E402
FILES = [
@@ -31,7 +31,6 @@
ANTIGRAVITY_FILES = [
ROOT / "DreamcoderAntigravity/Dreamcoder.json",
ROOT / "DreamcoderAntigravity/Dreamcoder-Dark.json",
- ROOT / "DreamcoderAntigravity/Dreamcoder-Night.json",
ROOT / "DreamcoderAntigravity/Dreamcoder-Light.json",
]
TOKEN_FILE = ROOT / "DreamcoderThemes/dreamcoder/tokens.json"
@@ -376,10 +375,8 @@ def check_tokens():
def check_dual_gate_candidates():
- """Validate the package dual gate on all four deterministic candidates:
- standard Light, standard Dark, design-system Dusk, and derived Night
- (Night = night_palette of canonical dark with canonical render_profiles,
- wallpaper adaptation disabled for the gate). Any dual-gate error blocks."""
+ """Validate the package dual gate on all three deterministic candidates:
+ Light, Dark, and design-system Dusk. Any dual-gate error blocks."""
tokens = load_tokens(TOKEN_FILE)
# float() narrows the comprehension to dict[str, float] (mirrors
# palette.load_guardrails), satisfying the dict-invariance typing.
@@ -389,24 +386,18 @@ def check_dual_gate_candidates():
}
except (TypeError, ValueError) as error:
raise SystemExit(f"invalid numeric guardrail: {error}") from error
- night_params = tokens.get("render_profiles", {}).get("night")
- require(
- isinstance(night_params, dict),
- "tokens: render_profiles.night missing (canonical Night parameters required)",
- )
candidates = [
("light", tokens["modes"]["light"], "light"),
("dark", tokens["modes"]["dark"], "dark"),
("dusk", tokens["modes"]["dusk"], "dusk"),
- ("night", night_palette(tokens["modes"]["dark"], night_params, guardrails), "dark"),
]
for label, palette, mode in candidates:
- errors = validate_palette(palette, guardrails, profile=label, mode=mode)
+ errors = validate_palette(palette, guardrails, mode=mode)
require(not errors, f"dual gate {label}:\n" + "\n".join(errors))
-def check_night_coverage():
- """Fail when the sync 33-consumer Night coverage declaration is missing,
+def check_coverage_declaration():
+ """Fail when the sync 33-consumer coverage declaration is missing,
duplicated, or not exactly the design matrix's 33 IDs."""
from dreamcoder_theme.sync import COVERAGE
@@ -417,18 +408,14 @@ def check_night_coverage():
def check_antigravity_files():
- """Validate Antigravity's semantic button pair for every render profile."""
+ """Validate Antigravity's semantic button pair for every mode file."""
if not any(file.exists() for file in ANTIGRAVITY_FILES):
return
tokens = load_tokens(TOKEN_FILE)
- guardrails = load_guardrails(TOKEN_FILE)
palettes = {
"Dreamcoder.json": tokens["modes"]["dark"],
"Dreamcoder-Dark.json": tokens["modes"]["dark"],
"Dreamcoder-Light.json": tokens["modes"]["light"],
- "Dreamcoder-Night.json": night_palette(
- tokens["modes"]["dark"], tokens["render_profiles"]["night"], guardrails
- ),
}
for file in ANTIGRAVITY_FILES:
colors = _load_json(file)["colors"]
@@ -573,17 +560,6 @@ def _health_findings():
for mode in ("dark", "light", "dusk")
if mode in tokens["modes"]
}
- guardrails = {
- key: float(value)
- for key, value in tokens["guardrails"].items()
- if isinstance(value, int | float)
- }
- expected_by_mode["night"] = opencode_content(
- night_palette(
- tokens["modes"]["dark"], tokens["render_profiles"]["night"], guardrails
- ),
- transparent_background=True,
- )
if actual not in set(expected_by_mode.values()):
findings.append(
"STALE_ARTIFACT: .opencode/themes/dreamcoder.json "
@@ -724,7 +700,7 @@ def check_hypr_colors_file(file):
check_opencode_repo()
check_design_system_contract()
check_dual_gate_candidates()
-check_night_coverage()
+check_coverage_declaration()
check_antigravity_files()
for file in KITTY_FILES:
check_kitty_colors(file)
diff --git a/skills/dreamcoder-palette-tokens/SKILL.md b/skills/dreamcoder-palette-tokens/SKILL.md
index 893f9b9e..f87f49b6 100644
--- a/skills/dreamcoder-palette-tokens/SKILL.md
+++ b/skills/dreamcoder-palette-tokens/SKILL.md
@@ -12,8 +12,7 @@ Defined in `DreamcoderThemes/dreamcoder/tokens.json` with validation schema at `
| `light` | Dreamcoder Light | `#f3eadc` | `#824f16` | 07:00-16:00 |
| `dusk` | Dreamcoder Dusk | `#ebe4d6` | `#8a5520` | 16:00-18:00 |
-These are the only canonical modes. `night` is a render profile derived from Dreamcoder Dark,
-not a schema mode. OLED behavior belongs to `modes.dark.surface_policy`.
+These are the only canonical modes; Dark and Light are the user-facing ones. OLED behavior belongs to `modes.dark.surface_policy`.
## Token Categories
diff --git a/src/dreamcoder_theme/cli_handlers.py b/src/dreamcoder_theme/cli_handlers.py
index 22f623c7..c50684ac 100644
--- a/src/dreamcoder_theme/cli_handlers.py
+++ b/src/dreamcoder_theme/cli_handlers.py
@@ -249,12 +249,8 @@ def handle_backup(args: argparse.Namespace) -> int:
# Theme activation transaction (design §7/§8, R4/R7; tasks 5.3/5.5)
# ---------------------------------------------------------------------------
-# User choice -> (base mode, render profile) per design §7 table.
-THEME_CHOICES: dict[str, tuple[str, str]] = {
- "light": ("light", "standard"),
- "dark": ("dark", "standard"),
- "night": ("dark", "night"),
-}
+# Activatable theme modes (design §7 table): Dreamcoder Light and Dark.
+THEME_CHOICES: tuple[str, ...] = ("light", "dark")
class ThemeActivationError(RuntimeError):
@@ -411,18 +407,14 @@ def _mutable_paths(paths: Any) -> list[Path]:
return mutable
-def _bridge_variant(base: str, profile: str) -> str:
- return "night" if profile == "night" else base
-
-
-def _flip_bridge_symlinks(paths: Any, base: str, profile: str) -> None:
+def _flip_bridge_symlinks(paths: Any, base: str) -> None:
"""Select the correct variant target for matugen bridge symlinks (design §6).
Only flips paths that are already symlinks — regular files are written
directly by the sync writers. The activation transaction snapshots these
before the flip, so a rollback restores the prior link targets exactly.
"""
- variant = _bridge_variant(base, profile)
+ variant = base
bridges = [
(paths.waybar_matugen, f"colors-{variant}.css"),
(paths.rofi_matugen, f"colors-{variant}.rasi"),
@@ -437,7 +429,7 @@ def _flip_bridge_symlinks(paths: Any, base: str, profile: str) -> None:
os.symlink(target, link)
-def run_reload_adapter(base: str, profile: str) -> None:
+def run_reload_adapter(base: str) -> None:
"""Invoke the bounded post-validation system/reload adapter (design §7).
``scripts/apply-theme-mode.sh`` owns the system-mode, symlink, and reload
@@ -453,12 +445,11 @@ def run_reload_adapter(base: str, profile: str) -> None:
env.update(
{
"DREAMCODER_THEME_MODE": base,
- "DREAMCODER_THEME_PROFILE": profile,
"DREAMCODER_SYNC_DONE": "1",
}
)
proc = subprocess.run(
- [str(script), base, env.get("DREAMCODER_WALLPAPER", ""), profile],
+ [str(script), base, env.get("DREAMCODER_WALLPAPER", "")],
env=env,
text=True,
stdout=subprocess.PIPE,
@@ -471,8 +462,8 @@ def run_reload_adapter(base: str, profile: str) -> None:
)
-def _regenerate_prior(paths: Any, prior_base: str, prior_profile: str) -> None:
- """Best-effort regeneration of the prior profile after a rollback.
+def _regenerate_prior(paths: Any, prior_base: str) -> None:
+ """Best-effort regeneration of the prior mode after a rollback.
Snapshots already restore every mutable path byte-for-byte; regeneration is
a safety net for deterministic repo artifacts and never masks the original
@@ -481,8 +472,8 @@ def _regenerate_prior(paths: Any, prior_base: str, prior_profile: str) -> None:
# Best-effort by contract: any regeneration failure must not mask the
# original activation error this rollback is recovering from.
with contextlib.suppress(Exception):
- prepared = sync.prepare(prior_base, prior_profile)
- sync.sync_active_targets(paths, prepared.active, prior_base, prior_profile)
+ prepared = sync.prepare(prior_base)
+ sync.sync_active_targets(paths, prepared.active, prior_base)
sync.sync_bat_theme_variants(paths, prepared.variants)
if write_repo_enabled():
sync.sync_repo_snippets(prepared.variants, prepared.active)
@@ -492,7 +483,6 @@ def _commit_activation(
paths: Any,
prepared: sync.PreparedSync,
base: str,
- profile: str,
changed: dict[str, bool],
) -> None:
"""Commit one prepared activation: persist, flip bridges, write, validate.
@@ -501,16 +491,14 @@ def _commit_activation(
(write failure, invalid post-write selector, blocking reload failure)
triggers a full snapshot rollback in the caller.
"""
- # 4. Persist settings before applying the base mode (R7: light/dark
- # explicitly persist standard and exit Night).
+ # 4. Persist settings before applying the base mode (R7).
set_nested_setting("terminal.default_mode", base)
- set_nested_setting("theme.render_profile", profile)
# 5. Flip matugen bridge symlinks to the target variant before the writers
- # run (design §6: "select colors-night.css before commit").
- _flip_bridge_symlinks(paths, base, profile)
+ # run (design §6).
+ _flip_bridge_symlinks(paths, base)
# 6. Commit variants, active files, and selectors. write_if_changed()
# semantics are preserved — each entry reports bool changed.
- changed.update(sync.sync_active_targets(paths, prepared.active, base, profile))
+ changed.update(sync.sync_active_targets(paths, prepared.active, base))
bat_changes = sync.sync_bat_theme_variants(paths, prepared.variants)
if write_repo_enabled():
repo_changes = sync.sync_repo_snippets(prepared.variants, prepared.active)
@@ -521,31 +509,30 @@ def _commit_activation(
raise ThemeActivationError(f"Generated Starship config is invalid: {paths.starship}")
# 8. Bounded system/reload adapter; a blocking reload failure rolls back.
if os.environ.get("DREAMCODER_SYNC_DONE") != "1":
- run_reload_adapter(base, profile)
+ run_reload_adapter(base)
-def apply_theme(base: str, profile: str, choice: str, as_json: bool) -> int:
- """Activate a (base, profile) pair as one transaction (design §7/§8).
+def apply_theme(base: str, as_json: bool) -> int:
+ """Activate a Light/Dark base mode as one transaction (design §7/§8).
Order: prepare+validate (zero writes) -> snapshot every mutable active path
+ selector file + settings -> persist settings -> flip bridge symlinks ->
commit variants/active files/selectors -> post-write validation -> bounded
system/reload adapter. Any exception, invalid post-write selector, blocking
reload failure, or incomplete coverage restores snapshots and prior settings
- and regenerates the prior profile before returning non-zero.
+ and regenerates the prior mode before returning non-zero.
"""
paths = theme_paths()
# 1. Prepare + validate: a failed gate exits before any persistence, write,
# symlink change, cleanup, system-mode change, or reload (R4/R8).
try:
- prepared = sync.prepare(base, profile)
+ prepared = sync.prepare(base)
except sync.ThemeGateError as exc:
if as_json:
emit(
{
- "requested": choice,
+ "requested": base,
"effective_base": base,
- "effective_profile": profile,
"coverage": "0/0",
"changed": {},
"rollback_state": "rejected",
@@ -559,9 +546,7 @@ def apply_theme(base: str, profile: str, choice: str, as_json: bool) -> int:
# 2. Prior persisted state (the rollback target).
prior_base = settings_get("terminal.default_mode")
- prior_profile = settings_get("theme.render_profile")
prior_base = prior_base if prior_base in {"light", "dark"} else "light"
- prior_profile = prior_profile if prior_profile in {"standard", "night"} else "standard"
# 3. Snapshot every mutable active path + selector file BEFORE the first
# mutation (design §4: "snapshot settings and every mutable active path").
@@ -585,19 +570,17 @@ def apply_theme(base: str, profile: str, choice: str, as_json: bool) -> int:
rollback_state = "none"
changed: dict[str, bool] = {}
try:
- _commit_activation(paths, prepared, base, profile, changed)
+ _commit_activation(paths, prepared, base, changed)
except Exception as exc:
rollback_state = "restored"
_restore_snapshots(snapshots, dir_captures)
set_nested_setting("terminal.default_mode", prior_base)
- set_nested_setting("theme.render_profile", prior_profile)
- _regenerate_prior(paths, prior_base, prior_profile)
+ _regenerate_prior(paths, prior_base)
if as_json:
emit(
{
- "requested": choice,
+ "requested": base,
"effective_base": prior_base,
- "effective_profile": prior_profile,
"coverage": f"{len(prepared.coverage)}/{len(prepared.coverage)}",
"changed": {},
"rollback_state": rollback_state,
@@ -611,9 +594,8 @@ def apply_theme(base: str, profile: str, choice: str, as_json: bool) -> int:
return 1
status = {
- "requested": choice,
+ "requested": base,
"effective_base": base,
- "effective_profile": profile,
"coverage": f"{len(prepared.coverage)}/{len(prepared.coverage)}",
"changed": changed,
"rollback_state": rollback_state,
@@ -632,8 +614,7 @@ def _restore_snapshots(
def handle_theme(args: argparse.Namespace) -> int:
- """Activate a theme base mode + render profile (design §7)."""
- if args.theme_cmd != "apply":
+ """Activate the Dreamcoder Light or Dark theme (design §7)."""
+ if args.theme_cmd != "apply" or args.choice not in THEME_CHOICES:
return 2
- base, profile = THEME_CHOICES[args.choice]
- return apply_theme(base, profile, args.choice, args.json)
+ return apply_theme(args.choice, args.json)
diff --git a/src/dreamcoder_theme/cli_parser.py b/src/dreamcoder_theme/cli_parser.py
index f77b95b7..31d823aa 100644
--- a/src/dreamcoder_theme/cli_parser.py
+++ b/src/dreamcoder_theme/cli_parser.py
@@ -112,11 +112,9 @@ def build_parser() -> argparse.ArgumentParser:
backup_restore.add_argument("--dry-run", action="store_true")
backup_restore.add_argument("--json", action="store_true")
- theme = sub.add_parser("theme", help="Activate a theme base mode and render profile")
+ theme = sub.add_parser("theme", help="Activate the Dreamcoder Light or Dark theme")
theme_sub = theme.add_subparsers(dest="theme_cmd", required=True)
theme_apply = theme_sub.add_parser("apply", help="Apply a theme choice as one transaction")
- theme_apply.add_argument(
- "choice", choices=["light", "dark", "night"], help="Theme choice to activate"
- )
+ theme_apply.add_argument("choice", choices=["light", "dark"], help="Theme choice to activate")
theme_apply.add_argument("--json", action="store_true")
return parser
diff --git a/src/dreamcoder_theme/doctor.py b/src/dreamcoder_theme/doctor.py
index 3a008fb3..0b4a3ed2 100644
--- a/src/dreamcoder_theme/doctor.py
+++ b/src/dreamcoder_theme/doctor.py
@@ -285,7 +285,7 @@ def doctor_checks() -> list[HealthCheck]:
timer_status = "ok" if result.returncode == 0 and timer_detail == "active" else "warn"
checks.append(
HealthCheck(
- name="day/night timer",
+ name="light/dark timer",
status=timer_status,
detail=timer_detail,
repair="systemctl --user enable --now dreamcoder-theme-auto.timer",
diff --git a/src/dreamcoder_theme/herdr-contract-evidence.md b/src/dreamcoder_theme/herdr-contract-evidence.md
index a1a4ce31..96429b57 100644
--- a/src/dreamcoder_theme/herdr-contract-evidence.md
+++ b/src/dreamcoder_theme/herdr-contract-evidence.md
@@ -155,7 +155,7 @@ from 0.9.0), binary SHA-256
`herdr config check` rejects unknown keys (`unknown config key
theme.custom.; ignoring key`) and unknown theme names with a non-zero
exit, which closes the open question of whether the 0.8.2 field set is still
-accepted: each generated `0.9.1/config.{dark,light,night}.toml` passed
+accepted: each generated `0.9.1/config.{dark,light}.toml` passed
`HERDR_CONFIG_PATH= herdr config check` with `config: ok`. The profile
therefore reuses the 0.8.2 theme, custom, and ui field sets exactly and adds no
new fields.
diff --git a/src/dreamcoder_theme/herdr_switch.py b/src/dreamcoder_theme/herdr_switch.py
index bf400d3d..f5db3945 100644
--- a/src/dreamcoder_theme/herdr_switch.py
+++ b/src/dreamcoder_theme/herdr_switch.py
@@ -31,12 +31,12 @@
_VERSION_TIMEOUT_SECONDS = 5
_RELOAD_TIMEOUT_SECONDS = 10
-_VALID_MODES = ("dark", "light", "night")
+_VALID_MODES = ("dark", "light")
_VARIANT_ROOT = ROOT / "DreamcoderHerdr/.config/herdr/dreamcoder"
_VERSION_OUTPUT = re.compile(r"herdr[ \t]+(\d+)\.(\d+)\.(\d+)", re.IGNORECASE)
Run = Callable[..., subprocess.CompletedProcess[str]]
-Mode = Literal["dark", "light", "night"]
+Mode = Literal["dark", "light"]
Status = Literal[
"applied",
diff --git a/src/dreamcoder_theme/palette.py b/src/dreamcoder_theme/palette.py
index 4ff7f255..7c304376 100644
--- a/src/dreamcoder_theme/palette.py
+++ b/src/dreamcoder_theme/palette.py
@@ -3,7 +3,6 @@
from __future__ import annotations
import json
-import math
import re
import subprocess
import warnings
@@ -32,9 +31,7 @@
"guard",
"hex_to_rgb",
"load_guardrails",
- "load_render_profile",
"mix",
- "night_palette",
"rel_luminance",
"rgb_to_hex",
"surface_guard",
@@ -206,7 +203,7 @@ def make_guard(palette: dict[str, str], minimum: float = 3.0) -> Callable[[str],
# Declarative APCA pair classes (ADR-002).
#
# Each class names its token pairs and the guardrail keys that own the
-# threshold — light/dusk floor and dark/night floor. No numeric policy
+# threshold — light/dusk floor and dark floor. No numeric policy
# literals are allowed here; a missing guardrail key fails validation.
# ------------------------------------------------------------------
_APCA_PAIR_CLASSES: tuple[tuple[str, tuple[tuple[str, str], ...], str, str], ...] = (
@@ -263,7 +260,6 @@ def validate_palette(
palette: dict[str, str],
guardrails: dict[str, float] | None = None,
*,
- profile: str = "standard",
mode: str | None = None,
) -> list[str]:
"""Return stable validation errors for a mode palette.
@@ -273,13 +269,12 @@ def validate_palette(
failure on the other. Thresholds are resolved from ``guardrails`` by
key; APCA thresholds must be present or validation fails closed.
- ``profile`` is the rendering profile (``standard`` today; ``night``
- arrives with the transform). ``mode`` defaults to the palette-derived
- base mode (``dark`` for ``details=darker``, otherwise ``light``).
+ ``mode`` defaults to the palette-derived base mode (``dark`` for
+ ``details=darker``, otherwise ``light``).
Metric diagnostics use the stable shape::
- {metric} fail: mode={mode} profile={profile} pair={fg}/{bg} \
+ {metric} fail: mode={mode} pair={fg}/{bg} \
measured={value} guardrail={key}={threshold}
"""
g = guardrails or {}
@@ -292,14 +287,14 @@ def validate_palette(
def wcag_diag(fg_key: str, bg_key: str, measured: float, key: str, threshold: float) -> str:
return (
- f"WCAG fail: mode={effective_mode} profile={profile} "
+ f"WCAG fail: mode={effective_mode} "
f"pair={fg_key}/{bg_key} measured={measured:.2f} "
f"guardrail={key}={threshold}"
)
def apca_diag(cls: str, fg_key: str, bg_key: str, lc: float, key: str, threshold: float) -> str:
return (
- f"APCA fail: mode={effective_mode} profile={profile} "
+ f"APCA fail: mode={effective_mode} "
f"pair={fg_key}/{bg_key} class={cls} measured={abs(lc):.1f} "
f"guardrail={key}={threshold}"
)
@@ -377,28 +372,7 @@ def apca_diag(cls: str, fg_key: str, bg_key: str, lc: float, key: str, threshold
return errors
-# ---------------------------------------------------------------------------
-# Canonical Night/Dim rendering profile (Phase 2; ADR-003).
-#
-# The transform is a deterministic brightness/saturation reduction of the
-# Dreamcoder Dark palette. All parameters come from the canonical token
-# contract (render_profiles.night) — never policy literals — and the bounded
-# corrective pass restores a floor without weakening any threshold or
-# brightening background/surface roles. Failure after the bound is reported by
-# the caller's validate_palette() gate (fail closed, no standard-dark fallback).
-# ---------------------------------------------------------------------------
-
-_NIGHT_PROFILE_KEYS = (
- "brightness_factor",
- "saturation_factor",
- "maximum_corrective_delta",
- "corrective_step",
-)
-
-_HEX_RE = re.compile(r"^#[0-9a-fA-F]{6}$")
-_RGBA_RE = re.compile(r"^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([^)]+)\)$")
-
-# Guardrail keys validate_palette() / night_palette() resolve; a missing key
+# Guardrail keys validate_palette() resolves; a missing key
# fails closed so thresholds always originate in the canonical token file.
_REQUIRED_GUARDRAIL_KEYS = (
"minimum_text_contrast",
@@ -422,27 +396,6 @@ def _require_guardrails(guardrails: dict[str, float]) -> None:
raise ValueError(f"missing required guardrails: {', '.join(missing)}")
-def _validate_profile_parameters(params: dict[str, float]) -> None:
- """Fail closed when render-profile parameters violate the canonical bounds."""
- if set(params) != set(_NIGHT_PROFILE_KEYS):
- raise ValueError(
- "night profile parameters must contain exactly "
- f"{sorted(_NIGHT_PROFILE_KEYS)}, got {sorted(params)}"
- )
- brightness = params["brightness_factor"]
- saturation = params["saturation_factor"]
- max_delta = params["maximum_corrective_delta"]
- step = params["corrective_step"]
- if not (0 < brightness <= 1):
- raise ValueError("brightness_factor must be in (0, 1]")
- if not (0 < saturation <= 1):
- raise ValueError("saturation_factor must be in (0, 1]")
- if not (0 <= max_delta <= 0.20):
- raise ValueError("maximum_corrective_delta must be in [0, 0.20]")
- if not (0 < step <= max_delta):
- raise ValueError("corrective_step must be in (0, maximum_corrective_delta]")
-
-
def load_guardrails(tokens_file: Path) -> dict[str, float]:
"""Load numeric guardrails from the canonical token file, failing closed.
@@ -460,282 +413,3 @@ def load_guardrails(tokens_file: Path) -> dict[str, float]:
numeric = {k: float(v) for k, v in guardrails.items() if isinstance(v, (int, float))}
_require_guardrails(numeric)
return numeric
-
-
-def load_render_profile(tokens_file: Path, name: str = "night") -> dict[str, float]:
- """Load canonical render-profile parameters, failing closed on absence."""
- if not tokens_file.is_file():
- raise ValueError(f"tokens file not found: {tokens_file}")
- try:
- tokens = json.loads(tokens_file.read_text(encoding="utf-8"))
- except (json.JSONDecodeError, OSError) as exc:
- raise ValueError(f"invalid tokens file: {tokens_file}") from exc
- profile = tokens.get("render_profiles", {}).get(name)
- if not isinstance(profile, dict):
- raise ValueError(f"render profile {name!r} missing from tokens")
- params = {k: float(v) for k, v in profile.items() if isinstance(v, (int, float))}
- _validate_profile_parameters(params)
- return params
-
-
-def _hsl_from_rgb(r: int, g: int, b: int) -> tuple[float, float, float]:
- rn, gn, bn = r / 255, g / 255, b / 255
- mx, mn = max(rn, gn, bn), min(rn, gn, bn)
- lightness = (mx + mn) / 2
- delta = mx - mn
- if delta == 0:
- return 0.0, 0.0, lightness
- saturation = delta / (1 - abs(2 * lightness - 1))
- if mx == rn:
- hue = 60 * (((gn - bn) / delta) % 6)
- elif mx == gn:
- hue = 60 * ((bn - rn) / delta + 2)
- else:
- hue = 60 * ((rn - gn) / delta + 4)
- return hue, saturation, lightness
-
-
-def _rgb_from_hsl(hue: float, saturation: float, lightness: float) -> tuple[int, int, int]:
- chroma = (1 - abs(2 * lightness - 1)) * saturation
- x = chroma * (1 - abs((hue / 60) % 2 - 1))
- m = lightness - chroma / 2
- if hue < 60:
- r1, g1, b1 = chroma, x, 0.0
- elif hue < 120:
- r1, g1, b1 = x, chroma, 0.0
- elif hue < 180:
- r1, g1, b1 = 0.0, chroma, x
- elif hue < 240:
- r1, g1, b1 = 0.0, x, chroma
- elif hue < 300:
- r1, g1, b1 = x, 0.0, chroma
- else:
- r1, g1, b1 = chroma, 0.0, x
- # Deterministic nearest-integer rounding for byte-identical output.
- r = round((r1 + m) * 255)
- g = round((g1 + m) * 255)
- b = round((b1 + m) * 255)
- return max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b))
-
-
-def _transform_hex(value: str, params: dict[str, float]) -> str:
- hue, saturation, lightness = _hsl_from_rgb(*hex_to_rgb(value))
- rgb = _rgb_from_hsl(
- hue,
- saturation * params["saturation_factor"],
- lightness * params["brightness_factor"],
- )
- return rgb_to_hex(rgb).lower()
-
-
-def _transform_rgba(value: str, params: dict[str, float]) -> str:
- match = _RGBA_RE.match(value)
- if match is None: # defensive: non-color metadata handled by caller
- return value
- r, g, b, alpha = int(match.group(1)), int(match.group(2)), int(match.group(3)), match.group(4)
- hue, saturation, lightness = _hsl_from_rgb(r, g, b)
- nr, ng, nb = _rgb_from_hsl(
- hue,
- saturation * params["saturation_factor"],
- lightness * params["brightness_factor"],
- )
- return f"rgba({nr}, {ng}, {nb}, {alpha})"
-
-
-def _alias_groups(base: dict[str, str]) -> dict[str, list[str]]:
- """Map every shared color value to its sorted key list (metadata excluded).
-
- Members of one group share a color identity (e.g. ``selection ==
- selection_bg``) and must stay byte-identical after the transform,
- preventing independent rounding or correction drift. Incidental equalities
- that mix roles (e.g. ``bg == on_accent``) are grouped here but the
- corrective pass never propagates onto background/surface roles.
- """
- groups: dict[str, list[str]] = {}
- for key, value in base.items():
- if key in ("name", "details"):
- continue
- if not (_HEX_RE.match(value) or _RGBA_RE.match(value)):
- continue
- groups.setdefault(value.lower(), []).append(key)
- return {value: sorted(keys) for value, keys in groups.items() if len(keys) > 1}
-
-
-# Background/surface roles that correction must never touch or brighten: the
-# on-accent pair's background is the semantic ``accent`` color, NOT one of
-# these roles, so the bounded pass may adjust it to restore the on-accent floor.
-_SURFACE_ROLES = frozenset(
- {
- "bg",
- "bg_soft",
- "surface0",
- "surface1",
- "surface2",
- "surface3",
- "selection",
- "selection_bg",
- "hover",
- "pressed",
- "prompt_bg",
- "prompt_surface0",
- "prompt_surface1",
- "prompt_surface2",
- }
-)
-
-
-def _move_toward_endpoint(out: dict[str, str], token: str, pair_other: str, step: float) -> str:
- """Move ``token`` lightness one bounded step toward its pair's contrast
- endpoint (dark-on-light darkens, light-on-dark lightens), preserving hue
- and the already-reduced saturation."""
- hue, saturation, lightness = _hsl_from_rgb(*hex_to_rgb(out[token]))
- if rel_luminance(out[token]) < rel_luminance(out[pair_other]):
- lightness = max(0.0, lightness - step)
- else:
- lightness = min(1.0, lightness + step)
- return rgb_to_hex(_rgb_from_hsl(hue, saturation, lightness)).lower()
-
-
-def _apply_with_aliases(
- out: dict[str, str], token: str, value: str, members_of: dict[str, list[str]]
-) -> None:
- """Write a corrected value to the token and its alias-group members, never
- onto background/surface roles (which keep their pure-transform value)."""
- for member in members_of.get(token, [token]):
- if member in _SURFACE_ROLES:
- continue
- out[member] = value
-
-
-def _corrective_pass(
- out: dict[str, str],
- guardrails: dict[str, float],
- params: dict[str, float],
- effective_mode: str,
- groups: dict[str, list[str]],
-) -> None:
- """Bounded lightness correction for failing declared pairs (in place).
-
- Each step moves a failing foreground's lightness toward its pair's
- contrast-safe endpoint (polarity-aware) while preserving hue and the
- already-reduced saturation. Total movement per token is structurally
- capped at ``maximum_corrective_delta``. Background/surface roles are never
- brightened; thresholds are never weakened. The on-accent pair is the one
- case whose foreground is pinned at a near-extreme (even pure black on the
- accent cannot reach its floor), so its semantic ``accent`` pair-background
- -- not a surface role -- receives the bounded adjustment instead; the
- corrected value propagates through alias groups so semantic relationships
- stay exact (``selection == selection_bg`` etc.).
- """
- max_delta = params["maximum_corrective_delta"]
- step = params["corrective_step"]
- if max_delta <= 0 or step <= 0:
- return
- text_min = guardrails["minimum_text_contrast"]
- # Alias-group membership per token: aliases share one value, so they
- # share one corrective budget and step at most once per sweep (otherwise
- # e.g. subtle/disabled would double-step the shared value and break the
- # maximum_corrective_delta cap).
- members_of: dict[str, list[str]] = {}
- group_of: dict[str, str] = {}
- for keys in groups.values():
- for member in keys:
- members_of[member] = keys
- group_of[member] = keys[0]
-
- def rep(token: str) -> str:
- return group_of.get(token, token)
-
- moved: dict[str, float] = {}
- max_sweeps = math.ceil(max_delta / step)
- for _ in range(max_sweeps):
- touched = False
- stepped: set[str] = set()
- for cls, pairs, light_key, dark_key in _APCA_PAIR_CLASSES:
- floor_key = light_key if effective_mode in ("light", "dusk") else dark_key
- floor = guardrails[floor_key]
- for fg_key, bg_key in pairs:
- if fg_key not in out or bg_key not in out:
- continue
- fg, bg = out[fg_key], out[bg_key]
- if abs(apca_lc(fg, bg)) >= floor and contrast(fg, bg) >= text_min:
- continue
- if bg_key not in _SURFACE_ROLES:
- # On-accent: the pair background is the semantic accent
- # color (pinned foreground cannot reach the floor); adjust
- # the background, bounded, to restore the floor.
- target, other = bg_key, fg_key
- else:
- target, other = fg_key, bg_key
- target_rep = rep(target)
- if target_rep in stepped:
- continue
- remaining = max_delta - moved.get(target_rep, 0.0)
- if remaining <= 0:
- continue
- old_lightness = _hsl_from_rgb(*hex_to_rgb(out[target]))[2]
- corrected = _move_toward_endpoint(out, target, other, min(step, remaining))
- stepped.add(target_rep)
- if corrected == out[target]:
- continue # clamped at the endpoint; stop stepping it
- new_lightness = _hsl_from_rgb(*hex_to_rgb(corrected))[2]
- moved[target_rep] = moved.get(target_rep, 0.0) + abs(new_lightness - old_lightness)
- _apply_with_aliases(out, target, corrected, members_of)
- touched = True
- if not touched:
- return
-
-
-def night_palette(
- base: dict[str, str],
- profile_parameters: dict[str, float],
- guardrails: dict[str, float],
-) -> dict[str, str]:
- """Derive the deterministic Night palette from the dark base (ADR-003).
-
- Steps (design §2): copy the input (never mutate), set the derived display
- name while ``details`` stays ``darker``, reduce HSL lightness/saturation
- with deterministic integer-rounded RGB (lowercase hex), preserve ``rgba()``
- alpha exactly, re-establish input aliases, apply the bounded corrective
- pass to declared foreground tokens, and reject newly introduced pure
- black/white on functional roles. Canonical canvas and on-color roles
- explicitly authored as black remain black under Dark's surface policy.
-
- Parameters and guardrails are canonical (``load_render_profile`` /
- ``load_guardrails``); invalid values fail closed with ``ValueError`` and
- never fall back to the standard dark palette.
- """
- _validate_profile_parameters(profile_parameters)
- _require_guardrails(guardrails)
- palette_base = {key: value for key, value in base.items() if isinstance(value, str)}
- groups = _alias_groups(palette_base)
-
- out = dict(palette_base)
- out["name"] = f"{palette_base.get('name', 'Dreamcoder Dark')} Night"
- # details intentionally untouched: the transform keeps dark semantics.
-
- for key, value in palette_base.items():
- if _HEX_RE.match(value):
- out[key] = _transform_hex(value, profile_parameters)
- elif _RGBA_RE.match(value):
- out[key] = _transform_rgba(value, profile_parameters)
-
- # Re-establish exact aliases (pure transform keeps equal inputs equal, but
- # the corrective pass below must propagate to every member of an alias
- # group, so the groups are shared between both stages).
- for _value, keys in groups.items():
- first = keys[0]
- for member in keys[1:]:
- out[member] = out[first]
-
- effective_mode = detect_mode(out)
- _corrective_pass(out, guardrails, profile_parameters, effective_mode, groups)
-
- for key, value in out.items():
- is_pure = _HEX_RE.match(value) and value.lower() in ("#000000", "#ffffff")
- policy_black_roles = {"bg", "prompt_bg", "on_accent", "on_error", "on_focus"}
- preserves_authored_extreme = key in policy_black_roles and palette_base.get(key) == value
- if is_pure and not preserves_authored_extreme:
- raise ValueError(f"night transform produced pure {value} for functional role {key}")
-
- return out
diff --git a/src/dreamcoder_theme/renderer_contract.py b/src/dreamcoder_theme/renderer_contract.py
index 89d3aba6..9c2f7f10 100644
--- a/src/dreamcoder_theme/renderer_contract.py
+++ b/src/dreamcoder_theme/renderer_contract.py
@@ -24,12 +24,12 @@
# leaf renderers and callers compatible; adapters may accept Mapping internally.
Palette = dict[str, str]
-# Canonical authored themes are distinct from runtime render variants. Night is
-# derived from Dark by a render profile; it is never a fourth canonical mode.
+# Canonical authored themes are distinct from runtime render variants: Dusk is
+# a design-system token set, never a generated runtime variant.
CanonicalMode = Literal["light", "dusk", "dark"]
CANONICAL_MODES: frozenset[CanonicalMode] = frozenset({"light", "dusk", "dark"})
-RenderVariant = Literal["dark", "light", "night"]
+RenderVariant = Literal["dark", "light"]
# Backward-compatible registration type name: registrations describe generated
# runtime variants, not the canonical token schema.
RenderMode = RenderVariant
@@ -43,7 +43,7 @@
OutputKind = Literal["active", "repository", "active-and-repository"]
# Closed set of generated runtime variants accepted by registrations.
-ALL_RENDER_VARIANTS: frozenset[RenderVariant] = frozenset({"dark", "light", "night"})
+ALL_RENDER_VARIANTS: frozenset[RenderVariant] = frozenset({"dark", "light"})
ALL_MODES = ALL_RENDER_VARIANTS
diff --git a/src/dreamcoder_theme/renderers_antigravity.py b/src/dreamcoder_theme/renderers_antigravity.py
index df31b284..b6f4c51d 100644
--- a/src/dreamcoder_theme/renderers_antigravity.py
+++ b/src/dreamcoder_theme/renderers_antigravity.py
@@ -6,9 +6,8 @@
def antigravity_content(c: dict[str, str]) -> str:
- # Night must be classified dark WITHOUT depending on the word "Dark" in
- # the derived name (design §5 row 10): the transformed palette keeps
- # dark semantics in ``details``, which is the reliable signal.
+ # Classify by ``details`` rather than the display name: it is the
+ # reliable dark/light signal carried by every palette.
theme_type = "dark" if c.get("details", "darker") != "lighter" else "light"
return json.dumps(
{
@@ -116,7 +115,7 @@ def antigravity_content(c: dict[str, str]) -> str:
consumer_id="antigravity",
renderer=antigravity_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_codex.py b/src/dreamcoder_theme/renderers_codex.py
index 60305f08..a26662ac 100644
--- a/src/dreamcoder_theme/renderers_codex.py
+++ b/src/dreamcoder_theme/renderers_codex.py
@@ -67,7 +67,7 @@ def codex_tmtheme_content(c: dict[str, str]) -> str:
consumer_id="codex_app",
renderer=opencode_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -81,7 +81,7 @@ def codex_tmtheme_content(c: dict[str, str]) -> str:
consumer_id="codex_theme",
renderer=codex_tmtheme_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -95,7 +95,7 @@ def codex_tmtheme_content(c: dict[str, str]) -> str:
consumer_id="bat_theme",
renderer=codex_tmtheme_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_bat_delta.py b/src/dreamcoder_theme/renderers_extra_bat_delta.py
index 74560147..bf4fdb37 100644
--- a/src/dreamcoder_theme/renderers_extra_bat_delta.py
+++ b/src/dreamcoder_theme/renderers_extra_bat_delta.py
@@ -8,10 +8,7 @@
def bat_content(c: dict[str, str]) -> str:
"""Return a Bat theme config snippet with modern defaults."""
mode_name = detect_mode(c)
- # The Night snippet selects the Night TextMate sibling (design §5 row
- # 17): the derived palette name is the deterministic profile signal
- # because the format embeds the theme name.
- theme = f"Dreamcoder-{'Night' if 'Night' in c.get('name', '') else mode_name.title()}"
+ theme = f"Dreamcoder-{mode_name.title()}"
return (
"#!/usr/bin/env bash\n"
"# Sourced snippet: never set shell options here.\n"
@@ -41,11 +38,7 @@ def g(color: str) -> str:
minus_bg = mix(c["error"], bg, 0.85)
hunk_bg = mix(c["muted"], bg, 0.85)
- # Syntax highlighting theme: the Night snippet selects the Night
- # TextMate sibling (design §5 row 18) — the format embeds the name.
- syntax_theme = (
- "Dreamcoder-Night" if "Night" in c.get("name", "") else f"Dreamcoder-{mode.title()}"
- )
+ syntax_theme = f"Dreamcoder-{mode.title()}"
return f"""# ========================================================
# {c["name"]} — Git Delta theme
@@ -110,7 +103,7 @@ def g(color: str) -> str:
consumer_id="bat",
renderer=bat_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -124,7 +117,7 @@ def g(color: str) -> str:
consumer_id="delta",
renderer=delta_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_btop.py b/src/dreamcoder_theme/renderers_extra_btop.py
index 6a2ed0f7..91eded6e 100644
--- a/src/dreamcoder_theme/renderers_extra_btop.py
+++ b/src/dreamcoder_theme/renderers_extra_btop.py
@@ -104,7 +104,7 @@ def btop_content(c: dict[str, str]) -> str:
consumer_id="btop",
renderer=btop_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_firefox.py b/src/dreamcoder_theme/renderers_extra_firefox.py
index c7a78240..cbf524e5 100644
--- a/src/dreamcoder_theme/renderers_extra_firefox.py
+++ b/src/dreamcoder_theme/renderers_extra_firefox.py
@@ -180,7 +180,7 @@ def firefox_content(c: dict[str, str]) -> str:
consumer_id="firefox",
renderer=firefox_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_notify.py b/src/dreamcoder_theme/renderers_extra_notify.py
index 58bb46df..02f8f828 100644
--- a/src/dreamcoder_theme/renderers_extra_notify.py
+++ b/src/dreamcoder_theme/renderers_extra_notify.py
@@ -94,7 +94,7 @@ def cava_content(c: dict[str, str]) -> str:
consumer_id="dunst",
renderer=dunst_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -108,7 +108,7 @@ def cava_content(c: dict[str, str]) -> str:
consumer_id="cava",
renderer=cava_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_nvim.py b/src/dreamcoder_theme/renderers_extra_nvim.py
index 40f01dd4..21232ba2 100644
--- a/src/dreamcoder_theme/renderers_extra_nvim.py
+++ b/src/dreamcoder_theme/renderers_extra_nvim.py
@@ -14,23 +14,21 @@ def nvim_dispatcher_content() -> str:
"""Return a dispatcher that loads the correct variant.
Priority:
- 1. DREAMCODER_THEME_PROFILE env var (night) — resolved BEFORE base mode
- 2. DREAMCODER_THEME_MODE env var (set by `dreamcoder dark` / `dreamcoder light`)
- 3. ~/.cache/dreamcoder/cursor-cli.env (written by apply-theme-mode.sh)
- 4. vim.o.background (Neovim's own mode)
- 5. Fallback: "dark"
+ 1. DREAMCODER_THEME_MODE env var (set by `dreamcoder dark` / `dreamcoder light`)
+ 2. ~/.cache/dreamcoder/cursor-cli.env (written by apply-theme-mode.sh)
+ 3. vim.o.background (Neovim's own mode)
+ 4. Fallback: "dark"
"""
return """-- ========================================================
-- Dreamcoder — auto-detect dispatcher
-- ========================================================
-- Usage: vim.cmd.colorscheme("dreamcoder")
-- Auto-loads the correct variant based on:
--- 1. DREAMCODER_THEME_PROFILE env var (night) — before base mode
--- 2. DREAMCODER_THEME_MODE env var (set by `dreamcoder light`)
--- 3. ~/.cache/dreamcoder/cursor-cli.env (persisted by apply-theme-mode.sh)
--- 4. vim.o.background (Neovim's own setting)
--- 5. Fallback: dark
--- Direct variant access: colorscheme dreamcoder-dark / dreamcoder-light / dreamcoder-night
+-- 1. DREAMCODER_THEME_MODE env var (set by `dreamcoder light`)
+-- 2. ~/.cache/dreamcoder/cursor-cli.env (persisted by apply-theme-mode.sh)
+-- 3. vim.o.background (Neovim's own setting)
+-- 4. Fallback: dark
+-- Direct variant access: colorscheme dreamcoder-dark / dreamcoder-light
-- ========================================================
vim.g.colors_name = "dreamcoder"
@@ -43,29 +41,6 @@ def nvim_dispatcher_content() -> str:
local src = debug.getinfo(1, "S").source:match("@?(.*)")
local theme_dir = src:match("^(.*[/\\\\])") or "."
--- Resolve render profile first (night wins over base mode), then mode.
-local profile = vim.env.DREAMCODER_THEME_PROFILE
-
-if not profile then
- local cache_file = vim.fn.expand("~/.cache/dreamcoder/cursor-cli.env")
- local f = io.open(cache_file, "r")
- if f then
- for line in f:lines() do
- local m = line:match('^export DREAMCODER_THEME_PROFILE="(.-)"')
- if m then
- profile = m
- break
- end
- end
- f:close()
- end
-end
-
-if profile == "night" then
- dofile(theme_dir .. "dreamcoder-night.lua")
- return
-end
-
-- Resolve base mode: env var > cache file > vim.o.background > dark
local mode = vim.env.DREAMCODER_THEME_MODE
@@ -123,10 +98,7 @@ def hl(name: str, **kwargs: Any) -> str:
return f' vim.api.nvim_set_hl(0, "{name}", {{\n{inner}\n }})\n'
invert = c["details"] == "lighter"
- # The derived Night variant reports its own identity (design §5 row 14)
- # so a direct ``colorscheme dreamcoder-night`` stays correct; standard
- # dark/light keep today's names.
- variant_name = "night" if "Night" in c.get("name", "") else ("light" if invert else "dark")
+ variant_name = "light" if invert else "dark"
bg = c["bg"]
normal_bg = bg if invert else "none"
text = c["text"]
@@ -199,7 +171,7 @@ def hl(name: str, **kwargs: Any) -> str:
consumer_id="nvim",
renderer=NvimDispatcherAdapter(),
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.NVIM_DISPATCHER,
diff --git a/src/dreamcoder_theme/renderers_extra_obsidian.py b/src/dreamcoder_theme/renderers_extra_obsidian.py
index baf45e1f..ce7b5e81 100644
--- a/src/dreamcoder_theme/renderers_extra_obsidian.py
+++ b/src/dreamcoder_theme/renderers_extra_obsidian.py
@@ -174,7 +174,7 @@ def obsidian_content(c: dict[str, str]) -> str:
consumer_id="obsidian",
renderer=obsidian_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_extra_shell.py b/src/dreamcoder_theme/renderers_extra_shell.py
index 1b2dc6ab..b83a0176 100644
--- a/src/dreamcoder_theme/renderers_extra_shell.py
+++ b/src/dreamcoder_theme/renderers_extra_shell.py
@@ -236,7 +236,7 @@ def fzf_content(c: dict[str, str]) -> str:
consumer_id="zsh_syntax",
renderer=zsh_syntax_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -250,7 +250,7 @@ def fzf_content(c: dict[str, str]) -> str:
consumer_id="ls_colors",
renderer=ls_colors_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -264,7 +264,7 @@ def fzf_content(c: dict[str, str]) -> str:
consumer_id="fzf",
renderer=fzf_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_ghostty_warp.py b/src/dreamcoder_theme/renderers_ghostty_warp.py
index a16cf575..013599e4 100644
--- a/src/dreamcoder_theme/renderers_ghostty_warp.py
+++ b/src/dreamcoder_theme/renderers_ghostty_warp.py
@@ -73,7 +73,7 @@ def warp_content(c: dict[str, str]) -> str:
consumer_id="ghostty",
renderer=ghostty_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -87,7 +87,7 @@ def warp_content(c: dict[str, str]) -> str:
consumer_id="warp",
renderer=warp_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_herdr.py b/src/dreamcoder_theme/renderers_herdr.py
index 708d725a..8786e7ad 100644
--- a/src/dreamcoder_theme/renderers_herdr.py
+++ b/src/dreamcoder_theme/renderers_herdr.py
@@ -49,11 +49,11 @@ class HerdrModeError(Exception):
def herdr_content(profile: HerdrProfile, mode: str, palette: dict[str, str]) -> str:
- """Render one static Light, Dark, or Night variant without touching active configuration."""
+ """Render one static Light or Dark variant without touching active configuration."""
if not profile.is_complete:
raise HerdrContractUnavailableError("Herdr color rendering requires a complete profile")
- if mode not in {"dark", "light", "night"}:
- raise HerdrModeError("Herdr supports only dark, light, and night variants")
+ if mode not in {"dark", "light"}:
+ raise HerdrModeError("Herdr supports only dark and light variants")
evidence = profile.evidence
if "name" not in evidence.allowed_theme_fields:
@@ -146,7 +146,7 @@ def herdr_ui_field_rhs() -> dict[str, str]:
consumer_id="herdr",
renderer=VersionedHerdrAdapter(_REPRESENTATIVE_PROFILE, "dark"),
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="repository",
sync=SyncDefinition(
renderer=RendererStrategy.VERSIONED_HERDR,
diff --git a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
index 0b6257e8..9baa6afe 100644
--- a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
+++ b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
@@ -360,7 +360,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="hyprland",
renderer=hypr_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -374,7 +374,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="hypr_colors_lua",
renderer=hypr_colors_lua_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
# Verified against live sync.py (not the prior "active"/NO_VARIANTS/
# ACTIVE_ONLY_BRIDGE declaration): sync_active_targets() writes the
# live ~/.config/hypr/colors.lua active file via plain
@@ -397,7 +397,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="hypr_colors_conf",
renderer=hypr_colors_conf_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
# See hypr_colors_lua comment above — identical verified shape:
# live active write (sync_active_targets -> paths.hypr_colors_conf)
# plus repo MODE_VARIANTS snippets (sync_repo_snippets), both via
@@ -415,7 +415,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="waybar",
renderer=waybar_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -429,7 +429,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="waybar_matugen",
renderer=waybar_matugen_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -443,7 +443,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="rofi",
renderer=rofi_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -457,7 +457,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
consumer_id="rofi_matugen",
renderer=rofi_matugen_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_kitty.py b/src/dreamcoder_theme/renderers_kitty.py
index 045d4526..6dbd2c68 100644
--- a/src/dreamcoder_theme/renderers_kitty.py
+++ b/src/dreamcoder_theme/renderers_kitty.py
@@ -139,7 +139,7 @@ def kitty_ui_content(c: dict[str, str]) -> str:
consumer_id="kitty",
renderer=kitty_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
@@ -153,7 +153,7 @@ def kitty_ui_content(c: dict[str, str]) -> str:
consumer_id="kitty_ui",
renderer=kitty_ui_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_lazygit.py b/src/dreamcoder_theme/renderers_lazygit.py
index c1aecf44..72fd83ba 100644
--- a/src/dreamcoder_theme/renderers_lazygit.py
+++ b/src/dreamcoder_theme/renderers_lazygit.py
@@ -1,6 +1,6 @@
"""Lazygit theme renderer for Dreamcoder.
-Generates full ``config.yml`` files (active + ``config.{dark,light,night}.yml``
+Generates full ``config.yml`` files (active + ``config.{dark,light}.yml``
variants) from the canonical palette tokens. Non-color behavior is preserved
verbatim from the previous static config; only colors are derived from tokens,
so there is no duplicated per-mode hardcoded palette anywhere.
@@ -16,8 +16,7 @@ def _delta_syntax_theme(c: dict[str, str]) -> str:
The Dreamcoder ``*-light`` TextMate theme does not exist, so Lazygit's
embedded Delta renderer must use installed valid Catppuccin themes:
- Latte for light, Mocha for dark and the Night derivation (ADR-003 keeps
- Night dark semantics, so ``details`` stays ``darker``).
+ Latte for light and Mocha for dark.
"""
return "Catppuccin Latte" if detect_mode(c) == "light" else "Catppuccin Mocha"
@@ -118,7 +117,7 @@ def lazygit_content(c: dict[str, str]) -> str:
consumer_id="lazygit",
renderer=lazygit_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_opencode.py b/src/dreamcoder_theme/renderers_opencode.py
index f7b8c67b..5412534e 100644
--- a/src/dreamcoder_theme/renderers_opencode.py
+++ b/src/dreamcoder_theme/renderers_opencode.py
@@ -232,7 +232,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
consumer_id="opencode",
renderer=TransparentOpenCodeAdapter(),
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active",
sync=SyncDefinition(
renderer=RendererStrategy.TRANSPARENT_OPENCODE,
diff --git a/src/dreamcoder_theme/renderers_pi.py b/src/dreamcoder_theme/renderers_pi.py
index 57a86eea..a519debb 100644
--- a/src/dreamcoder_theme/renderers_pi.py
+++ b/src/dreamcoder_theme/renderers_pi.py
@@ -104,7 +104,7 @@ def pi_theme_content(c: dict[str, str]) -> str:
consumer_id="pi_theme",
renderer=pi_theme_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_readme.py b/src/dreamcoder_theme/renderers_readme.py
index 6283347d..753712ba 100644
--- a/src/dreamcoder_theme/renderers_readme.py
+++ b/src/dreamcoder_theme/renderers_readme.py
@@ -12,7 +12,6 @@ def readme_content() -> str:
- `tokens.schema.json`: machine-readable token contract.
- `*-dark.*`: Dreamcoder Dark mode with near-black bg, cool steel-blue accents, and muted slate surfaces.
- `*-light.*`: paper-like daytime mode with flat surface ladder and distinct semantic tokens.
-- `*-night.*`: Night/Dim rendering profile — deterministic brightness/saturation-reduced derivation of Dreamcoder Dark (user-controlled display profile, no medical claims).
## Color-only Snippets (Hooks)
Hook these into your app configs after ML4W/Gentleman files:
diff --git a/src/dreamcoder_theme/renderers_starship.py b/src/dreamcoder_theme/renderers_starship.py
index 0a94cdfb..619d8bd0 100644
--- a/src/dreamcoder_theme/renderers_starship.py
+++ b/src/dreamcoder_theme/renderers_starship.py
@@ -21,11 +21,7 @@ def starship_content(c: dict[str, str]) -> str:
diag = c["diagnostic"]
lavender_col = c["lavender"]
mauve_col = c["mauve"]
- # The named Night sibling must never reference a standard-dark palette
- # section (design §5 row 11): the derived name is the deterministic
- # profile signal because the format embeds the palette identity.
- is_night = "Night" in c.get("name", "")
- palette_id = "dreamcoder-night" if is_night else "dreamcoder"
+ palette_id = "dreamcoder"
return f'''# ========================================================
# {c["name"]} — Starship prompt
@@ -191,7 +187,7 @@ def starship_content(c: dict[str, str]) -> str:
consumer_id="starship",
renderer=starship_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_tmux.py b/src/dreamcoder_theme/renderers_tmux.py
index b97feb6e..80afb37b 100644
--- a/src/dreamcoder_theme/renderers_tmux.py
+++ b/src/dreamcoder_theme/renderers_tmux.py
@@ -93,7 +93,7 @@ def tmux_content(c: dict[str, str]) -> str:
consumer_id="tmux",
renderer=tmux_content,
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.DIRECT_CONTENT,
diff --git a/src/dreamcoder_theme/renderers_zellij.py b/src/dreamcoder_theme/renderers_zellij.py
index 52a031e6..2d8c50f7 100644
--- a/src/dreamcoder_theme/renderers_zellij.py
+++ b/src/dreamcoder_theme/renderers_zellij.py
@@ -13,8 +13,8 @@ def zellij_content(c: dict[str, str], theme_name: str) -> str:
"""Render a Zellij KDL theme block from a palette dictionary.
``theme_name`` is the named theme key emitted inside ``themes { }`` (for
- example ``dreamcoder-night``); colors are derived from the (transformed)
- palette, never hand-tuned (ADR-003/ADR-004).
+ example ``dreamcoder-dark``); colors are derived from the palette, never
+ hand-tuned (ADR-004).
"""
return f"""// {c.get("name", "Dreamcoder")} — Zellij theme ({theme_name})
// Auto-generated from tokens.json — do not edit manually
@@ -73,7 +73,7 @@ def zellij_content(c: dict[str, str], theme_name: str) -> str:
consumer_id="zellij",
renderer=NamedZellijAdapter("dreamcoder"),
contract_version=1,
- modes=frozenset({"dark", "light", "night"}),
+ modes=frozenset({"dark", "light"}),
output_kind="active-and-repository",
sync=SyncDefinition(
renderer=RendererStrategy.NAMED_ZELLIJ,
diff --git a/src/dreamcoder_theme/repair_engine.py b/src/dreamcoder_theme/repair_engine.py
index a777c98b..a2bcb91d 100644
--- a/src/dreamcoder_theme/repair_engine.py
+++ b/src/dreamcoder_theme/repair_engine.py
@@ -107,10 +107,10 @@ def repair_plan() -> dict[str, Any]:
"command": "./scripts/dreamcoder installer plan --json",
}
)
- elif check.name == "day/night timer":
+ elif check.name == "light/dark timer":
actions.append(
{
- "id": "enable-day-night-timer",
+ "id": "enable-light-dark-timer",
"check": check.name,
"safe": False,
"target": "dreamcoder-theme-auto.timer",
diff --git a/src/dreamcoder_theme/settings.py b/src/dreamcoder_theme/settings.py
index fd2c7545..3814f4fd 100644
--- a/src/dreamcoder_theme/settings.py
+++ b/src/dreamcoder_theme/settings.py
@@ -7,8 +7,6 @@
from dataclasses import dataclass, field
from pathlib import Path
-from .settings_store import settings_get
-
ROOT = Path(__file__).resolve().parents[2]
PI_THEME_SCHEMA = (
"https://raw.githubusercontent.com/earendil-works/pi/main/"
@@ -108,61 +106,6 @@ def theme_mode() -> str:
return mode
-VALID_RENDER_PROFILES = frozenset({"standard", "night"})
-
-
-def render_profile() -> str:
- """Resolve the effective rendering profile (design §3, R6).
-
- Precedence:
- 1. ``DREAMCODER_THEME_PROFILE`` — process-only override for isolated
- generation and tests; it never mutates the persisted setting.
- 2. Persisted ``settings_get("theme.render_profile")``.
- 3. Schema default ``standard`` when absent.
-
- Invalid values fail closed instead of being interpreted as a runtime
- profile (R6 scenario "Invalid profile value is rejected").
- """
- env_profile = os.environ.get("DREAMCODER_THEME_PROFILE")
- if env_profile is not None:
- profile = env_profile.lower()
- if profile not in VALID_RENDER_PROFILES:
- raise SystemExit(
- f"DREAMCODER_THEME_PROFILE must be 'standard' or 'night' (got '{env_profile}')"
- )
- return profile
- persisted = settings_get("theme.render_profile")
- if persisted is not None:
- if not isinstance(persisted, str) or persisted not in VALID_RENDER_PROFILES:
- raise SystemExit(
- f"persisted theme.render_profile={persisted!r} is invalid; "
- "expected 'standard' or 'night'"
- )
- return persisted
- return "standard"
-
-
-def effective_base_mode(mode: str | None = None, profile: str | None = None) -> str:
- """Resolve the base mode together with the render profile (ADR-003).
-
- Enforces ``profile == night -> mode == dark``: Night always derives from
- the Dreamcoder Dark base. A conflicting invocation such as
- ``DREAMCODER_THEME_MODE=light`` + ``DREAMCODER_THEME_PROFILE=night`` fails
- with an actionable error instead of choosing Dusk or silently coercing
- output (design §3). ``theme_mode()`` remains the Light/Dark base authority
- and never accepts Night as a base mode.
- """
- base = theme_mode() if mode is None else mode
- resolved_profile = render_profile() if profile is None else profile
- if resolved_profile == "night" and base != "dark":
- raise SystemExit(
- f"render profile 'night' requires base mode 'dark' (got '{base}'): "
- "Night always derives from the Dreamcoder Dark base; "
- "set DREAMCODER_THEME_MODE=dark or run the 'night' activation."
- )
- return base
-
-
def theme_paths() -> ThemePaths:
config_home = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
data_home = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share"))
diff --git a/src/dreamcoder_theme/settings_store.py b/src/dreamcoder_theme/settings_store.py
index 2c418c2f..166ae294 100644
--- a/src/dreamcoder_theme/settings_store.py
+++ b/src/dreamcoder_theme/settings_store.py
@@ -24,12 +24,6 @@
"default": "balanced",
"description": "Active motion preset.",
},
- "theme.render_profile": {
- "type": "string",
- "enum": ["standard", "night"],
- "default": "standard",
- "description": "Theme rendering profile (standard or night).",
- },
}
diff --git a/src/dreamcoder_theme/sync.py b/src/dreamcoder_theme/sync.py
index 69e9031c..170d9958 100644
--- a/src/dreamcoder_theme/sync.py
+++ b/src/dreamcoder_theme/sync.py
@@ -13,9 +13,7 @@
from .palette import (
adaptive_palette,
load_guardrails,
- load_render_profile,
load_variants,
- night_palette,
validate_palette,
)
from .palette_tokens import VARIANTS as DEFAULT_VARIANTS
@@ -57,9 +55,7 @@
from .renderers_orca import sync_orca_theme
from .settings import (
ROOT,
- VALID_RENDER_PROFILES,
adaptive_enabled,
- render_profile,
theme_mode,
theme_paths,
write_repo_enabled,
@@ -82,7 +78,7 @@
class ThemeGateError(RuntimeError):
"""Raised by ``prepare()`` when the dual gate or coverage assertion fails.
- Carries the structured failure list (metric/profile/pair/measured/
+ Carries the structured failure list (metric/mode/pair/measured/
threshold for WCAG+APCA, or the coverage problems) and renders the
fail-closed message main() and the CLI handler surface. It is raised
before any writer, selector, or settings mutation (R4/R8, design §4/§8).
@@ -100,31 +96,28 @@ class PreparedSync:
"""Immutable result of validation-first preparation (design §4, ADR-004).
``prepare()`` produces this with ZERO filesystem writes: the validated
- active palette, the dark/light/night render-variant map, the frozen
+ active palette, the dark/light render-variant map, the frozen
33-consumer coverage declaration, and the in-memory render of every
coverage consumer. The caller (``main()`` or the CLI activation
transaction) owns the commit.
"""
mode: str
- profile: str
active: dict[str, str]
variants: dict[str, dict[str, str]]
coverage: tuple[CoverageRow, ...]
render_plan: Mapping[str, str]
-def sync_active_targets(
- paths: Any, active: dict[str, str], mode: str, profile: str = "standard"
-) -> dict[str, bool]:
+def sync_active_targets(paths: Any, active: dict[str, str], mode: str) -> dict[str, bool]:
return {
"kitty": write_if_changed(paths.kitty, kitty_content(active)),
"kitty_ui": write_if_changed(paths.kitty_ui, kitty_ui_content(active)),
"kitty_config": ensure_kitty_ui_include(paths.kitty_config),
"ghostty": write_if_changed(paths.ghostty, ghostty_content(active)),
- "ghostty_config": update_ghostty_theme(paths.ghostty_config, mode, profile),
+ "ghostty_config": update_ghostty_theme(paths.ghostty_config, mode),
"warp": write_if_changed(paths.warp, warp_content(active)),
- "warp_settings": update_warp_settings(paths.warp_settings, mode, profile),
+ "warp_settings": update_warp_settings(paths.warp_settings, mode),
"opencode": write_if_changed(
paths.opencode, opencode_content(active, transparent_background=True)
),
@@ -140,7 +133,7 @@ def sync_active_targets(
"starship": write_if_changed(paths.starship, starship_content(active)),
"tmux": write_if_changed(paths.tmux, tmux_content(active)),
"lazygit": write_if_changed(paths.lazygit, lazygit_content(active)),
- "zellij": update_zellij_config(paths.zellij_config, mode, profile),
+ "zellij": update_zellij_config(paths.zellij_config, mode),
# New targets
"nvim": write_if_changed(paths.nvim, nvim_dispatcher_content()),
"zsh_syntax": write_if_changed(paths.zsh_syntax, zsh_syntax_content(active)),
@@ -172,26 +165,27 @@ def sync_active_targets(
# hyprland, waybar, rofi, nvim, and opencode-transparent
# stay as explicit calls below the loop.
# ------------------------------------------------------------------
-D = {"dark": "dark", "light": "light", "night": "night"}
+D = {"dark": "dark", "light": "light"}
# ------------------------------------------------------------------
-# Exact 33-consumer Night coverage declaration (design §5 matrix).
+# Exact 33-consumer coverage declaration (design §5 matrix).
# ------------------------------------------------------------------
-# ``night_artifact`` is the deterministic Night output path (relative to
-# ROOT, POSIX separators) for repo-generation rows. Active-only matugen
-# bridges carry an ``active:`` marker because their Night delivery is
-# the live ``colors.css``/``colors.rasi`` file, not a repository artifact.
-# ``source`` records which sync branch owns the row (registry loop, explicit
+# ``artifact`` is the deterministic Dark output path (relative to ROOT, POSIX
+# separators) for repo-generation rows; the Light sibling follows the same
+# naming with ``light``/``Light``. Active-only matugen bridges carry an
+# ``active:`` marker because their delivery is the live
+# ``colors.css``/``colors.rasi`` file, not a repository artifact. ``source``
+# records which sync branch owns the row (registry loop, explicit
# sync_repo_snippets() branch, or Herdr) and is the bijection-test hook.
class CoverageRow(NamedTuple):
- """One row of the 33-consumer Night coverage contract (design §5)."""
+ """One row of the 33-consumer coverage contract (design §5)."""
consumer_id: str
klass: str
writer: str
- night_artifact: str
+ artifact: str
selection_strategy: str
source: str
@@ -201,176 +195,176 @@ class CoverageRow(NamedTuple):
"kitty",
"variant file + active-selected",
"write_variant_files + write_if_changed; kitty_content",
- "DreamcoderKitty/.config/kitty/colors-dreamcoder-night.conf",
- "active symlink/file selects or receives Night",
+ "DreamcoderKitty/.config/kitty/colors-dreamcoder-dark.conf",
+ "active symlink/file selects or receives the mode",
"registry",
),
CoverageRow(
"kitty_ui",
"variant file + active-selected",
"write_variant_files + write_if_changed; kitty_ui_content",
- "DreamcoderKitty/.config/kitty/dreamcoder-ui-night.conf",
- "stable dreamcoder-ui.conf includes/contains Night",
+ "DreamcoderKitty/.config/kitty/dreamcoder-ui-dark.conf",
+ "stable dreamcoder-ui.conf includes/contains the mode",
"registry",
),
CoverageRow(
"ghostty",
"variant file + active-selected",
"write_variant_files + update_ghostty_theme; ghostty_content",
- "DreamcoderGhostty/.config/ghostty/themes/dreamcoder-night",
- "select theme = dreamcoder-night",
+ "DreamcoderGhostty/.config/ghostty/themes/dreamcoder-dark",
+ "select theme = dreamcoder-dark / dreamcoder",
"registry",
),
CoverageRow(
"warp",
"variant file + active-selected",
"write_variant_files + update_warp_settings; warp_content",
- "DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Night.yaml",
- "active symlink/file selects it; dark opacity/blur semantics",
+ "DreamcoderWarp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml",
+ "active symlink/file selects it; mode-aware opacity/blur",
"registry",
),
CoverageRow(
"starship",
"variant file + active-selected",
"write_variant_files + write_if_changed; starship_content",
- "DreamcoderShell/.config/starship-night.toml",
- "palette section never standard-dark ([palettes.dreamcoder-night])",
+ "DreamcoderShell/.config/starship-dark.toml",
+ "palette section [palettes.dreamcoder]",
"registry",
),
CoverageRow(
"codex_app",
"variant file + active-selected",
"write_variant_files + write_if_changed; opencode_content",
- "DreamcoderCodexApp/Dreamcoder-Night.codex-theme.json",
- "stable Dreamcoder.codex-theme.json receives Night",
+ "DreamcoderCodexApp/Dreamcoder-Dark.codex-theme.json",
+ "stable Dreamcoder.codex-theme.json receives the active mode",
"registry",
),
CoverageRow(
"codex_theme",
"variant file + active-selected",
"write_variant_files + write_if_changed; codex_tmtheme_content",
- "DreamcoderCodexCLI/Dreamcoder-Night.tmTheme",
- "stable Dreamcoder.tmTheme receives Night",
+ "DreamcoderCodexCLI/Dreamcoder-Dark.tmTheme",
+ "stable Dreamcoder.tmTheme receives the active mode",
"registry",
),
CoverageRow(
"bat_theme",
"variant file + active-selected",
"write_variant_files + write_if_changed; codex_tmtheme_content",
- "DreamcoderBat/.config/bat/themes/Dreamcoder-Night.tmTheme",
- "stable Dreamcoder.tmTheme receives Night",
+ "DreamcoderBat/.config/bat/themes/Dreamcoder-Dark.tmTheme",
+ "stable Dreamcoder.tmTheme receives the active mode",
"registry",
),
CoverageRow(
"pi_theme",
"variant file + active-selected",
"write_variant_files + write_if_changed; pi_theme_content",
- "DreamcoderPi/.pi/agent/themes/dreamcoder-night.json",
- "stable dreamcoder.json receives/selects Night",
+ "DreamcoderPi/.pi/agent/themes/dreamcoder-dark.json",
+ "stable dreamcoder.json receives/selects the active mode",
"registry",
),
CoverageRow(
"antigravity",
"variant file + active-selected",
"write_variant_files + write_if_changed; antigravity_content",
- "DreamcoderAntigravity/Dreamcoder-Night.json",
- "stable Dreamcoder.json receives Night; classified dark without name detection",
+ "DreamcoderAntigravity/Dreamcoder-Dark.json",
+ "stable Dreamcoder.json pinned to Dark; classified by details",
"registry",
),
CoverageRow(
"tmux",
"variant file + active-selected",
"write_variant_files + write_if_changed; tmux_content",
- "DreamcoderThemes/dreamcoder/tmux-dreamcoder-night.conf",
- "active file receives Night",
+ "DreamcoderThemes/dreamcoder/tmux-dreamcoder-dark.conf",
+ "active file receives the mode",
"registry",
),
CoverageRow(
"lazygit",
"variant file + active-selected",
"write_variant_files + write_if_changed; lazygit_content",
- "DreamcoderLazygit/.config/lazygit/config.night.yml",
- "live ~/.config/lazygit/config.yml symlink selects Night",
+ "DreamcoderLazygit/.config/lazygit/config.dark.yml",
+ "live ~/.config/lazygit/config.yml symlink selects the mode",
"registry",
),
CoverageRow(
"zsh_syntax",
"snippet + active-selected",
"write_variant_files + write_if_changed; zsh_syntax_content",
- "DreamcoderThemes/dreamcoder/zsh-syntax-highlighting-dreamcoder-night.zsh",
- "active sourced file receives/selects Night",
+ "DreamcoderThemes/dreamcoder/zsh-syntax-highlighting-dreamcoder-dark.zsh",
+ "active sourced file receives/selects the mode",
"registry",
),
CoverageRow(
"ls_colors",
"snippet + active-selected",
"write_variant_files + write_if_changed; ls_colors_content",
- "DreamcoderThemes/dreamcoder/ls-colors-dreamcoder-night.sh",
- "active sourced file receives/selects Night",
+ "DreamcoderThemes/dreamcoder/ls-colors-dreamcoder-dark.sh",
+ "active sourced file receives/selects the mode",
"registry",
),
CoverageRow(
"bat",
"snippet + active-selected",
"write_variant_files + write_if_changed; bat_content",
- "DreamcoderThemes/dreamcoder/bat-dreamcoder-night.sh",
- "selects the Night TextMate sibling (BAT_THEME=Dreamcoder-Night)",
+ "DreamcoderThemes/dreamcoder/bat-dreamcoder-dark.sh",
+ "selects the mode TextMate sibling (BAT_THEME=Dreamcoder-Dark)",
"registry",
),
CoverageRow(
"delta",
"snippet + active-selected",
"write_variant_files + write_if_changed; delta_content",
- "DreamcoderThemes/dreamcoder/delta-dreamcoder-night.gitconfig",
- "active include/symlink selects Night; syntax-theme=Dreamcoder-Night",
+ "DreamcoderThemes/dreamcoder/delta-dreamcoder-dark.gitconfig",
+ "active include/symlink selects the mode; syntax-theme=Dreamcoder-Dark",
"registry",
),
CoverageRow(
"fzf",
"snippet + active-selected",
"write_variant_files + write_if_changed; fzf_content",
- "DreamcoderThemes/dreamcoder/fzf-dreamcoder-night.sh",
- "active sourced file receives/selects Night",
+ "DreamcoderThemes/dreamcoder/fzf-dreamcoder-dark.sh",
+ "active sourced file receives/selects the mode",
"registry",
),
CoverageRow(
"btop",
"variant file + active-selected",
"write_variant_files + write_if_changed; btop_content",
- "DreamcoderThemes/dreamcoder/btop-dreamcoder-night.theme",
- "active dreamcoder.theme symlink selects Night",
+ "DreamcoderThemes/dreamcoder/btop-dreamcoder-dark.theme",
+ "active dreamcoder.theme symlink selects the mode",
"registry",
),
CoverageRow(
"dunst",
"snippet + active-selected",
"write_variant_files + write_if_changed; dunst_content",
- "DreamcoderThemes/dreamcoder/dunst-dreamcoder-night.conf",
- "active included file receives/selects Night",
+ "DreamcoderThemes/dreamcoder/dunst-dreamcoder-dark.conf",
+ "active included file receives/selects the mode",
"registry",
),
CoverageRow(
"firefox",
"snippet + active-selected",
"write_variant_files + write_if_changed; firefox_content",
- "DreamcoderThemes/dreamcoder/firefox-dreamcoder-night.css",
- "active userChrome import receives/selects Night",
+ "DreamcoderThemes/dreamcoder/firefox-dreamcoder-dark.css",
+ "active userChrome import receives/selects the mode",
"registry",
),
CoverageRow(
"obsidian",
"snippet + active-selected",
"write_variant_files + write_if_changed; obsidian_content",
- "DreamcoderThemes/dreamcoder/obsidian-dreamcoder-night.css",
- "active snippet receives/selects Night and keeps .theme-dark",
+ "DreamcoderThemes/dreamcoder/obsidian-dreamcoder-dark.css",
+ "active snippet receives/selects the mode",
"registry",
),
CoverageRow(
"cava",
"snippet + active-selected",
"write_variant_files + write_if_changed; cava_content",
- "DreamcoderThemes/dreamcoder/cava-dreamcoder-night.config",
- "active include receives/selects Night",
+ "DreamcoderThemes/dreamcoder/cava-dreamcoder-dark.config",
+ "active include receives/selects the mode",
"registry",
),
CoverageRow(
@@ -378,55 +372,55 @@ class CoverageRow(NamedTuple):
"active-selected",
"write_if_changed; opencode_content(transparent_background=True)",
".opencode/themes/dreamcoder.json",
- "stable theme ID dreamcoder overwritten with Night; no dreamcoder-night.json sibling",
+ "stable theme ID dreamcoder overwritten with the active mode",
"explicit",
),
CoverageRow(
"zellij",
"variant file + active-selected",
"write_if_changed; zellij_content + update_zellij_config",
- "DreamcoderZellij/.config/zellij/dreamcoder-night.kdl",
- 'generate KDL with themes { dreamcoder-night } and select theme "dreamcoder-night"',
+ "DreamcoderZellij/.config/zellij/dreamcoder-dark.kdl",
+ 'generate KDL with themes { dreamcoder-dark } and select theme "dreamcoder-dark"',
"explicit",
),
CoverageRow(
"nvim",
"variant file + active-selected",
"write_variant_files + nvim_dispatcher_content; nvim_content",
- "DreamcoderNvim/.config/nvim/colors/dreamcoder-night.lua",
- "dispatcher resolves DREAMCODER_THEME_PROFILE before base mode",
+ "DreamcoderNvim/.config/nvim/colors/dreamcoder-dark.lua",
+ "dispatcher resolves DREAMCODER_THEME_MODE",
"explicit",
),
CoverageRow(
"hyprland",
"variant file + active-selected",
"write_if_changed; hypr_content",
- "DreamcoderThemes/dreamcoder/hyprland-night.conf",
- "stable hyprland.conf receives Night; shell selector may point to the Night sibling",
+ "DreamcoderThemes/dreamcoder/hyprland-dark.conf",
+ "stable hyprland.conf receives the mode; shell selector may point to a sibling",
"explicit",
),
CoverageRow(
"hypr_colors_lua",
"snippet + active-selected",
"write_variant_files; hypr_colors_lua_content",
- "DreamcoderThemes/dreamcoder/hypr-colors-night.lua",
- "active symlink/file selects Night",
+ "DreamcoderThemes/dreamcoder/hypr-colors-dark.lua",
+ "active symlink/file selects the mode",
"explicit",
),
CoverageRow(
"hypr_colors_conf",
"snippet + active-selected",
"write_variant_files; hypr_colors_conf_content",
- "DreamcoderThemes/dreamcoder/hypr-colors-night.conf",
- "active symlink/file selects Night",
+ "DreamcoderThemes/dreamcoder/hypr-colors-dark.conf",
+ "active symlink/file selects the mode",
"explicit",
),
CoverageRow(
"waybar",
"variant file + active-selected",
"write_if_changed; waybar_content",
- "DreamcoderThemes/dreamcoder/waybar-night.css",
- "stable/selected Waybar CSS receives Night",
+ "DreamcoderThemes/dreamcoder/waybar-dark.css",
+ "stable/selected Waybar CSS receives the mode",
"explicit",
),
CoverageRow(
@@ -434,15 +428,15 @@ class CoverageRow(NamedTuple):
"snippet + active-selected",
"write_if_changed; waybar_matugen_content",
"active:waybar/colors.css",
- "write transformed Night directly; symlink-aware colors-night.css selection",
+ "write the mode directly; symlink-aware colors-{mode}.css selection",
"explicit",
),
CoverageRow(
"rofi",
"variant file + active-selected",
"write_if_changed; rofi_content",
- "DreamcoderThemes/dreamcoder/rofi-night.rasi",
- "stable/selected Rofi theme receives Night",
+ "DreamcoderThemes/dreamcoder/rofi-dark.rasi",
+ "stable/selected Rofi theme receives the mode",
"explicit",
),
CoverageRow(
@@ -450,22 +444,22 @@ class CoverageRow(NamedTuple):
"snippet + active-selected",
"write_if_changed; rofi_matugen_content",
"active:rofi/colors.rasi",
- "write transformed Night directly; symlink-aware colors-night.rasi selection",
+ "write the mode directly; symlink-aware colors-{mode}.rasi selection",
"explicit",
),
CoverageRow(
"herdr",
"variant file",
"sync_herdr_repo_variants + write_if_changed; herdr_content",
- "DreamcoderHerdr/.config/herdr/dreamcoder//config.night.toml",
- "config.night.toml for every complete SUPPORTED_PROFILES entry; repository-only, no live activation",
+ "DreamcoderHerdr/.config/herdr/dreamcoder//config.dark.toml",
+ "config.{dark,light}.toml for every complete SUPPORTED_PROFILES entry; repository-only",
"herdr",
),
)
def validate_coverage_declaration(rows: tuple[CoverageRow, ...] = COVERAGE) -> list[str]:
- """Fail closed on missing, duplicate, or undeclared Night coverage.
+ """Fail closed on missing, duplicate, or undeclared consumer coverage.
Internal consistency gate (R5): the registry loop and the explicit
branches must each be represented by exactly one row, with no duplicate
@@ -481,13 +475,13 @@ def validate_coverage_declaration(rows: tuple[CoverageRow, ...] = COVERAGE) -> l
if len(rows) != 33:
problems.append(f"coverage declares {len(rows)} rows, expected exactly 33")
- registry_night = {
- (base / names["night"]).relative_to(ROOT).as_posix()
+ registry_dark = {
+ (base / names["dark"]).relative_to(ROOT).as_posix()
for base, names, _builder, _active in VARIANT_REGISTRY
}
- declared_registry = {row.night_artifact for row in rows if row.source == "registry"}
- missing = sorted(registry_night - declared_registry)
- extra = sorted(declared_registry - registry_night)
+ declared_registry = {row.artifact for row in rows if row.source == "registry"}
+ missing = sorted(registry_dark - declared_registry)
+ extra = sorted(declared_registry - registry_dark)
if missing or extra:
problems.append(f"registry coverage mismatch: undeclared={missing}, unregistered={extra}")
@@ -652,9 +646,6 @@ def sync_herdr_repo_variants(
"""Generate managed repository variants for every supported profile.
Repository variants only; never select or touch a live configuration.
- ``config.night.toml`` is emitted only when the caller supplies a night
- palette, so standard dark/light runs keep today's exact behavior (R5,
- design §5 row 32).
"""
changes: list[bool] = []
for profile in profiles:
@@ -671,12 +662,6 @@ def sync_herdr_repo_variants(
base / "config.light.toml", herdr_content(profile, "light", variants["light"])
)
)
- if "night" in variants:
- changes.append(
- write_if_changed(
- base / "config.night.toml", herdr_content(profile, "night", variants["night"])
- )
- )
return changes
@@ -686,7 +671,7 @@ def write_active_repo_file(path: Path, content: str) -> bool:
Unlike a live ~/.config target, this path must always be an
independent file: an external mode selector, prior manual edit, or a
stale artifact from an older sync can leave it as a symlink to one of
- its own -dark/-light/-night siblings, and write_if_changed follows
+ its own -dark/-light siblings, and write_if_changed follows
symlinks — so writing the current active mode's content here would
silently overwrite whichever sibling the symlink happened to point
to (exactly the DreamcoderKitty/dreamcoder-ui.conf corruption fixed
@@ -721,7 +706,7 @@ def sync_repo_snippets(variants: dict[str, dict[str, str]], active: dict[str, st
)
# Kitty UI active file (variants handled by VARIANT_REGISTRY above).
- # This path can be left as a symlink to one of the -dark/-light/-night
+ # This path can be left as a symlink to one of the -dark/-light
# siblings by an external mode selector; write_if_changed would follow
# that symlink and overwrite the sibling's own content instead of this
# file's, so unlink it first to guarantee an independent write target.
@@ -751,12 +736,6 @@ def sync_repo_snippets(variants: dict[str, dict[str, str]], active: dict[str, st
hypr_content(variants["light"]),
)
)
- repo_changes.append(
- write_active_repo_file(
- ROOT / "DreamcoderThemes/dreamcoder/hyprland-night.conf",
- hypr_content(variants["night"]),
- )
- )
repo_changes += write_variant_files(
ROOT / "DreamcoderThemes/dreamcoder",
{k: f"hypr-colors-{v}.lua" for k, v in D.items()},
@@ -783,12 +762,6 @@ def sync_repo_snippets(variants: dict[str, dict[str, str]], active: dict[str, st
waybar_content(variants["light"]),
)
)
- repo_changes.append(
- write_active_repo_file(
- ROOT / "DreamcoderThemes/dreamcoder/waybar-night.css",
- waybar_content(variants["night"]),
- )
- )
# Rofi — per-mode + active
repo_changes.append(
@@ -803,12 +776,6 @@ def sync_repo_snippets(variants: dict[str, dict[str, str]], active: dict[str, st
rofi_content(variants["light"]),
)
)
- repo_changes.append(
- write_active_repo_file(
- ROOT / "DreamcoderThemes/dreamcoder/rofi-night.rasi",
- rofi_content(variants["night"]),
- )
- )
# Desktop/WM active files (no suffix — tracks current mode)
repo_changes.append(
@@ -881,12 +848,6 @@ def sync_repo_snippets(variants: dict[str, dict[str, str]], active: dict[str, st
zellij_content(variants["light"], "dreamcoder-light"),
)
)
- repo_changes.append(
- write_active_repo_file(
- ROOT / "DreamcoderZellij/.config/zellij/dreamcoder-night.kdl",
- zellij_content(variants["night"], "dreamcoder-night"),
- )
- )
# Herdr repository variants are intentionally separate from live selection.
repo_changes += sync_herdr_repo_variants(variants)
@@ -959,28 +920,16 @@ def print_summary(
print(f"Repo variant/snippet changes: {sum(repo_changes)}")
-def _generation_profile() -> str:
- """Resolve the repo-generation render profile for this invocation.
-
- Task 4.3 replaced the Phase-3 env-only hook with the persisted
- ``render_profile()`` resolver: ``DREAMCODER_THEME_PROFILE`` (process-only,
- never mutates) -> persisted ``theme.render_profile`` -> schema default
- ``standard`` (design §3).
- """
- return render_profile()
-
-
def render_coverage_plan(
paths: Any,
active: dict[str, str],
mode: str,
- profile: str,
variants: dict[str, dict[str, str]],
) -> dict[str, str]:
- """Render content for every one of the 32 coverage consumers in memory.
+ """Render content for every one of the 33 coverage consumers in memory.
Pure rendering: no writer, selector, or filesystem mutation (R5, ADR-004).
- This is the preparation boundary's in-memory 32-target render — the subject
+ This is the preparation boundary's in-memory 33-target render — the subject
of the coverage assertion and the render-variant plan the activation
transaction commits. Renderers still receive only ``dict[str, str]``.
"""
@@ -1014,57 +963,39 @@ def render_coverage_plan(
"waybar_matugen": waybar_matugen_content(active),
"rofi": rofi_content(active),
"rofi_matugen": rofi_matugen_content(active),
- # Named-profile selector consumers (design §5 rows 13/6/10/32): the
- # selector line and the repo-only artifacts are rendered here too.
- "zellij": (
- 'theme "dreamcoder-night"\n' if profile == "night" else f'theme "dreamcoder-{mode}"\n'
- ),
- "codex_app": opencode_content(variants["night"]),
- "antigravity": antigravity_content(variants["night"]),
+ # Selector and repository-only consumers (design §5 rows 13/6/10/32),
+ # rendered exactly as sync_repo_snippets() writes them: the Codex app
+ # active file follows the active palette, Antigravity's active file is
+ # pinned to Dark, and Herdr renders the base mode's variant.
+ "zellij": f'theme "dreamcoder-{mode}"\n',
+ "codex_app": opencode_content(active),
+ "antigravity": antigravity_content(variants["dark"]),
}
complete = next((p for p in SUPPORTED_PROFILES if p is not None and p.is_complete), None)
- plan["herdr"] = herdr_content(complete, "night", variants["night"]) if complete else ""
+ plan["herdr"] = herdr_content(complete, mode, variants[mode]) if complete else ""
return plan
-def prepare(base: str, profile: str) -> PreparedSync:
+def prepare(base: str) -> PreparedSync:
"""Validate-first preparation boundary (design §4, ADR-004; task 5.1).
- Loads canonical variants/guardrails/profile parameters, resolves the
- base+profile pair, adapts, transforms (Night), validates the final palette
- with the independent WCAG 2.2 + APCA dual gate, renders all 33 coverage
- consumers in memory, and asserts the coverage declaration — with ZERO
- filesystem writes. ``main()`` and the CLI activation transaction commit the
- returned immutable plan; a failed gate raises ``ThemeGateError`` before any
- writer, selector, or settings mutation (R4/R8).
+ Loads canonical variants/guardrails, adapts the base palette, validates
+ it with the independent WCAG 2.2 + APCA dual gate, renders all 33
+ coverage consumers in memory, and asserts the coverage declaration —
+ with ZERO filesystem writes. ``main()`` and the CLI activation
+ transaction commit the returned immutable plan; a failed gate raises
+ ``ThemeGateError`` before any writer, selector, or settings mutation
+ (R4/R8).
"""
if base not in {"light", "dark"}:
raise SystemExit(f"base mode must be 'light' or 'dark' (got '{base}')")
- if profile not in VALID_RENDER_PROFILES:
- raise SystemExit(f"render profile must be 'standard' or 'night' (got '{profile}')")
- if profile == "night" and base != "dark":
- raise SystemExit(
- f"render profile 'night' requires base mode 'dark' (got '{base}'): "
- "Night always derives from the Dreamcoder Dark base (ADR-003)."
- )
paths = theme_paths()
guardrails = load_guardrails(paths.tokens_file)
- params = load_render_profile(paths.tokens_file)
variants = load_variants(DEFAULT_VARIANTS, paths.tokens_file)
- # Night is a derived render variant (ADR-003): canonical dark + the
- # deterministic transform. Registry and explicit branches consume it through
- # the same dict[str, str] renderer shape (ADR-004).
- variants["night"] = night_palette(dict(variants["dark"]), params, guardrails)
-
- if profile == "night":
- adapted = adaptive_palette(variants["dark"], "dark", paths.wallpaper, adaptive_enabled())
- active = night_palette(adapted, params, guardrails)
- else:
- adapted = adaptive_palette(variants[base], base, paths.wallpaper, adaptive_enabled())
- active = adapted
-
- gate_errors = validate_palette(active, guardrails, profile=profile, mode=base)
+ active = adaptive_palette(variants[base], base, paths.wallpaper, adaptive_enabled())
+
+ gate_errors = validate_palette(active, guardrails, mode=base)
if gate_errors:
raise ThemeGateError(gate_errors)
@@ -1072,7 +1003,7 @@ def prepare(base: str, profile: str) -> PreparedSync:
if coverage_problems:
raise ThemeGateError(coverage_problems, kind="Coverage gate failed")
- render_plan = render_coverage_plan(paths, active, base, profile, variants)
+ render_plan = render_coverage_plan(paths, active, base, variants)
missing = [row.consumer_id for row in COVERAGE if row.consumer_id not in render_plan]
if missing:
raise ThemeGateError(
@@ -1082,7 +1013,6 @@ def prepare(base: str, profile: str) -> PreparedSync:
return PreparedSync(
mode=base,
- profile=profile,
active=active,
variants=variants,
coverage=COVERAGE,
@@ -1095,33 +1025,18 @@ def main() -> None:
if gen.is_file():
subprocess.run([sys.executable, str(gen)], check=True)
paths = theme_paths()
- profile = _generation_profile()
- # Night always resolves the Dreamcoder Dark base; otherwise the
- # Light/Dark base comes from theme_mode() (unchanged responsibility).
- base = "dark" if profile == "night" else theme_mode()
+ base = theme_mode()
# Validation-first preparation (R4, design §4): the final palette must pass
# the independent WCAG 2.2 + APCA dual gate and the 33-consumer coverage
# assertion BEFORE any writer or selector runs. A failed gate exits non-zero
- # with zero writes and no profile/settings mutation.
+ # with zero writes and no settings mutation.
try:
- prepared = prepare(base, profile)
+ prepared = prepare(base)
except ThemeGateError as exc:
raise SystemExit(str(exc)) from None
- if profile == "night":
- # Repository-only Night generation: the 32-target coverage is produced
- # through sync_repo_snippets(); live active paths and the active bat
- # theme dir are left untouched (CLI activation owns them, Phase 5).
- repo_changes = (
- sync_repo_snippets(prepared.variants, prepared.active) if write_repo_enabled() else []
- )
- changed: dict[str, bool] = {}
- print_summary(prepared.mode, paths, changed, repo_changes)
- print("Night repository generation only — active outputs untouched (PR3)")
- return
-
- changed = sync_active_targets(paths, prepared.active, prepared.mode, prepared.profile)
+ changed = sync_active_targets(paths, prepared.active, prepared.mode)
bat_variant_changes = sync_bat_theme_variants(paths, prepared.variants)
repo_changes = (
sync_repo_snippets(prepared.variants, prepared.active) if write_repo_enabled() else []
@@ -1132,9 +1047,9 @@ def main() -> None:
raise SystemExit(f"Generated Starship config is invalid: {paths.starship}")
print_summary(prepared.mode, paths, changed, repo_changes)
- # Best-effort, outside the 33-consumer coverage contract: Orca has no
- # Night profile of its own and lives entirely in the user's live
- # settings file, so a failure here must never abort the rest of sync.
+ # Best-effort, outside the 33-consumer coverage contract: Orca lives
+ # entirely in the user's live settings file, so a failure here must
+ # never abort the rest of sync.
try:
orca_status = sync_orca_theme(prepared.active)
except OSError as exc:
diff --git a/src/dreamcoder_theme/targets.py b/src/dreamcoder_theme/targets.py
index 2c5af5cc..549a7a5f 100644
--- a/src/dreamcoder_theme/targets.py
+++ b/src/dreamcoder_theme/targets.py
@@ -134,9 +134,7 @@ def _validate_records(records: list[Any]) -> None:
if "dusk" in modes:
raise ManifestError(f"dusk is not a runtime render mode: {target_id}")
if modes != set(ALL_RENDER_VARIANTS):
- raise ManifestError(
- f"render variants must be dark, light and derived night: {target_id}"
- )
+ raise ManifestError(f"render variants must be dark and light: {target_id}")
for output in render["repository_outputs"].values():
if output in outputs:
raise ManifestError(f"duplicate repository output: {output}")
diff --git a/src/dreamcoder_theme/writers.py b/src/dreamcoder_theme/writers.py
index cf527ae5..6b8b48cc 100644
--- a/src/dreamcoder_theme/writers.py
+++ b/src/dreamcoder_theme/writers.py
@@ -111,22 +111,16 @@ def ensure_kitty_ui_include(path: Path) -> bool:
return True
-def update_ghostty_theme(path: Path, mode: str, profile: str = "standard") -> bool:
+def update_ghostty_theme(path: Path, mode: str) -> bool:
"""Update Ghostty config to use the correct theme name, opacity, and blur.
- Profile-aware selector (design §6): Night maps to ``dreamcoder-night``;
- the legacy ``dreamcoder`` name is retained only for standard light;
- standard dark keeps ``dreamcoder-dark``.
+ The legacy ``dreamcoder`` name is retained only for light; dark keeps
+ ``dreamcoder-dark``.
"""
if not path.exists():
return False
content = path.read_text()
- if profile == "night":
- theme_name = "dreamcoder-night"
- elif mode != "light":
- theme_name = "dreamcoder-dark"
- else:
- theme_name = "dreamcoder"
+ theme_name = "dreamcoder-dark" if mode != "light" else "dreamcoder"
changed = False
@@ -144,29 +138,12 @@ def update_ghostty_theme(path: Path, mode: str, profile: str = "standard") -> bo
return write_if_changed(path, content) if changed else False
-def update_zellij_config(
- path: Path, mode: str, profile: str = "standard", kdl_ready: bool = True
-) -> bool:
- """Patch Zellij config.kdl to select the theme for base mode + profile.
-
- Night writes ``theme "dreamcoder-night"`` only when its KDL artifact
- exists in the prepared plan (design §6): passing ``kdl_ready=False``
- fails closed rather than pointing at a missing theme (no silent
- standard-dark substitution, R5).
- """
+def update_zellij_config(path: Path, mode: str) -> bool:
+ """Patch Zellij config.kdl to select ``theme "dreamcoder-{mode}"``."""
if not path.exists():
return False
content = path.read_text()
- if profile == "night":
- theme_name = "dreamcoder-night"
- if not kdl_ready:
- raise ValueError(
- 'cannot select theme "dreamcoder-night": its KDL artifact is '
- "not present in the prepared plan (fail closed, no standard-"
- "dark substitution)"
- )
- else:
- theme_name = f"dreamcoder-{mode}"
+ theme_name = f"dreamcoder-{mode}"
new_line = f'theme "{theme_name}"'
pattern = re.compile(r'^\s*theme\s+".*?"\s*$', re.MULTILINE)
@@ -180,14 +157,9 @@ def update_zellij_config(
return write_if_changed(path, content)
-def update_warp_settings(path: Path, mode: str, profile: str = "standard") -> bool:
- """Patch Warp settings.toml with mode-aware opacity/blur for glass coherence.
-
- Appearance class resolves from base/profile (design §6): Night keeps the
- dark opacity/blur behavior and never enters the light branch.
- """
- dark_appearance = mode == "dark" or profile == "night"
- if dark_appearance:
+def update_warp_settings(path: Path, mode: str) -> bool:
+ """Patch Warp settings.toml with mode-aware opacity/blur for glass coherence."""
+ if mode == "dark":
opacity_val = 76
blur_val = 20
blur_texture = True
@@ -232,9 +204,8 @@ def write_variant_files(
"""Write every named variant, failing closed before the first write.
The declared ``names`` must be a subset of the provided ``variants`` map
- (dark/light/night) so a caller can never silently fall back to standard
- dark for a missing Night palette (R5, design §6): the preflight raises
- before any file is touched.
+ (dark/light) so a caller can never silently fall back to another palette
+ for a missing variant: the preflight raises before any file is touched.
"""
missing = set(names) - set(variants)
if missing:
@@ -260,7 +231,7 @@ def write_variant_files_and_active(
All content is rendered in memory before the first write, and
``write_variant_files``' fail-closed ``names <= variants`` preflight runs
- before any file is touched — a missing Night variant or a render error
+ before any file is touched — a missing variant or a render error
aborts with zero mutations. Full snapshot/rollback of the activation
transaction is owned by the Phase 5 activation layer.
"""
diff --git a/tests/characterization/capture_legacy_sync.py b/tests/characterization/capture_legacy_sync.py
index e1ca290c..18082f79 100644
--- a/tests/characterization/capture_legacy_sync.py
+++ b/tests/characterization/capture_legacy_sync.py
@@ -1,16 +1,15 @@
"""Characterization harness for the current (legacy) sync plan.
-Hexagonal-architecture-v2 Phase 0 tasks 0.2 + 0.3: capture, for all 32
+Hexagonal-architecture-v2 Phase 0 tasks 0.2 + 0.3: capture, for all 33
consumers declared by ``sync.py:COVERAGE``, the resolved active path,
repository variant paths per mode, mode set, writer/selector behavior,
summary rows (via the real ``print_summary``), and the rendered content
hashes produced by the current ``prepare()``/render path — WITHOUT running
any writer, selector, or filesystem mutation.
-Variant paths are derived from the authoritative ``COVERAGE.night_artifact``
-row by mode substitution ("night" -> dark/light, "Night" -> Dark/Light),
-which matches every current repository artifact naming pattern; only Zellij
-(current code writes the night KDL only) carries an explicit override.
+Variant paths are derived from the authoritative ``COVERAGE.artifact`` (Dark)
+row by mode substitution ("dark" -> light, "Dark" -> Light), which matches
+every current repository artifact naming pattern.
Outputs (deterministic, sorted):
- tests/fixtures/legacy_sync_characterization.json
@@ -53,6 +52,7 @@
hypr_content,
kitty_content,
kitty_ui_content,
+ lazygit_content,
ls_colors_content,
nvim_content,
obsidian_content,
@@ -71,7 +71,7 @@
from dreamcoder_theme.settings import ROOT, theme_paths # noqa: E402
from dreamcoder_theme.sync import COVERAGE, prepare, print_summary # noqa: E402
-MODES = ("dark", "light", "night")
+MODES = ("dark", "light")
# Builders used by the current sync path for repository variant artifacts.
BUILDERS = {
@@ -86,6 +86,7 @@
"pi_theme": pi_theme_content,
"antigravity": antigravity_content,
"tmux": tmux_content,
+ "lazygit": lazygit_content,
"zsh_syntax": zsh_syntax_content,
"ls_colors": ls_colors_content,
"bat": bat_content,
@@ -119,6 +120,7 @@
"pi_theme": "pi_theme",
"antigravity": None,
"tmux": "tmux",
+ "lazygit": "lazygit",
"zsh_syntax": "zsh_syntax",
"ls_colors": "ls_colors",
"bat": "bat",
@@ -142,12 +144,6 @@
"herdr": None,
}
-# Consumers whose current repo artifact is written for only a subset of modes.
-# Zellij: only the night KDL is generated today (dark/light have no artifact).
-MODE_ARTIFACT_OVERRIDES: dict[str, dict[str, str | None]] = {
- "zellij": {"dark": None, "light": None},
-}
-
def rel(p: Path) -> str:
"""Deterministic POSIX path relative to the repository root."""
@@ -159,25 +155,21 @@ def rel(p: Path) -> str:
def variant_paths_for(row) -> dict[str, str | None]:
- """Per-mode repository artifact paths from the COVERAGE night artifact."""
- artifact = row.night_artifact
+ """Per-mode repository artifact paths from the COVERAGE (Dark) artifact."""
+ artifact = row.artifact
if artifact.startswith("active:"):
return {m: artifact for m in MODES}
- override = MODE_ARTIFACT_OVERRIDES.get(row.consumer_id, {})
out: dict[str, str | None] = {}
for mode in MODES:
- if mode in override:
- out[mode] = override[mode]
- else:
- resolved = artifact
- if "" in resolved:
- complete = next(
- (p for p in SUPPORTED_PROFILES if p is not None and p.is_complete), None
- )
- resolved = resolved.replace(
- "", complete.evidence.version if complete else "unknown"
- )
- out[mode] = resolved.replace("night", mode).replace("Night", mode.title())
+ resolved = artifact
+ if "" in resolved:
+ complete = next(
+ (p for p in SUPPORTED_PROFILES if p is not None and p.is_complete), None
+ )
+ resolved = resolved.replace(
+ "", complete.evidence.version if complete else "unknown"
+ )
+ out[mode] = resolved.replace("dark", mode).replace("Dark", mode.title())
return out
@@ -207,7 +199,7 @@ def capture_characterization() -> dict:
"writer": row.writer,
"selection_strategy": row.selection_strategy,
"source": row.source,
- "night_artifact": row.night_artifact,
+ "artifact": row.artifact,
"modes": list(MODES),
"active_path": active_path_for(row, paths),
"variant_paths": variant_paths_for(row),
@@ -281,7 +273,7 @@ def artifact_hash_for(row, base_mode: str, prepared) -> dict:
def capture_output_hashes() -> dict:
artifacts: dict[str, dict[str, dict]] = {}
for base_mode in ("dark", "light"):
- prepared = prepare(base_mode, "standard")
+ prepared = prepare(base_mode)
for row in COVERAGE:
artifacts.setdefault(row.consumer_id, {})[base_mode] = artifact_hash_for(
row, base_mode, prepared
diff --git a/tests/ml4w/ml4w_managed.bats b/tests/ml4w/ml4w_managed.bats
index 2eb52f0c..e78430d3 100644
--- a/tests/ml4w/ml4w_managed.bats
+++ b/tests/ml4w/ml4w_managed.bats
@@ -157,7 +157,7 @@ load_ml4w_lib() {
@test "ml4w lib: a symlink to a Dreamcoder variant is accepted" {
mkdir -p "${HOME}/.config/hypr"
- ln -s "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/hypr-colors-night.lua" "${HOME}/.config/hypr/colors.lua"
+ ln -s "${DREAMCODER_DOTS_DIR}/DreamcoderThemes/dreamcoder/hypr-colors-light.lua" "${HOME}/.config/hypr/colors.lua"
load_ml4w_lib
run hypr_colors_is_dreamcoder "${HOME}/.config/hypr/colors.lua"
diff --git a/tests/shell/test_apply_theme.bats b/tests/shell/test_apply_theme.bats
index 7755a8eb..6943c30e 100644
--- a/tests/shell/test_apply_theme.bats
+++ b/tests/shell/test_apply_theme.bats
@@ -26,50 +26,28 @@ teardown() {
[ "$status" -eq 1 ]
}
-@test "apply-theme-mode.sh with invalid profile exits non-zero" {
- run bash scripts/apply-theme-mode.sh dark "" dusk 2>&1
+@test "apply-theme-mode.sh rejects night as a mode" {
+ run bash scripts/apply-theme-mode.sh night 2>&1
[ "$status" -eq 1 ]
- [[ "$output" == *"Invalid render profile"* ]]
+ [[ "$output" == *"Invalid mode: night"* ]]
}
-@test "apply-theme-mode.sh night profile: missing artifact blocks with zero mutation" {
- export HOME="${TEST_DIR}/home"
- mkdir -p "${HOME}/.config/waybar"
- ln -sf colors-dark.css "${HOME}/.config/waybar/colors.css"
-
- run bash scripts/apply-theme-mode.sh dark "" night 2>&1
- [ "$status" -eq 1 ]
- [[ "$output" == *"Night preparation failed"* ]]
- # Zero mutation pre-preparation: symlink untouched, cursor-cli env absent.
- [ "$(readlink "${HOME}/.config/waybar/colors.css")" = "colors-dark.css" ]
- [ ! -e "${HOME}/.cache/dreamcoder/cursor-cli.env" ]
-}
-
-@test "apply-theme-mode.sh selects night artifacts when profile=night" {
- # Manual trace: the selector code paths must resolve the *-night
- # artifacts through VARIANT while DREAMCODER_THEME_MODE stays dark.
+@test "apply-theme-mode.sh selects mode artifacts with no render profile" {
+ # Every selector resolves the *-dark / *-light artifacts straight from
+ # MODE; the retired Night profile and its artifacts are gone.
script="scripts/apply-theme-mode.sh"
- grep -q 'VARIANT="${MODE}"' "$script"
- grep -q '\[\[ "${PROFILE}" == "night" \]\] && VARIANT="night"' "$script"
- grep -q 'ln -sf "colors-${VARIANT}.css"' "$script"
- grep -q 'ln -sf "colors-${VARIANT}.rasi"' "$script"
- grep -q 'ln -sf "colors-${VARIANT}.lua"' "$script"
- grep -q 'ln -sf "hypr-colors-${VARIANT}.lua"' "$script"
- grep -q '"${_link%.conf}-${VARIANT}.conf"' "$script"
- grep -q 'Dreamcoder-${VARIANT^}.yaml' "$script"
- grep -q 'ln -sf "dreamcoder-${VARIANT}.theme"' "$script"
- grep -qF 'theme \"dreamcoder-${VARIANT}\"' "$script"
- grep -q 'delta-dreamcoder-${VARIANT}.gitconfig' "$script"
- grep -q 'config.${VARIANT}.yml' "$script"
- grep -q 'LAZYGIT_LINK=' "$script"
- grep -q 'DreamcoderLazygit/.config/lazygit/config.night.yml' "$script"
- grep -q 'DREAMCODER_THEME_PROFILE="${PROFILE}"' "$script"
- grep -q 'REQUIRED_NIGHT_ARTIFACTS' "$script"
-}
-
-@test "apply-theme-mode.sh kanagawa bridge carries night-derived colors" {
- grep -q '@ukiyo-color-text "#beccd8"' scripts/apply-theme-mode.sh
- grep -q '@ukiyo-color-bg-pane "#07090f"' scripts/apply-theme-mode.sh
+ grep -q 'ln -sf "colors-${MODE}.css"' "$script"
+ grep -q 'ln -sf "colors-${MODE}.rasi"' "$script"
+ grep -q 'ln -sf "colors-${MODE}.lua"' "$script"
+ grep -q 'ln -sf "hypr-colors-${MODE}.lua"' "$script"
+ grep -q '"${_link%.conf}-${MODE}.conf"' "$script"
+ grep -q 'Dreamcoder-${MODE^}.yaml' "$script"
+ grep -q 'ln -sf "dreamcoder-${MODE}.theme"' "$script"
+ grep -qF 'theme \"dreamcoder-${MODE}\"' "$script"
+ grep -q 'delta-dreamcoder-${MODE}.gitconfig' "$script"
+ grep -q 'config.${MODE}.yml' "$script"
+ ! grep -qi 'night' "$script"
+ ! grep -q 'PROFILE=' "$script"
}
@test "apply-theme-mode.sh flips the live lazygit config to the current variant" {
@@ -78,17 +56,10 @@ teardown() {
# existing delta selector), never at the static mode-tracking config.yml.
script="scripts/apply-theme-mode.sh"
grep -q 'LAZYGIT_LINK="${HOME}/.config/lazygit/config.yml"' "$script"
- grep -q 'LAZYGIT_VARIANT="${DOTS_DIR}/DreamcoderLazygit/.config/lazygit/config.${VARIANT}.yml"' "$script"
+ grep -q 'LAZYGIT_VARIANT="${DOTS_DIR}/DreamcoderLazygit/.config/lazygit/config.${MODE}.yml"' "$script"
grep -qF 'ln -sf "${LAZYGIT_VARIANT}" "${LAZYGIT_LINK}"' "$script"
}
-@test "apply-theme-mode.sh night gate requires the generated lazygit night artifact" {
- script="scripts/apply-theme-mode.sh"
- grep -q 'DreamcoderLazygit/.config/lazygit/config.night.yml' "$script"
- run bash -n "$script"
- [ "$status" -eq 0 ]
-}
-
@test "apply-theme-mode.sh re-selects the Dreamcoder btop theme after ML4W resets it" {
# ML4W upgrades rewrite btop.conf with color_theme = "matugen"; every apply
# must point it back at the Dreamcoder theme without rewriting it needlessly.
diff --git a/tests/shell/test_dreamcoder_dispatch.bats b/tests/shell/test_dreamcoder_dispatch.bats
index 6a42f3d7..c650856d 100644
--- a/tests/shell/test_dreamcoder_dispatch.bats
+++ b/tests/shell/test_dreamcoder_dispatch.bats
@@ -50,6 +50,13 @@ assert report["upstreams"]["ml4w"]["mappings"] == [], report
[[ "$output" == *"upstream-diff"* ]]
}
+@test "dreamcoder rejects night as an unknown command" {
+ run bash "${TEST_DIR}/scripts/dreamcoder" night
+ [ "$status" -eq 2 ]
+ [[ "$output" == *"Usage: dreamcoder"* ]]
+ [[ "$output" != *"night"* ]]
+}
+
@test "dreamcoder exposes src/ to Python routes without an installed package" {
mkdir -p "${TEST_DIR}/src/dreamcoder_theme"
printf 'def main():\n print("sync-ok")\n' > "${TEST_DIR}/src/dreamcoder_theme/sync.py"
diff --git a/tests/shell/test_pi_theme.bats b/tests/shell/test_pi_theme.bats
index 522b3e6e..77a694b6 100644
--- a/tests/shell/test_pi_theme.bats
+++ b/tests/shell/test_pi_theme.bats
@@ -1,7 +1,7 @@
#!/usr/bin/env bats
# ============================================================================
-# Tests for DreamcoderPi/.pi/agent/scripts/pi-theme.sh (profile-aware selector,
-# design §5 row 9, task 4.6)
+# Tests for DreamcoderPi/.pi/agent/scripts/pi-theme.sh (Light/Dark selector,
+# design §5 row 9)
# ============================================================================
setup() {
@@ -17,34 +17,34 @@ setup() {
cp DreamcoderPi/.pi/agent/scripts/pi-theme.sh \
"${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh"
touch "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-dark.json"
- touch "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json"
+ touch "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-light.json"
}
teardown() {
rm -rf "${TEST_DIR}"
}
-@test "pi-theme.sh selects night artifact when profile=night" {
- run env DREAMCODER_THEME_PROFILE=night bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
- [ "$status" -eq 0 ]
- [ "$(readlink "${PI_AGENT_DIR}/themes/dreamcoder.json")" = "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json" ]
-}
-
-@test "pi-theme.sh keeps dark artifact for standard profile" {
+@test "pi-theme.sh selects the dark artifact in dark mode" {
run bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
[ "$status" -eq 0 ]
[ "$(readlink "${PI_AGENT_DIR}/themes/dreamcoder.json")" = "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-dark.json" ]
}
-@test "pi-theme.sh rejects an invalid profile" {
- run env DREAMCODER_THEME_PROFILE=dusk bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
- [ "$status" -eq 1 ]
- [[ "$output" == *"invalid render profile"* ]]
+@test "pi-theme.sh selects the light artifact in light mode" {
+ run env DREAMCODER_THEME_MODE=light bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
+ [ "$status" -eq 0 ]
+ [ "$(readlink "${PI_AGENT_DIR}/themes/dreamcoder.json")" = "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-light.json" ]
}
-@test "pi-theme.sh fails closed when night artifact is missing" {
- rm "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-night.json"
+@test "pi-theme.sh ignores a stale DREAMCODER_THEME_PROFILE" {
run env DREAMCODER_THEME_PROFILE=night bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
+ [ "$status" -eq 0 ]
+ [ "$(readlink "${PI_AGENT_DIR}/themes/dreamcoder.json")" = "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-dark.json" ]
+}
+
+@test "pi-theme.sh fails closed when the mode artifact is missing" {
+ rm "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/themes/dreamcoder-dark.json"
+ run bash "${DREAMCODER_DOTS_DIR}/DreamcoderPi/.pi/agent/scripts/pi-theme.sh" 2>&1
[ "$status" -eq 1 ]
[[ "$output" == *"not found"* ]]
}
diff --git a/tests/test_canonical_loaders.py b/tests/test_canonical_loaders.py
index 38ae6b06..0711ac10 100644
--- a/tests/test_canonical_loaders.py
+++ b/tests/test_canonical_loaders.py
@@ -1,8 +1,7 @@
"""Canonical loader tests (task 2.5): thresholds come from tokens, never code.
-`load_guardrails()` and `load_render_profile()` read the canonical Night
-parameters and guardrail floors from `tokens.json` and fail closed when a
-required key is missing — no policy literals are permitted on the runtime path
+`load_guardrails()` reads the canonical guardrail floors from `tokens.json`
+and fails closed when a required key is missing — no policy literals are permitted on the runtime path
(ADR-002, R2/R3).
"""
@@ -14,8 +13,6 @@
from dreamcoder_theme._math import apca_lc
from dreamcoder_theme.palette import (
load_guardrails,
- load_render_profile,
- night_palette,
validate_palette,
)
@@ -85,44 +82,8 @@ def test_gate_uses_loaded_threshold_not_a_literal(tmp_path):
tokens["guardrails"]["minimum_apca_quiet"] = 55
g = load_guardrails(_write_tokens(tmp_path, tokens))
pal = dict(tokens["modes"]["dark"])
- errors = validate_palette(pal, g, profile="standard", mode="dark")
+ errors = validate_palette(pal, g, mode="dark")
quiet_errors = [e for e in errors if "class=quiet" in e and "minimum_apca_quiet" in e]
assert quiet_errors, "quiet floor change was not picked up from loaded tokens"
assert "=55" in quiet_errors[0]
assert abs(apca_lc(pal["subtle"], pal["bg"])) < 55
-
-
-def test_load_render_profile_returns_canonical_night_parameters():
- params = load_render_profile(THEME_ROOT / "tokens.json")
- assert params == {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- }
-
-
-def test_load_render_profile_fails_when_profile_missing(tmp_path):
- tokens = _tokens()
- del tokens["render_profiles"]["night"]
- with pytest.raises(ValueError, match="render profile 'night' missing"):
- load_render_profile(_write_tokens(tmp_path, tokens))
-
-
-def test_load_render_profile_fails_on_invalid_bounds(tmp_path):
- tokens = _tokens()
- tokens["render_profiles"]["night"]["corrective_step"] = 0.2 # > maximum_corrective_delta 0.12
- with pytest.raises(ValueError, match="corrective_step"):
- load_render_profile(_write_tokens(tmp_path, tokens))
-
-
-def test_night_transform_uses_loaded_profile_parameters(tmp_path):
- """A canonical brightness change must change the derived palette (no literals)."""
- tokens = _tokens()
- tokens["render_profiles"]["night"]["brightness_factor"] = 0.9
- params = load_render_profile(_write_tokens(tmp_path, tokens))
- g = load_guardrails(THEME_ROOT / "tokens.json")
- base = dict(tokens["modes"]["dark"])
- night_0_86 = night_palette(base, load_render_profile(THEME_ROOT / "tokens.json"), g)
- night_0_90 = night_palette(base, params, g)
- assert night_0_90["surface1"] != night_0_86["surface1"]
diff --git a/tests/test_cli_theme_activation.py b/tests/test_cli_theme_activation.py
index 5fb9e061..7e99591c 100644
--- a/tests/test_cli_theme_activation.py
+++ b/tests/test_cli_theme_activation.py
@@ -1,7 +1,8 @@
"""CLI theme activation + transaction/rollback (Phase 5, tasks 5.2/5.3/5.5/5.7).
-Covers R7/R4 scenarios: ``theme apply`` persists base+profile, light/dark exit
-Night, a failing gate changes nothing and exits non-zero, and an injected
+Covers R7/R4 scenarios: ``theme apply`` persists the Light/Dark base, ``night``
+is no longer an accepted choice, a failing gate changes nothing and exits
+non-zero, and an injected
post-commit write/reload failure restores file bytes, symlink targets, and
prior settings. Tests drive ``control.main`` directly (no subprocess) with an
isolated temp config home; the system/reload adapter is mocked.
@@ -85,7 +86,6 @@ def theme_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path / ".cache"))
monkeypatch.setenv("DREAMCODER_WRITE_REPO", "0")
monkeypatch.setenv("DREAMCODER_CLEAN_OPENCODE_THEMES", "0")
- monkeypatch.delenv("DREAMCODER_THEME_PROFILE", raising=False)
monkeypatch.delenv("DREAMCODER_THEME_MODE", raising=False)
monkeypatch.delenv("DREAMCODER_SYNC_DONE", raising=False)
monkeypatch.delenv("DREAMCODER_WALLPAPER", raising=False)
@@ -122,54 +122,53 @@ def _tree_state(root: Path) -> dict[str, str]:
# ---------------------------------------------------------------------------
-# R7: Night activation
+# R7: Light/Dark activation
# ---------------------------------------------------------------------------
-def test_night_activation_persists_profile_and_dark_base(theme_home: Path, capsys) -> None:
- rc, payload = _run("night", capsys)
+def test_dark_activation_persists_base(theme_home: Path, capsys) -> None:
+ rc, payload = _run("dark", capsys)
assert rc == 0
assert settings_get("terminal.default_mode") == "dark"
- assert settings_get("theme.render_profile") == "night"
- assert payload["requested"] == "night"
+ assert payload["requested"] == "dark"
assert payload["effective_base"] == "dark"
- assert payload["effective_profile"] == "night"
+ assert "effective_profile" not in payload
assert payload["coverage"] == "33/33"
assert payload["rollback_state"] == "none"
assert payload["changed"]["kitty"] is True
- # The dark base + Night profile produce an active kitty output.
assert theme_paths().kitty.exists()
-# ---------------------------------------------------------------------------
-# R7: Light and Dark exit Night
-# ---------------------------------------------------------------------------
-
-
-def test_light_from_night_persists_standard_and_regenerates_all_active(
- theme_home: Path, capsys
-) -> None:
- _run("night", capsys)
- night_kitty = theme_paths().kitty.read_bytes()
+def test_light_from_dark_regenerates_all_active(theme_home: Path, capsys) -> None:
+ _run("dark", capsys)
+ dark_kitty = theme_paths().kitty.read_bytes()
rc, payload = _run("light", capsys)
assert rc == 0
assert settings_get("terminal.default_mode") == "light"
- assert settings_get("theme.render_profile") == "standard"
- assert payload["effective_profile"] == "standard"
assert payload["coverage"] == "33/33"
assert payload["rollback_state"] == "none"
- light_kitty = theme_paths().kitty.read_bytes()
- assert light_kitty != night_kitty # active outputs were regenerated
+ assert theme_paths().kitty.read_bytes() != dark_kitty # active outputs regenerated
-def test_dark_from_night_persists_standard(theme_home: Path, capsys) -> None:
- _run("night", capsys)
- rc, payload = _run("dark", capsys)
+def test_night_choice_is_rejected(theme_home: Path, capsys) -> None:
+ before = _tree_state(theme_home)
+ with pytest.raises(SystemExit) as exc:
+ control.main(["theme", "apply", "night", "--json"])
+ assert exc.value.code == 2
+ assert "invalid choice" in capsys.readouterr().err
+ assert _tree_state(theme_home) == before
+
+
+def test_activation_ignores_and_preserves_legacy_render_profile(theme_home: Path, capsys) -> None:
+ """A settings file from the Night era still activates cleanly."""
+ settings_file = theme_home / ".config/dreamcoder/settings.json"
+ settings_file.parent.mkdir(parents=True)
+ settings_file.write_text(json.dumps({"theme": {"render_profile": "night"}}))
+ rc, payload = _run("light", capsys)
assert rc == 0
- assert settings_get("terminal.default_mode") == "dark"
- assert settings_get("theme.render_profile") == "standard"
- assert payload["effective_profile"] == "standard"
- assert payload["coverage"] == "33/33"
+ assert payload["effective_base"] == "light"
+ assert settings_get("terminal.default_mode") == "light"
+ assert settings_get("theme.render_profile") == "night" # preserved, never read
# ---------------------------------------------------------------------------
@@ -182,12 +181,12 @@ def test_failing_gate_changes_no_settings_or_output_and_exits_nonzero(
) -> None:
before = _tree_state(theme_home)
with mock.patch("dreamcoder_theme.sync.validate_palette", return_value=["forced gate failure"]):
- rc, payload = _run("night", capsys)
+ rc, payload = _run("dark", capsys)
assert rc != 0
assert payload["rollback_state"] == "rejected"
assert payload["errors"] == ["forced gate failure"]
assert _tree_state(theme_home) == before # zero writes
- assert settings_get("theme.render_profile") is None # nothing persisted
+ assert settings_get("terminal.default_mode") is None # nothing persisted
# ---------------------------------------------------------------------------
@@ -198,7 +197,7 @@ def test_failing_gate_changes_no_settings_or_output_and_exits_nonzero(
def test_post_commit_reload_failure_restores_bytes_symlinks_and_settings(
theme_home: Path, capsys
) -> None:
- # Seed prior state: light/standard with a matugen bridge symlink exactly as
+ # Seed prior state: light with a matugen bridge symlink exactly as
# the shell adapter would leave it (colors.css -> colors-light.css).
_run("light", capsys)
paths = theme_paths()
@@ -209,13 +208,12 @@ def test_post_commit_reload_failure_restores_bytes_symlinks_and_settings(
os.symlink("colors-light.css", paths.waybar_matugen)
before = _tree_state(theme_home)
- rc, payload = _run("night", capsys, reload_failure=True)
+ rc, payload = _run("dark", capsys, reload_failure=True)
assert rc != 0
assert payload["rollback_state"] == "restored"
assert _tree_state(theme_home) == before
assert os.readlink(paths.waybar_matugen) == "colors-light.css"
assert bridge.read_text() == real_content.decode()
- assert settings_get("theme.render_profile") == "standard"
assert settings_get("terminal.default_mode") == "light"
@@ -234,11 +232,11 @@ def failing_writer(path: Path, content: str) -> bool:
return True
with mock.patch("dreamcoder_theme.sync.write_if_changed", side_effect=failing_writer):
- rc, payload = _run("night", capsys)
+ rc, payload = _run("dark", capsys)
assert rc != 0
assert payload["rollback_state"] == "restored"
assert _tree_state(theme_home) == before
- assert settings_get("theme.render_profile") == "standard"
+ assert settings_get("terminal.default_mode") == "light"
# ---------------------------------------------------------------------------
@@ -246,17 +244,15 @@ def failing_writer(path: Path, content: str) -> bool:
# ---------------------------------------------------------------------------
-def test_night_light_dark_end_to_end_transitions(theme_home: Path, capsys) -> None:
- transitions: list[tuple[str, str]] = []
- for choice in ("night", "light", "dark"):
+def test_dark_light_dark_end_to_end_transitions(theme_home: Path, capsys) -> None:
+ transitions: list[str] = []
+ for choice in ("dark", "light", "dark"):
rc, payload = _run(choice, capsys)
assert rc == 0
assert payload["coverage"] == "33/33"
assert payload["rollback_state"] == "none"
- transitions.append(
- (settings_get("terminal.default_mode"), settings_get("theme.render_profile"))
- )
- assert transitions == [("dark", "night"), ("light", "standard"), ("dark", "standard")]
+ transitions.append(settings_get("terminal.default_mode"))
+ assert transitions == ["dark", "light", "dark"]
# ---------------------------------------------------------------------------
@@ -298,7 +294,7 @@ def test_reload_failure_restores_lazygit_active_and_live_symlink(theme_home: Pat
os.symlink("config.dark.yml", live / "config.yml")
before = _tree_state(theme_home)
- rc, payload = _run("night", capsys, reload_failure=True)
+ rc, payload = _run("dark", capsys, reload_failure=True)
assert rc != 0
assert payload["rollback_state"] == "restored"
assert _tree_state(theme_home) == before
@@ -333,7 +329,7 @@ def test_reload_failure_restores_herdr_override_selector(
os.symlink(old_target, override)
monkeypatch.setenv("HERDR_CONFIG_PATH", str(override))
- def fail_after_switch(_base: str, _profile: str) -> None:
+ def fail_after_switch(_base: str) -> None:
override.unlink()
os.symlink(new_target, override)
raise RuntimeError("injected Herdr reload failure")
@@ -350,9 +346,9 @@ def fail_after_switch(_base: str, _profile: str) -> None:
def test_generic_settings_interface_keeps_working(theme_home: Path, capsys) -> None:
- assert control.main(["settings", "set", "theme.render_profile", "night", "--json"]) == 0
+ assert control.main(["settings", "set", "motion.active", "fluid", "--json"]) == 0
capsys.readouterr()
- assert settings_get("theme.render_profile") == "night"
- assert control.main(["settings", "get", "theme.render_profile", "--json"]) == 0
+ assert settings_get("motion.active") == "fluid"
+ assert control.main(["settings", "get", "motion.active", "--json"]) == 0
out = json.loads(capsys.readouterr().out)
- assert out["value"] == "night"
+ assert out["value"] == "fluid"
diff --git a/tests/test_dark_neutral_palette.py b/tests/test_dark_neutral_palette.py
index 8e19543d..5ea5581d 100644
--- a/tests/test_dark_neutral_palette.py
+++ b/tests/test_dark_neutral_palette.py
@@ -9,8 +9,6 @@
apca_lc,
contrast,
load_guardrails,
- load_render_profile,
- night_palette,
)
ROOT = Path(__file__).resolve().parents[1]
@@ -92,13 +90,3 @@ def test_dark_roles_meet_their_declared_apca_thresholds(role: str, guardrail: st
thresholds = load_guardrails(TOKENS)
assert abs(apca_lc(dark[role], dark["bg"])) >= thresholds[guardrail]
-
-
-def test_night_is_derived_from_the_neutral_dark_source_without_parameter_changes():
- dark = _dark()
- night = night_palette(dark, load_render_profile(TOKENS), load_guardrails(TOKENS))
-
- assert night["bg"] == "#000000"
- assert night["selection"] == night["selection_bg"]
- assert night["text"] != dark["text"]
- assert contrast(night["text"], night["surface3"]) >= 7
diff --git a/tests/test_dreamcoder_sync.py b/tests/test_dreamcoder_sync.py
index b463c505..a1516505 100644
--- a/tests/test_dreamcoder_sync.py
+++ b/tests/test_dreamcoder_sync.py
@@ -11,7 +11,7 @@
import pytest
from dreamcoder_theme import sync
-from dreamcoder_theme.palette import load_render_profile, load_variants, night_palette
+from dreamcoder_theme.palette import load_variants
from dreamcoder_theme.palette_tokens import VARIANTS as V
from dreamcoder_theme.settings import ThemePaths
@@ -138,13 +138,9 @@ def active() -> dict[str, str]:
@pytest.fixture
def variants() -> dict[str, dict[str, str]]:
- tokens_file = ROOT / "DreamcoderThemes" / "dreamcoder" / "tokens.json"
- params = load_render_profile(tokens_file)
- guardrails = _canonical_guardrails()
return {
"dark": dict(V["dark"]),
"light": dict(V["light"]),
- "night": night_palette(dict(V["dark"]), params, guardrails),
}
@@ -218,13 +214,6 @@ def _main_patches(mock_paths, active, variants, **overrides):
write_repo_enabled=True,
valid_starship=True,
load_guardrails=_canonical_guardrails(),
- load_render_profile={
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- night_palette=active,
batch_theme_variants=[False],
)
vals.update(overrides)
@@ -238,8 +227,6 @@ def _main_patches(mock_paths, active, variants, **overrides):
"write_repo_enabled",
"valid_starship",
"load_guardrails",
- "load_render_profile",
- "night_palette",
):
if name in vals:
result.append(mock.patch(f"dreamcoder_theme.sync.{name}", return_value=vals[name]))
@@ -275,9 +262,9 @@ def test_main_gate_failure_blocks_all_writes(mock_paths, active, variants):
"""R4: a failed dual gate performs zero writes and exits non-zero.
No writer, selector, variant, or repo writer may run when validation
- fails, and no settings/profile mutation occurs (Phase 2 main() performs
- none; persisted profile state is Phase 4/5 — here the fail-closed contract
- is: non-zero exit, zero writes, prior profile untouched by construction).
+ fails, and no settings mutation occurs (main() performs none — here the
+ fail-closed contract is: non-zero exit, zero writes, prior state untouched
+ by construction).
"""
patches = [
mock.patch("dreamcoder_theme.sync.theme_paths", return_value=mock_paths),
@@ -287,16 +274,6 @@ def test_main_gate_failure_blocks_all_writes(mock_paths, active, variants):
mock.patch("dreamcoder_theme.sync.adaptive_enabled", return_value=False),
mock.patch("dreamcoder_theme.sync.write_repo_enabled", return_value=True),
mock.patch("dreamcoder_theme.sync.load_guardrails", return_value=_canonical_guardrails()),
- mock.patch(
- "dreamcoder_theme.sync.load_render_profile",
- return_value={
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- ),
- mock.patch("dreamcoder_theme.sync.night_palette", return_value=active),
mock.patch("dreamcoder_theme.sync.validate_palette", return_value=["forced gate failure"]),
]
# Every write path must be untouched when the gate fails.
@@ -480,7 +457,7 @@ def test_kitty_ui_active_write_does_not_corrupt_symlinked_sibling(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, variants, active
) -> None:
"""An external mode selector can leave dreamcoder-ui.conf as a symlink to
- one of the -dark/-light/-night siblings. Writing the active (dark) file
+ one of the -dark/-light siblings. Writing the active (dark) file
through that symlink must never overwrite the sibling's own content."""
monkeypatch.setattr(sync, "ROOT", tmp_path)
kitty_dir = tmp_path / "DreamcoderKitty/.config/kitty"
diff --git a/tests/test_four_candidate_validation.py b/tests/test_four_candidate_validation.py
index 6cad8a71..fcea0013 100644
--- a/tests/test_four_candidate_validation.py
+++ b/tests/test_four_candidate_validation.py
@@ -1,10 +1,9 @@
-"""Four-candidate deterministic validation tests (task 2.6).
+"""Deterministic candidate validation tests (task 2.6).
-The dual gate (ADR-002) is exercised against the four deterministic
-candidates: standard Light, standard Dark, design-system Dusk, and the derived
-Night palette (adaptive disabled for the gate — candidates are built directly
-from the canonical token modes, never through ``adaptive_palette``). All four
-must pass both the WCAG 2.2 and APCA floors. Per-class at-floor passes and
+The dual gate (ADR-002) is exercised against the deterministic candidates:
+Light, Dark, and design-system Dusk (adaptive disabled for the gate —
+candidates are built directly from the canonical token modes, never through
+``adaptive_palette``). All must pass both the WCAG 2.2 and APCA floors. Per-class at-floor passes and
just-below-floor failures are asserted for Heading (Lc 60 light / 45 dark),
Body (75 light / 50 dark), Quiet (44), UI (60 light / 28 dark), and On-accent
(60), using the mode-aware floor keys.
@@ -18,8 +17,6 @@
from dreamcoder_theme._math import apca_lc, contrast
from dreamcoder_theme.palette import (
load_guardrails,
- load_render_profile,
- night_palette,
validate_palette,
)
@@ -60,30 +57,16 @@ def _nearest_gray(
# ---------------------------------------------------------------------------
-# Four deterministic candidates
+# Deterministic candidates
# ---------------------------------------------------------------------------
-def test_four_candidates_all_pass_the_dual_gate():
+def test_candidates_all_pass_the_dual_gate():
tokens = _tokens()
g = _guardrails()
- params = load_render_profile(THEME_ROOT / "tokens.json")
for mode in ("light", "dark", "dusk"):
- errors = validate_palette(dict(tokens["modes"][mode]), g, profile="standard", mode=mode)
- assert errors == [], f"standard:{mode} gate failures: {errors}"
- night = night_palette(dict(tokens["modes"]["dark"]), params, g)
- errors = validate_palette(night, g, profile="night", mode="dark")
- assert errors == [], f"derived:night gate failures: {errors}"
-
-
-def test_night_candidate_is_deterministic():
- tokens = _tokens()
- params = load_render_profile(THEME_ROOT / "tokens.json")
- base = dict(tokens["modes"]["dark"])
- first = night_palette(base, params, _guardrails())
- second = night_palette(base, params, _guardrails())
- assert first == second
- assert validate_palette(first, _guardrails(), profile="night", mode="dark") == []
+ errors = validate_palette(dict(tokens["modes"][mode]), g, mode=mode)
+ assert errors == [], f"{mode} gate failures: {errors}"
def test_candidates_use_canonical_palettes_not_adaptive_output():
@@ -114,7 +97,7 @@ def _assert_class_floor(mode: str, fg_key: str, floor_key: str, pair_bg: str | N
assert above_lc >= floor and above_wcag >= 4.5
pal_above = dict(pal)
pal_above[fg_key] = above
- errors = validate_palette(pal_above, g, profile="standard", mode=mode)
+ errors = validate_palette(pal_above, g, mode=mode)
assert not [e for e in errors if f"{fg_key}/{pair_bg or 'bg'}" in e], (
f"{mode}.{fg_key} at-floor {above} (Lc {above_lc:.1f} >= {floor}) should pass"
)
@@ -123,7 +106,7 @@ def _assert_class_floor(mode: str, fg_key: str, floor_key: str, pair_bg: str | N
assert below_lc < floor
pal_below = dict(pal)
pal_below[fg_key] = below
- errors = validate_palette(pal_below, g, profile="standard", mode=mode)
+ errors = validate_palette(pal_below, g, mode=mode)
apca_errors = [e for e in errors if "APCA fail" in e and f"pair={fg_key}/" in e]
assert apca_errors, (
f"{mode}.{fg_key} just-below {below} (Lc {below_lc:.1f} < {floor}) must block"
diff --git a/tests/test_herdr_switch.py b/tests/test_herdr_switch.py
index ac014b47..9d5eaabe 100644
--- a/tests/test_herdr_switch.py
+++ b/tests/test_herdr_switch.py
@@ -38,7 +38,7 @@ def _variant_root(
tmp_path: Path,
*,
versions: tuple[str, ...] = ("0.7.3", "0.8.0", "0.8.2", "0.9.1"),
- modes: tuple[str, ...] = ("dark", "light", "night"),
+ modes: tuple[str, ...] = ("dark", "light"),
) -> Path:
root = tmp_path / "variants"
for version in versions:
@@ -151,8 +151,8 @@ def test_newer_unprofiled_version_falls_back_to_newest_variant(tmp_path: Path) -
def test_installed_091_selects_its_own_variant(tmp_path: Path) -> None:
root = _variant_root(tmp_path)
- choice = choose_variant("herdr 0.9.1\n", "night", root)
- assert choice.path == root / "0.9.1/config.night.toml"
+ choice = choose_variant("herdr 0.9.1\n", "light", root)
+ assert choice.path == root / "0.9.1/config.light.toml"
assert choice.profile_version == "0.9.1"
assert choice.fallback is False
@@ -352,7 +352,7 @@ def test_reload_failure_removes_a_newly_created_selector(tmp_path: Path) -> None
def test_reload_launch_failure_restores_the_previous_selector(tmp_path: Path) -> None:
root = _variant_root(tmp_path)
selector = tmp_path / "config.toml"
- original = root / "0.9.1/config.night.toml"
+ original = root / "0.9.1/config.dark.toml"
selector.symlink_to(original)
run = FakeRun(reload_error=FileNotFoundError("herdr"))
outcome = switch_herdr("light", run=run, selector=selector, variant_root=root)
diff --git a/tests/test_herdr_theme_generation.py b/tests/test_herdr_theme_generation.py
index d1bd0ac5..1e9ab49b 100644
--- a/tests/test_herdr_theme_generation.py
+++ b/tests/test_herdr_theme_generation.py
@@ -100,9 +100,9 @@ def test_herdr_080_light_renders_dreamcoder_light() -> None:
assert parsed["ui"]["accent"] == "#6FA0AF"
-@pytest.mark.parametrize("mode", ("dark", "light", "night"))
+@pytest.mark.parametrize("mode", ("dark", "light"))
def test_herdr_082_uses_mode_aware_base_and_sidebar_tokens(mode: str) -> None:
- palette = VARIANTS["dark"] if mode == "night" else VARIANTS[mode]
+ palette = VARIANTS[mode]
parsed = tomllib.loads(herdr_content(HERDR_082_PROFILE, mode, palette))
expected_base = "catppuccin-latte" if mode == "light" else "catppuccin"
@@ -142,9 +142,9 @@ def test_herdr_variants_are_byte_stable_and_have_matching_structure() -> None:
assert dark != light
-@pytest.mark.parametrize("mode", ("dusk", "invalid"))
+@pytest.mark.parametrize("mode", ("dusk", "night", "invalid"))
def test_herdr_rejects_non_static_modes(mode: str) -> None:
- with pytest.raises(HerdrModeError, match="only dark, light, and night"):
+ with pytest.raises(HerdrModeError, match="only dark and light"):
herdr_content(HERDR_073_PROFILE, mode, VARIANTS["dark"])
@@ -180,7 +180,6 @@ def test_repository_sync_writes_only_versioned_variants(
def test_checked_in_repository_variants_match_the_renderer() -> None:
repo = Path(__file__).parents[1]
- night = sync.prepare("dark", "standard").variants["night"]
for profile in (HERDR_073_PROFILE, HERDR_080_PROFILE, HERDR_082_PROFILE, HERDR_091_PROFILE):
base = repo / "DreamcoderHerdr/.config/herdr/dreamcoder" / profile.evidence.version
@@ -190,7 +189,6 @@ def test_checked_in_repository_variants_match_the_renderer() -> None:
assert (base / "config.light.toml").read_text() == herdr_content(
profile, "light", VARIANTS["light"]
)
- assert (base / "config.night.toml").read_text() == herdr_content(profile, "night", night)
def test_unsupported_profile_produces_no_repository_files(
diff --git a/tests/test_lazygit_renderer.py b/tests/test_lazygit_renderer.py
index 2a2cd70f..de94de9c 100644
--- a/tests/test_lazygit_renderer.py
+++ b/tests/test_lazygit_renderer.py
@@ -3,7 +3,7 @@
Covers the token-driven Lazygit integration: every mode variant parses as valid
YAML, theme/author/branch-log colors come from the canonical tokens (never a
duplicated per-mode palette), the Delta syntax theme stays valid per mode
-(Catppuccin Latte light / Mocha dark+night), non-color Lazygit behavior is
+(Catppuccin Latte light / Mocha dark), non-color Lazygit behavior is
preserved, and the registry registration is consistent with the engine.
"""
@@ -16,7 +16,7 @@
import pytest
import yaml
-from dreamcoder_theme.palette import detect_mode, load_render_profile, night_palette
+from dreamcoder_theme.palette import detect_mode
from dreamcoder_theme.palette_tokens import VARIANTS
from dreamcoder_theme.renderer_registry import REGISTRATIONS
from dreamcoder_theme.renderers_lazygit import lazygit_content
@@ -26,18 +26,10 @@
TOKENS = json.loads(TOKENS_FILE.read_text())
-def _night() -> dict[str, str]:
- params = load_render_profile(TOKENS_FILE)
- guardrails = {
- k: float(v) for k, v in TOKENS["guardrails"].items() if isinstance(v, (int, float))
- }
- return night_palette(dict(VARIANTS["dark"]), params, guardrails)
+VARIANTS_BY_MODE = {"dark": VARIANTS["dark"], "light": VARIANTS["light"]}
-VARIANTS_BY_MODE = {"dark": VARIANTS["dark"], "light": VARIANTS["light"], "night": _night()}
-
-
-@pytest.fixture(params=["dark", "light", "night"])
+@pytest.fixture(params=["dark", "light"])
def mode(request: pytest.FixtureRequest) -> str:
return request.param
@@ -63,7 +55,6 @@ def test_active_config_artifact_parses(self) -> None:
ROOT / "DreamcoderLazygit/.config/lazygit/config.yml",
ROOT / "DreamcoderLazygit/.config/lazygit/config.dark.yml",
ROOT / "DreamcoderLazygit/.config/lazygit/config.light.yml",
- ROOT / "DreamcoderLazygit/.config/lazygit/config.night.yml",
):
assert path.is_file(), f"missing generated artifact {path}"
assert _parse(path.read_text())["gui"]["theme"]
@@ -124,17 +115,10 @@ def test_dark_uses_catppuccin_mocha(self) -> None:
cmd = _parse(lazygit_content(VARIANTS["dark"]))["git"]["diffRenderers"][0]["command"]
assert 'delta --syntax-theme "Catppuccin Mocha" --paging=never' in cmd
- def test_night_keeps_dark_theme(self) -> None:
- cmd = _parse(lazygit_content(VARIANTS_BY_MODE["night"]))["git"]["diffRenderers"][0][
- "command"
- ]
- assert 'delta --syntax-theme "Catppuccin Mocha" --paging=never' in cmd
-
def test_installed_catppuccin_themes_are_used(self) -> None:
# The chosen themes must match the machine-installed set (Latte/Mocha).
assert detect_mode(VARIANTS["light"]) == "light"
assert detect_mode(VARIANTS["dark"]) == "dark"
- assert detect_mode(VARIANTS_BY_MODE["night"]) == "dark" # Night keeps dark semantics
class TestNonColorBehaviorPreserved:
@@ -169,13 +153,13 @@ def test_lazygit_registered_as_active_and_repository(self) -> None:
regs = {r.consumer_id: r for r in REGISTRATIONS}
assert "lazygit" in regs
reg = regs["lazygit"]
- assert reg.modes == frozenset({"dark", "light", "night"})
+ assert reg.modes == frozenset({"dark", "light"})
assert reg.output_kind == "active-and-repository"
assert reg.sync.repository.value == "mode_variants"
assert reg.sync.active.value == "resolved_active_path"
def test_renderer_conforms_for_all_modes(self) -> None:
regs = {r.consumer_id: r for r in REGISTRATIONS}
- for mode in ("dark", "light", "night"):
+ for mode in ("dark", "light"):
result = regs["lazygit"].renderer(VARIANTS_BY_MODE[mode])
assert type(result) is str and len(result) > 0
diff --git a/tests/test_night_naming_selection.py b/tests/test_night_naming_selection.py
deleted file mode 100644
index 7b9d7faf..00000000
--- a/tests/test_night_naming_selection.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""Night naming/selection tests (Phase 4, task 4.6).
-
-Covers R5/R9: every Night registry entry resolves to a distinct ``*-night``
-artifact name, the Ghostty/Zellij/Warp/Neovim/Pi selectors are profile-aware,
-and standard-dark substitution is detected and forbidden.
-"""
-
-from __future__ import annotations
-
-import re
-from pathlib import Path
-
-import pytest
-
-from dreamcoder_theme import sync
-from dreamcoder_theme.renderers_extra_nvim import nvim_dispatcher_content
-from dreamcoder_theme.writers import (
- update_ghostty_theme,
- update_warp_settings,
- update_zellij_config,
- write_variant_files,
-)
-
-# ---------------------------------------------------------------------------
-# Every Night registry entry has a distinct *-night name (R5)
-# ---------------------------------------------------------------------------
-
-
-def test_every_registry_entry_declares_a_night_name() -> None:
- """Each VARIANT_REGISTRY entry must carry a ``night`` key (D = dark/light/night)."""
- for base, names, _builder, _active in sync.VARIANT_REGISTRY:
- assert "night" in names, f"registry entry {base} has no night name"
-
-
-def test_night_names_never_equal_dark_names() -> None:
- """Standard-dark substitution is forbidden: night must not reuse the dark name."""
- for base, names, _builder, _active in sync.VARIANT_REGISTRY:
- assert names["night"] != names["dark"], f"night alias for {base} equals dark"
- assert names["night"] != names["light"], f"night alias for {base} equals light"
-
-
-def test_night_names_carry_a_night_marker() -> None:
- """Every registry night name is visually distinct via -night/-Night or .night.
-
- Hyphen markers (``*-night``/``*-Night``) are the majority convention; the
- dotted marker (``config.night.yml``) follows the established Herdr
- ``config.night.toml`` naming for config files whose base name must stay
- ``config`` (Lazygit consumes ``config.yml``).
- """
- for base, names, _builder, _active in sync.VARIANT_REGISTRY:
- name = names["night"]
- assert re.search(r"-night\b|-Night\b|\.night\b", name), f"{name} lacks a night marker"
-
-
-def test_registry_coverage_rows_use_the_same_night_names() -> None:
- """COVERAGE night_artifact rows match the registry names byte-for-byte."""
- registry_night = {
- (base / names["night"]).relative_to(sync.ROOT).as_posix()
- for base, names, _builder, _active in sync.VARIANT_REGISTRY
- }
- declared = {row.night_artifact for row in sync.COVERAGE if row.source == "registry"}
- assert declared == registry_night
- problems = sync.validate_coverage_declaration()
- assert problems == [], problems
-
-
-def test_write_variant_files_writes_night_artifact(tmp_path: Path) -> None:
- """write_variant_files produces the night artifact from the night palette."""
-
- def builder(variant: dict[str, str]) -> str:
- return f"bg={variant['bg']}\n"
-
- base = tmp_path / "themes"
- names = {"dark": "x-dark.conf", "light": "x-light.conf", "night": "x-night.conf"}
- variants = {
- "dark": {"bg": "#000000"},
- "light": {"bg": "#ffffff"},
- "night": {"bg": "#0d1015"},
- }
- write_variant_files(base, names, builder, variants)
- assert (base / "x-night.conf").read_text() == "bg=#0d1015\n"
-
-
-def test_write_variant_files_missing_night_fails_closed(tmp_path: Path) -> None:
- """A missing night variant aborts before any write (no silent fallback)."""
-
- def builder(variant: dict[str, str]) -> str:
- return f"bg={variant['bg']}\n"
-
- base = tmp_path / "themes"
- names = {"dark": "x-dark.conf", "night": "x-night.conf"}
- variants = {"dark": {"bg": "#000000"}} # night missing
- with pytest.raises(ValueError, match="night"):
- write_variant_files(base, names, builder, variants)
- assert not (base / "x-dark.conf").exists()
- assert not (base / "x-night.conf").exists()
-
-
-# ---------------------------------------------------------------------------
-# Profile-aware selectors (design §6)
-# ---------------------------------------------------------------------------
-
-
-class TestGhosttySelector:
- def test_night_selects_dreamcoder_night(self, tmp_path: Path) -> None:
- path = tmp_path / "config"
- path.write_text("[theme]\n")
- assert update_ghostty_theme(path, "dark", "night") is True
- assert "theme = dreamcoder-night" in path.read_text()
-
- def test_night_wins_over_light(self, tmp_path: Path) -> None:
- path = tmp_path / "config"
- path.write_text("[theme]\n")
- assert update_ghostty_theme(path, "light", "night") is True
- assert "theme = dreamcoder-night" in path.read_text()
- assert "theme = dreamcoder\n" not in path.read_text()
-
- def test_standard_dark_keeps_legacy_name(self, tmp_path: Path) -> None:
- path = tmp_path / "config"
- path.write_text("[theme]\n")
- assert update_ghostty_theme(path, "dark") is True
- assert "theme = dreamcoder-dark" in path.read_text()
-
- def test_standard_light_keeps_legacy_name(self, tmp_path: Path) -> None:
- path = tmp_path / "config"
- path.write_text("[theme]\n")
- assert update_ghostty_theme(path, "light") is True
- assert "theme = dreamcoder" in path.read_text()
-
- def test_night_never_selects_standard_dark(self, tmp_path: Path) -> None:
- path = tmp_path / "config"
- path.write_text("theme = dreamcoder-dark\n")
- assert update_ghostty_theme(path, "dark", "night") is True
- content = path.read_text()
- assert "theme = dreamcoder-night" in content
- assert "theme = dreamcoder-dark" not in content
-
-
-class TestZellijSelector:
- def test_night_selects_night_kdl(self, tmp_path: Path) -> None:
- path = tmp_path / "config.kdl"
- path.write_text("some_setting true\n")
- assert update_zellij_config(path, "dark", "night") is True
- assert 'theme "dreamcoder-night"' in path.read_text()
-
- def test_night_fails_closed_when_kdl_not_ready(self, tmp_path: Path) -> None:
- path = tmp_path / "config.kdl"
- path.write_text("some_setting true\n")
- with pytest.raises(ValueError, match="dreamcoder-night"):
- update_zellij_config(path, "dark", "night", kdl_ready=False)
- # No selector mutation happened.
- assert 'theme "dreamcoder-night"' not in path.read_text()
-
- def test_standard_mode_selector_unchanged(self, tmp_path: Path) -> None:
- path = tmp_path / "config.kdl"
- path.write_text("some_setting true\n")
- assert update_zellij_config(path, "light") is True
- assert 'theme "dreamcoder-light"' in path.read_text()
-
-
-class TestWarpSelector:
- def test_night_keeps_dark_appearance(self, tmp_path: Path) -> None:
- path = tmp_path / "settings.toml"
- assert update_warp_settings(path, "dark", "night") is True
- content = path.read_text()
- assert "override_opacity = 76" in content
- assert "override_blur = 20" in content
- assert "override_blur_texture = true" in content
-
- def test_night_never_enters_light_branch(self, tmp_path: Path) -> None:
- """Even with a light base, night keeps dark appearance (conflict is caught upstream)."""
- path = tmp_path / "settings.toml"
- assert update_warp_settings(path, "light", "night") is True
- content = path.read_text()
- assert "override_opacity = 76" in content
- assert "override_opacity = 96" not in content
-
- def test_standard_light_keeps_light_appearance(self, tmp_path: Path) -> None:
- path = tmp_path / "settings.toml"
- assert update_warp_settings(path, "light") is True
- assert "override_opacity = 96" in path.read_text()
-
-
-class TestNvimSelector:
- def test_dispatcher_resolves_profile_before_base_mode(self) -> None:
- content = nvim_dispatcher_content()
- profile_pos = content.index("DREAMCODER_THEME_PROFILE")
- mode_pos = content.index("DREAMCODER_THEME_MODE")
- assert profile_pos < mode_pos, "profile must be resolved before base mode"
- assert "dreamcoder-night.lua" in content
- assert 'profile == "night"' in content
-
- def test_background_alone_still_selects_standard_dark(self) -> None:
- content = nvim_dispatcher_content()
- # The dispatcher must never fall back to night when only the base mode
- # is known; background=dark resolves to dreamcoder-dark.lua.
- assert "dreamcoder-dark.lua" in content
-
-
-class TestPiSelector:
- def test_pi_theme_script_is_profile_aware(self) -> None:
- script = (
- Path(__file__).resolve().parents[1] / "DreamcoderPi/.pi/agent/scripts/pi-theme.sh"
- ).read_text()
- assert "DREAMCODER_THEME_PROFILE" in script
- assert 'variant="night"' in script or 'variant="${profile}"' in script
- assert "dreamcoder-night.json" in script or "dreamcoder-$variant.json" in script
-
- def test_pi_theme_script_fails_closed_on_invalid_profile(self) -> None:
- script = (
- Path(__file__).resolve().parents[1] / "DreamcoderPi/.pi/agent/scripts/pi-theme.sh"
- ).read_text()
- assert "invalid render profile" in script
-
-
-# ---------------------------------------------------------------------------
-# Standard-dark-substitution detection (R5)
-# ---------------------------------------------------------------------------
-
-
-def test_apply_theme_script_has_no_standard_dark_night_leak() -> None:
- """The shell selector must not substitute standard dark while reporting night.
-
- The night branch of the Kanagawa bridge and the VARIANT-based artifact
- selection must differ from the standard dark values.
- """
- script = (Path(__file__).resolve().parents[1] / "scripts/apply-theme-mode.sh").read_text()
- # Night selectors are driven by VARIANT, never hardcoded to the dark name.
- assert 'VARIANT="${MODE}"' in script
- assert '[[ "${PROFILE}" == "night" ]] && VARIANT="night"' in script
- # The night Kanagawa palette is distinct from the standard dark palette.
- assert '@ukiyo-color-text "#c6c6c6"' in script
- assert '@ukiyo-color-text "#E6E6E6"' in script
-
-
-def test_coverage_night_artifacts_all_distinct_from_dark() -> None:
- """Every declared Night artifact carries a night marker (no dark reuse).
-
- The ``opencode`` row is the documented stable-ID exception (design §5 row
- 5): the active ``dreamcoder.json`` is overwritten with Night bytes and no
- ``dreamcoder-night.json`` sibling exists — its selection strategy carries
- the Night identity instead of the artifact name.
- """
- for row in sync.COVERAGE:
- artifact = row.night_artifact
- if row.consumer_id == "opencode" or artifact.startswith("active:"):
- assert "night" in row.selection_strategy.lower()
- continue
- assert re.search(r"-night\b|-Night\b|night", artifact), artifact
- assert "dark" not in artifact.split("/")[-1] or "night" in artifact
diff --git a/tests/test_night_palette.py b/tests/test_night_palette.py
deleted file mode 100644
index ecc184dd..00000000
--- a/tests/test_night_palette.py
+++ /dev/null
@@ -1,339 +0,0 @@
-"""RED/GREEN tests for the deterministic Night transform (tasks 2.3/2.4).
-
-Contract under test (design §2, ADR-003):
-
-- ``night_palette(base, profile_parameters, guardrails) -> dict[str, str]``
- never mutates its input, preserves every palette token key (excluding canonical
- mode metadata), and returns lowercase hex.
-- The derived display name is "Dreamcoder Dark Night" while
- ``details`` stays "darker".
-- HSL lightness/saturation are multiplied by the canonical factors with
- deterministic integer-rounded RGB; ``rgba()`` tokens preserve alpha exactly.
-- Input aliases (e.g. ``selection == selection_bg``) stay exact in the output.
-- A bounded corrective pass moves only declared foreground tokens toward the
- contrast-safe endpoint, never brightens background/surface roles, and never
- exceeds ``maximum_corrective_delta`` of lightness movement.
-- New pure ``#000000``/``#ffffff`` values are rejected for functional roles;
- explicitly authored policy-approved black canvas/on-color roles are preserved.
-- The transform never silently falls back to the standard dark palette.
-
-The HSL helpers below are test-side color plumbing (a generic conversion, not
-the SAPC/APCA formula); the canonical contrast math is imported from the
-package.
-"""
-
-import json
-from pathlib import Path
-
-import pytest
-
-from dreamcoder_theme._math import apca_lc
-from dreamcoder_theme.palette import night_palette
-
-ROOT = Path(__file__).resolve().parents[1]
-THEME_ROOT = ROOT / "DreamcoderThemes" / "dreamcoder"
-
-CANONICAL_PARAMS = {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
-}
-
-
-def _tokens() -> dict:
- return json.loads((THEME_ROOT / "tokens.json").read_text())
-
-
-def _guardrails() -> dict[str, float]:
- tokens = _tokens()
- return {k: v for k, v in tokens["guardrails"].items() if isinstance(v, (int, float))}
-
-
-def _dark_base(**overrides: str) -> dict[str, str]:
- pal = dict(_tokens()["modes"]["dark"])
- pal.update(overrides)
- return pal
-
-
-# ---------------------------------------------------------------------------
-# Test-side HSL plumbing (generic color conversion; deterministic round-half).
-# ---------------------------------------------------------------------------
-
-
-def _rgb(v: str) -> tuple[int, int, int]:
- v = v.lstrip("#")
- return int(v[0:2], 16), int(v[2:4], 16), int(v[4:6], 16)
-
-
-def _hsl(v: str) -> tuple[float, float, float]:
- r, g, b = (c / 255 for c in _rgb(v))
- mx, mn = max(r, g, b), min(r, g, b)
- lightness = (mx + mn) / 2
- delta = mx - mn
- if delta == 0:
- return 0.0, 0.0, lightness
- saturation = delta / (1 - abs(2 * lightness - 1))
- if mx == r:
- hue = 60 * (((g - b) / delta) % 6)
- elif mx == g:
- hue = 60 * ((b - r) / delta + 2)
- else:
- hue = 60 * ((r - g) / delta + 4)
- return hue, saturation, lightness
-
-
-def _pure_transform(v: str, params: dict[str, float]) -> str:
- """The deterministic HSL-reduction applied by night_palette to one hex."""
- hue, sat, light = _hsl(v)
- return _hex_from_hsl(
- hue, sat * params["saturation_factor"], light * params["brightness_factor"]
- )
-
-
-def _hex_from_hsl(hue: float, sat: float, light: float) -> str:
- c = (1 - abs(2 * light - 1)) * sat
- x = c * (1 - abs((hue / 60) % 2 - 1))
- m = light - c / 2
- if hue < 60:
- r1, g1, b1 = c, x, 0.0
- elif hue < 120:
- r1, g1, b1 = x, c, 0.0
- elif hue < 180:
- r1, g1, b1 = 0.0, c, x
- elif hue < 240:
- r1, g1, b1 = 0.0, x, c
- elif hue < 300:
- r1, g1, b1 = x, 0.0, c
- else:
- r1, g1, b1 = c, 0.0, x
- r = round((r1 + m) * 255)
- g = round((g1 + m) * 255)
- b = round((b1 + m) * 255)
- return "#" + "".join(f"{max(0, min(255, ch)):02x}" for ch in (r, g, b))
-
-
-# ---------------------------------------------------------------------------
-# Contract tests
-# ---------------------------------------------------------------------------
-
-
-def test_night_preserves_token_keys():
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert set(night) == {key for key, value in base.items() if isinstance(value, str)}
-
-
-def test_night_never_mutates_input():
- base = _dark_base()
- snapshot = dict(base)
- night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert base == snapshot
-
-
-def test_night_metadata_and_details():
- night = night_palette(_dark_base(), CANONICAL_PARAMS, _guardrails())
- assert night["name"] == "Dreamcoder Dark Night"
- assert night["details"] == "darker"
-
-
-def test_night_outputs_lowercase_hex():
- night = night_palette(_dark_base(), CANONICAL_PARAMS, _guardrails())
- for key, value in night.items():
- if value.startswith("#"):
- assert value == value.lower(), f"{key} not lowercase: {value}"
-
-
-def test_night_preserves_exact_aliases():
- """selection == selection_bg (and every input alias) stays exact."""
- night = night_palette(_dark_base(), CANONICAL_PARAMS, _guardrails())
- assert night["selection"] == night["selection_bg"]
- assert night["subtle"] == night["disabled"] # aliased in canonical dark
- assert night["bg"] == night["prompt_bg"]
- assert night["text"] == night["on_surface"]
-
-
-def test_night_preserves_rgba_alpha_exactly():
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- for key in ("panel_rgba", "module_rgba", "active_rgba", "inactive_border", "overlay", "scrim"):
- value = base[key]
- assert value.startswith("rgba("), key
- alpha = value.rsplit(",", 1)[1].rstrip(")")
- assert night[key].rsplit(",", 1)[1].rstrip(")") == alpha
- assert night[key].endswith(")")
-
-
-def test_night_reduces_lightness_and_saturation():
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert night["bg"] == base["bg"]
- assert _hsl(night["surface1"])[2] < _hsl(base["surface1"])[2]
- assert _hsl(night["accent"])[2] < _hsl(base["accent"])[2]
- assert _hsl(night["accent"])[1] < _hsl(base["accent"])[1]
-
-
-def test_night_is_byte_identical_across_runs():
- base = _dark_base()
- first = night_palette(base, CANONICAL_PARAMS, _guardrails())
- second = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert first == second
-
-
-def test_night_only_preserves_pure_black_for_explicit_oled_roles():
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- pure_black_keys = {key for key, value in night.items() if value == base["bg"]}
- hex_tokens = {value for value in night.values() if value.startswith("#")}
-
- assert pure_black_keys == {"bg", "prompt_bg", "on_accent", "on_error", "on_focus"}
- assert "#ffffff" not in hex_tokens
-
-
-def test_night_preserves_explicit_oled_black_roles():
- base = _dark_base()
- for key in ("bg", "prompt_bg", "on_accent", "on_error", "on_focus"):
- base[key] = "#000000"
-
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
-
- for key in ("bg", "prompt_bg", "on_accent", "on_error", "on_focus"):
- assert night[key] == "#000000"
-
-
-@pytest.mark.parametrize("token", ["#000000"])
-def test_night_rejects_pure_black_on_functional_roles(token):
- base = _dark_base()
- base["hover"] = token
- with pytest.raises(ValueError):
- night_palette(base, CANONICAL_PARAMS, _guardrails())
-
-
-def test_night_transforms_white_away_from_pure_white():
- """A pure-white input is dimmed (L*brightness < 1), never emitted white."""
- base = _dark_base()
- base["hover"] = "#ffffff"
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert night["hover"] != "#ffffff"
- assert _hsl(night["hover"])[2] < 1.0
-
-
-def test_night_parameter_bounds_are_enforced():
- base = _dark_base()
- g = _guardrails()
- bad_params = [
- {
- "brightness_factor": 0,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 1.01,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 0,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 1.01,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": -0.01,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.21,
- "corrective_step": 0.02,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0,
- },
- {
- "brightness_factor": 0.86,
- "saturation_factor": 0.72,
- "maximum_corrective_delta": 0.12,
- "corrective_step": 0.02,
- "extra": 1,
- },
- ]
- for params in bad_params:
- with pytest.raises(ValueError):
- night_palette(base, params, g)
-
-
-def test_night_corrective_movement_is_capped_at_maximum_delta():
- """A token below its floor may be corrected only up to the bound."""
- base = _dark_base(subtle="#2f3a44")
- g = _guardrails()
- night = night_palette(base, CANONICAL_PARAMS, g)
- pure_lightness = _hsl(_pure_transform("#2f3a44", CANONICAL_PARAMS))[2]
- moved = _hsl(night["subtle"])[2] - pure_lightness
- assert 0 <= moved <= CANONICAL_PARAMS["maximum_corrective_delta"] + 1e-9
- # The deficit is large enough that the bound cannot reach the quiet floor:
- assert abs(apca_lc(night["subtle"], night["bg"])) < g["minimum_apca_quiet"]
-
-
-def test_night_bounded_correction_restores_a_floor():
- """A small deficit is fixed within the bound (R4 scenario)."""
- base = _dark_base(subtle="#7e8c9e")
- g = _guardrails()
- night = night_palette(base, CANONICAL_PARAMS, g)
- assert abs(apca_lc(night["subtle"], night["bg"])) >= g["minimum_apca_quiet"]
- # And the correction respected the bound: lightness moved at most max_delta.
- moved = _hsl(night["subtle"])[2] - _hsl(_pure_transform("#7e8c9e", CANONICAL_PARAMS))[2]
- assert 0 <= moved <= CANONICAL_PARAMS["maximum_corrective_delta"] + 1e-9
-
-
-def test_night_never_brightens_backgrounds():
- """Correction touches only declared pairs, never background/surface roles."""
- base = _dark_base(subtle="#2f3a44")
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- for key in (
- "bg",
- "bg_soft",
- "surface0",
- "surface1",
- "surface2",
- "surface3",
- "selection_bg",
- "hover",
- "pressed",
- ):
- assert night[key] == _pure_transform(base[key], CANONICAL_PARAMS), (
- f"background {key} was corrected"
- )
-
-
-def test_night_has_no_silent_standard_dark_fallback():
- """The transform never returns the base palette as its output."""
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert night != base
- # An unfixable deficit still yields the bounded transform, never `base`.
- broken = _dark_base(subtle="#1a1a1a")
- night_broken = night_palette(broken, CANONICAL_PARAMS, _guardrails())
- assert night_broken != broken
-
-
-def test_night_derivation_differs_from_standard_dark():
- """The Night palette must not silently substitute standard dark bytes."""
- base = _dark_base()
- night = night_palette(base, CANONICAL_PARAMS, _guardrails())
- assert night["surface1"] != base["surface1"]
- assert night["text"] != base["text"]
diff --git a/tests/test_palette_dual_gate.py b/tests/test_palette_dual_gate.py
index 398487e7..30624388 100644
--- a/tests/test_palette_dual_gate.py
+++ b/tests/test_palette_dual_gate.py
@@ -48,7 +48,7 @@ def test_wcag_pass_apca_fail_is_blocking():
assert contrast(pal["bg"], pal["diagnostic"]) >= 4.5
assert abs(apca_lc(pal["diagnostic"], pal["bg"])) < _guardrails()["minimum_apca_body_dark"]
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("APCA fail" in e and "diagnostic/bg" in e for e in errors)
assert not any("WCAG fail" in e and "diagnostic/bg" in e for e in errors)
@@ -61,7 +61,7 @@ def test_apca_pass_wcag_fail_is_blocking():
assert abs(apca_lc(pal["diagnostic"], pal["bg"])) >= _guardrails()["minimum_apca_body_dark"]
assert contrast(pal["bg"], pal["diagnostic"]) < 4.5
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("WCAG fail" in e and "diagnostic/bg" in e for e in errors)
assert not any("APCA fail" in e and "diagnostic/bg" in e for e in errors)
@@ -74,7 +74,7 @@ def test_light_wcag_pass_apca_fail_is_blocking():
assert contrast(pal["bg"], pal["diagnostic"]) >= 4.5
assert abs(apca_lc(pal["diagnostic"], pal["bg"])) < _guardrails()["minimum_apca_body"]
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="light")
+ errors = validate_palette(pal, _guardrails(), mode="light")
assert any("APCA fail" in e and "diagnostic/bg" in e for e in errors)
assert not any("WCAG fail" in e and "diagnostic/bg" in e for e in errors)
@@ -84,7 +84,7 @@ def test_both_metric_failures_accumulate():
"""A pair failing both metrics yields both diagnostics, not a short-circuit."""
pal = _clean_palette("dark")
pal["diagnostic"] = "#4a4a4a" # WCAG 2.23 and APCA 11.8 — both fail
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("WCAG fail" in e and "diagnostic/bg" in e for e in errors)
assert any("APCA fail" in e and "diagnostic/bg" in e for e in errors)
@@ -93,27 +93,26 @@ def test_both_metric_failures_accumulate():
def test_diagnostic_carries_guardrail_key_and_value():
pal = _clean_palette("dark")
pal["diagnostic"] = "#7b7b7b"
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
apca_errors = [e for e in errors if "APCA fail" in e and "diagnostic/bg" in e]
assert len(apca_errors) == 1
assert "minimum_apca_body_dark" in apca_errors[0]
assert "=50" in apca_errors[0]
assert "mode=dark" in apca_errors[0]
- assert "profile=standard" in apca_errors[0]
def test_missing_apca_guardrail_key_fails_closed():
pal = _clean_palette("dark")
guardrails = dict(_guardrails())
del guardrails["minimum_apca_body_dark"]
- errors = validate_palette(pal, guardrails, profile="standard", mode="dark")
+ errors = validate_palette(pal, guardrails, mode="dark")
assert any("missing guardrail key: minimum_apca_body_dark" in e for e in errors)
def test_mode_derives_from_palette_when_omitted():
pal = _clean_palette("dark")
pal["diagnostic"] = "#7b7b7b"
- errors = validate_palette(pal, _guardrails(), profile="standard")
+ errors = validate_palette(pal, _guardrails())
assert any("mode=dark" in e for e in errors if "APCA fail" in e)
@@ -126,7 +125,7 @@ def test_near_invisible_quiet_pair_fails_wcag_despite_apca_boost():
assert contrast(pal["bg"], pal["subtle"]) < 4.5
assert abs(apca_lc(pal["subtle"], pal["bg"])) >= _guardrails()["minimum_apca_quiet"]
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("WCAG fail" in e and "subtle/bg" in e for e in errors)
@@ -136,7 +135,7 @@ def test_every_declared_pair_requires_both_metrics():
floor — an APCA pass alone never waives WCAG (ADR-002)."""
pal = _clean_palette("dark")
pal["border_ui"] = "#1b1b1b" # APCA 57.1 >= ui_dark 28, WCAG 1.15 < 4.5
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("WCAG fail" in e and "border_ui/bg" in e for e in errors)
assert not any("APCA fail" in e and "border_ui/bg" in e for e in errors)
@@ -144,7 +143,7 @@ def test_every_declared_pair_requires_both_metrics():
def test_invalid_mode_is_rejected():
pal = _clean_palette("dark")
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="bogus")
+ errors = validate_palette(pal, _guardrails(), mode="bogus")
assert any("invalid mode: bogus" in e for e in errors)
@@ -153,7 +152,7 @@ def test_dusk_uses_light_floors():
floors, never the weaker dark floors (design class table)."""
pal = _clean_palette("dark")
pal["diagnostic"] = "#7b7b7b" # APCA 32.2 < body 75 (light floor) and < body_dark 50
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dusk")
+ errors = validate_palette(pal, _guardrails(), mode="dusk")
apca_errors = [e for e in errors if "APCA fail" in e and "diagnostic/bg" in e]
assert len(apca_errors) == 1
assert "minimum_apca_body" in apca_errors[0]
@@ -163,5 +162,5 @@ def test_dusk_uses_light_floors():
def test_missing_declared_pair_token_is_reported():
pal = _clean_palette("dark")
pal.pop("text_heading")
- errors = validate_palette(pal, _guardrails(), profile="standard", mode="dark")
+ errors = validate_palette(pal, _guardrails(), mode="dark")
assert any("missing token: text_heading (declared heading pair)" in e for e in errors)
diff --git a/tests/test_render_profile_settings.py b/tests/test_render_profile_settings.py
deleted file mode 100644
index 79278fc4..00000000
--- a/tests/test_render_profile_settings.py
+++ /dev/null
@@ -1,156 +0,0 @@
-"""Settings contract for ``theme.render_profile`` (Phase 4, tasks 4.1/4.2/4.3).
-
-Covers R6 scenarios: schema entry with closed values, invalid-value rejection,
-unknown-setting preservation, schema default, environment-override precedence
-without mutation, persisted resolution, and the ``profile == night -> mode ==
-dark`` effective-base conflict rule (design §3, ADR-003).
-"""
-
-from __future__ import annotations
-
-import json
-from pathlib import Path
-
-import pytest
-
-from dreamcoder_theme import settings as settings_mod
-from dreamcoder_theme import settings_store
-
-
-@pytest.fixture
-def settings_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
- """Isolate persisted settings under a temp config home."""
- monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config"))
- monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / ".local" / "share"))
- monkeypatch.setenv("HOME", str(tmp_path))
- monkeypatch.delenv("DREAMCODER_THEME_PROFILE", raising=False)
- monkeypatch.delenv("DREAMCODER_THEME_MODE", raising=False)
- return tmp_path
-
-
-def _persist(settings_home: Path, key: str, value: str) -> None:
- path = settings_home / ".config" / "dreamcoder" / "settings.json"
- data = json.loads(path.read_text()) if path.exists() else {}
- cursor = data
- parts = key.split(".")
- for part in parts[:-1]:
- cursor = cursor.setdefault(part, {})
- cursor[parts[-1]] = value
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
-
-
-# ---------------------------------------------------------------------------
-# 4.1 RED — schema entry
-# ---------------------------------------------------------------------------
-
-
-def test_schema_declares_render_profile_with_standard_default(settings_home: Path) -> None:
- """``theme.render_profile`` is a typed schema entry defaulting to standard."""
- spec = settings_store.SETTINGS_SCHEMA["theme.render_profile"]
- assert spec["type"] == "string"
- assert spec["enum"] == ["standard", "night"]
- assert spec["default"] == "standard"
-
-
-def test_render_profile_accepts_standard_and_night(settings_home: Path) -> None:
- """Both closed values validate and round-trip through settings_set/get."""
- for value in ("standard", "night"):
- assert settings_store.validate_setting_value("theme.render_profile", value) == []
- result = settings_store.settings_set("theme.render_profile", value)
- assert result["key"] == "theme.render_profile"
- assert settings_store.settings_get("theme.render_profile") == value
-
-
-def test_render_profile_rejects_invalid_value(settings_home: Path) -> None:
- """An unknown profile value is rejected by settings_set (never persisted)."""
- with pytest.raises(ValueError, match=r"theme\.render_profile must be one of"):
- settings_store.settings_set("theme.render_profile", "neon")
- assert settings_store.settings_get("theme.render_profile") is None
-
-
-def test_unknown_settings_preserved_with_warnings(settings_home: Path) -> None:
- """Unknown settings produce warnings and remain preserved (forward compat)."""
- path = settings_home / ".config" / "dreamcoder" / "settings.json"
- path.parent.mkdir(parents=True)
- path.write_text(json.dumps({"future": {"option": 42}}))
- report = settings_store.validate_settings()
- assert report["valid"] is True
- keys = [warning["key"] for warning in report["warnings"]]
- assert "future.option" in keys
- assert json.loads(path.read_text()) == {"future": {"option": 42}}
-
-
-# ---------------------------------------------------------------------------
-# 4.1/4.3 RED — resolver precedence
-# ---------------------------------------------------------------------------
-
-
-def test_default_profile_is_standard(settings_home: Path) -> None:
- """Absent env override and absent persisted setting resolve to standard."""
- assert settings_mod.render_profile() == "standard"
-
-
-def test_persisted_profile_is_resolved(settings_home: Path) -> None:
- """Persisted ``theme.render_profile=night`` resolves absent an override."""
- _persist(settings_home, "theme.render_profile", "night")
- assert settings_mod.render_profile() == "night"
-
-
-def test_env_override_wins_without_mutation(
- settings_home: Path, monkeypatch: pytest.MonkeyPatch
-) -> None:
- """Process-only env override wins and never mutates the persisted value."""
- _persist(settings_home, "theme.render_profile", "night")
- monkeypatch.setenv("DREAMCODER_THEME_PROFILE", "standard")
- assert settings_mod.render_profile() == "standard"
- # Persisted value untouched.
- assert settings_store.settings_get("theme.render_profile") == "night"
-
-
-def test_env_profile_invalid_fails_closed(
- settings_home: Path, monkeypatch: pytest.MonkeyPatch
-) -> None:
- """An invalid env profile fails closed instead of being interpreted."""
- monkeypatch.setenv("DREAMCODER_THEME_PROFILE", "dusk")
- with pytest.raises(SystemExit, match=r"DREAMCODER_THEME_PROFILE must be 'standard' or 'night'"):
- settings_mod.render_profile()
-
-
-def test_persisted_invalid_profile_fails_closed(settings_home: Path) -> None:
- """An invalid persisted value fails closed, never a runtime profile."""
- _persist(settings_home, "theme.render_profile", "dusk")
- with pytest.raises(SystemExit, match=r"theme\.render_profile"):
- settings_mod.render_profile()
-
-
-# ---------------------------------------------------------------------------
-# 4.3 RED — effective-base resolver (ADR-003 invariant)
-# ---------------------------------------------------------------------------
-
-
-def test_effective_base_mode_night_requires_dark(
- settings_home: Path, monkeypatch: pytest.MonkeyPatch
-) -> None:
- """profile=night + mode=dark resolves to dark base."""
- monkeypatch.setenv("DREAMCODER_THEME_MODE", "dark")
- monkeypatch.setenv("DREAMCODER_THEME_PROFILE", "night")
- assert settings_mod.effective_base_mode() == "dark"
-
-
-def test_effective_base_mode_conflict_fails_actionably(
- settings_home: Path, monkeypatch: pytest.MonkeyPatch
-) -> None:
- """MODE=light + PROFILE=night fails with an actionable error (no Dusk, no coercion)."""
- monkeypatch.setenv("DREAMCODER_THEME_MODE", "light")
- monkeypatch.setenv("DREAMCODER_THEME_PROFILE", "night")
- with pytest.raises(SystemExit, match="requires base mode 'dark'"):
- settings_mod.effective_base_mode()
-
-
-def test_effective_base_mode_standard_keeps_light(
- settings_home: Path, monkeypatch: pytest.MonkeyPatch
-) -> None:
- """Standard profile leaves the light/dark base untouched."""
- monkeypatch.setenv("DREAMCODER_THEME_MODE", "light")
- assert settings_mod.effective_base_mode() == "light"
diff --git a/tests/test_renderer_contract.py b/tests/test_renderer_contract.py
index 8baca3cf..5fd638b2 100644
--- a/tests/test_renderer_contract.py
+++ b/tests/test_renderer_contract.py
@@ -23,8 +23,7 @@
def test_canonical_modes_are_distinct_from_derived_render_variants() -> None:
assert {"light", "dusk", "dark"} == CANONICAL_MODES
- assert {"light", "dark", "night"} == ALL_RENDER_VARIANTS
- assert "night" not in CANONICAL_MODES
+ assert {"light", "dark"} == ALL_RENDER_VARIANTS
assert "dusk" not in ALL_RENDER_VARIANTS
@@ -70,7 +69,7 @@ def adapters(cls):
TransparentOpenCodeAdapter(),
NvimDispatcherAdapter(),
NamedZellijAdapter("dreamcoder"),
- VersionedHerdrAdapter(complete, "night"),
+ VersionedHerdrAdapter(complete, "dark"),
)
def test_every_adapter_conforms_to_the_one_contract(self, adapters) -> None:
diff --git a/tests/test_renderer_registry.py b/tests/test_renderer_registry.py
index 9caa4802..d7561c27 100644
--- a/tests/test_renderer_registry.py
+++ b/tests/test_renderer_registry.py
@@ -55,7 +55,7 @@ def make_registration(
consumer_id: str = "test_consumer",
renderer: object = kitty_content,
contract_version: int = SUPPORTED_CONTRACT_VERSION,
- modes: frozenset[str] = frozenset({"dark", "light", "night"}),
+ modes: frozenset[str] = frozenset({"dark", "light"}),
output_kind: str = "active",
sync: SyncDefinition | None = None,
summary_label: str = "Test consumer",
diff --git a/tests/test_renderers_hypr_rgba.py b/tests/test_renderers_hypr_rgba.py
index 0e67521d..49d66d78 100644
--- a/tests/test_renderers_hypr_rgba.py
+++ b/tests/test_renderers_hypr_rgba.py
@@ -21,7 +21,7 @@ def test_light_inactive_border(self) -> None:
# 3-digit channels must be clamped to 2 hex digits, not padded.
self.assertEqual(_rgba_to_argb("rgba(138, 115, 88, 0.93)"), "rgba(8a7358ed)")
- def test_night_inactive_border(self) -> None:
+ def test_muted_translucent_border(self) -> None:
self.assertEqual(_rgba_to_argb("rgba(167, 148, 122, 0.87)"), "rgba(a7947ade)")
def test_all_channels_large(self) -> None:
diff --git a/tests/test_settings_legacy_render_profile.py b/tests/test_settings_legacy_render_profile.py
new file mode 100644
index 00000000..480a5c75
--- /dev/null
+++ b/tests/test_settings_legacy_render_profile.py
@@ -0,0 +1,91 @@
+"""Legacy ``theme.render_profile`` tolerance after the Night profile removal.
+
+Dreamcoder ships only Light and Dark. Settings files written by older
+versions may still carry ``theme.render_profile`` (``standard``/``night``);
+loading them must never crash, and the stale key is treated like any other
+unknown setting: reported as a warning and preserved untouched.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+import pytest
+
+from dreamcoder_theme import settings as settings_mod
+from dreamcoder_theme import settings_store
+
+
+@pytest.fixture
+def settings_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
+ """Isolate persisted settings and the mode cache under a temp home."""
+ monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config"))
+ monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path / ".cache"))
+ monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / ".local" / "share"))
+ monkeypatch.setenv("HOME", str(tmp_path))
+ monkeypatch.delenv("DREAMCODER_THEME_MODE", raising=False)
+ return tmp_path
+
+
+def _write_settings(settings_home: Path, data: dict) -> Path:
+ path = settings_home / ".config" / "dreamcoder" / "settings.json"
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
+ return path
+
+
+def test_schema_no_longer_declares_render_profile() -> None:
+ assert "theme.render_profile" not in settings_store.SETTINGS_SCHEMA
+ assert not hasattr(settings_mod, "render_profile")
+
+
+@pytest.mark.parametrize("legacy_value", ["standard", "night"])
+def test_persisted_legacy_render_profile_loads_as_unknown_setting(
+ settings_home: Path, legacy_value: str
+) -> None:
+ """A stale key validates (warning only) and is preserved byte-for-byte."""
+ path = _write_settings(
+ settings_home,
+ {"terminal": {"default_mode": "dark"}, "theme": {"render_profile": legacy_value}},
+ )
+ before = path.read_bytes()
+
+ report = settings_store.validate_settings()
+
+ assert report["valid"] is True
+ assert report["errors"] == []
+ assert [w["key"] for w in report["warnings"]] == ["theme.render_profile"]
+ assert settings_store.settings_get("terminal.default_mode") == "dark"
+ assert settings_store.settings_get("theme.render_profile") == legacy_value
+ assert path.read_bytes() == before
+
+
+def test_legacy_render_profile_does_not_affect_theme_mode(
+ settings_home: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ """A persisted legacy ``night`` value never selects a mode."""
+ _write_settings(settings_home, {"theme": {"render_profile": "night"}})
+ assert settings_mod.theme_mode() == "dark"
+ monkeypatch.setenv("DREAMCODER_THEME_MODE", "light")
+ assert settings_mod.theme_mode() == "light"
+
+
+def test_writing_another_setting_keeps_legacy_key(settings_home: Path) -> None:
+ """Updating a known key never crashes on, or drops, the stale key."""
+ _write_settings(settings_home, {"theme": {"render_profile": "night"}})
+ settings_store.settings_set("terminal.default_mode", "light")
+ assert settings_store.settings_get() == {
+ "terminal": {"default_mode": "light"},
+ "theme": {"render_profile": "night"},
+ }
+
+
+def test_unknown_settings_preserved_with_warnings(settings_home: Path) -> None:
+ """Unknown settings produce warnings and remain preserved (forward compat)."""
+ path = _write_settings(settings_home, {"future": {"option": 42}})
+ report = settings_store.validate_settings()
+ assert report["valid"] is True
+ keys = [warning["key"] for warning in report["warnings"]]
+ assert "future.option" in keys
+ assert json.loads(path.read_text()) == {"future": {"option": 42}}
diff --git a/tests/test_tokens_schema_night.py b/tests/test_tokens_schema_night.py
deleted file mode 100644
index 7dbb2c11..00000000
--- a/tests/test_tokens_schema_night.py
+++ /dev/null
@@ -1,129 +0,0 @@
-"""Schema contract for the canonical `render_profiles.night` parameters (task 2.2).
-
-`tokens.schema.json` must require `render_profiles.night` and bound its four
-parameters: brightness/saturation factors in (0, 1], `maximum_corrective_delta`
-in [0, 0.20], and `corrective_step` in (0, maximum_corrective_delta]. JSON
-Schema cannot express the relative `corrective_step <= maximum_corrective_delta`
-bound, so the schema bounds `corrective_step` to (0, 0.20] and the cross-field
-invariant is enforced by the runtime loader (task 2.5) and asserted here.
-"""
-
-import copy
-import json
-from pathlib import Path
-
-import jsonschema
-import pytest
-
-ROOT = Path(__file__).resolve().parents[1]
-THEME_ROOT = ROOT / "DreamcoderThemes" / "dreamcoder"
-
-NIGHT_KEYS = (
- "brightness_factor",
- "saturation_factor",
- "maximum_corrective_delta",
- "corrective_step",
-)
-
-
-@pytest.fixture(scope="module")
-def schema() -> dict:
- return json.loads((THEME_ROOT / "tokens.schema.json").read_text())
-
-
-@pytest.fixture(scope="module")
-def tokens() -> dict:
- return json.loads((THEME_ROOT / "tokens.json").read_text())
-
-
-@pytest.fixture(scope="module")
-def validator(schema: dict) -> jsonschema.Draft202012Validator:
- return jsonschema.Draft202012Validator(schema)
-
-
-def test_render_profiles_required_in_schema(schema):
- assert "render_profiles" in schema["required"]
-
-
-def test_render_profiles_requires_night(schema):
- assert "night" in schema["properties"]["render_profiles"]["required"]
-
-
-def test_night_requires_all_four_parameters(schema):
- required = set(schema["properties"]["render_profiles"]["properties"]["night"]["required"])
- assert set(NIGHT_KEYS) <= required
-
-
-def test_tokens_json_validates_against_schema(validator, tokens):
- validator.validate(tokens)
-
-
-def test_tokens_json_contains_canonical_night_parameters(tokens):
- night = tokens["render_profiles"]["night"]
- assert night["brightness_factor"] == 0.86
- assert night["saturation_factor"] == 0.72
- assert night["maximum_corrective_delta"] == 0.12
- assert night["corrective_step"] == 0.02
-
-
-def test_missing_render_profiles_fails_validation(schema, tokens):
- broken = copy.deepcopy(tokens)
- del broken["render_profiles"]
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_night_without_night_profile_fails_validation(schema, tokens):
- broken = copy.deepcopy(tokens)
- del broken["render_profiles"]["night"]
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-@pytest.mark.parametrize("key", NIGHT_KEYS)
-def test_night_missing_parameter_fails_validation(schema, tokens, key):
- broken = copy.deepcopy(tokens)
- del broken["render_profiles"]["night"][key]
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_brightness_factor_must_be_in_open_unit_interval(schema, tokens):
- for bad in (0, 1.01):
- broken = copy.deepcopy(tokens)
- broken["render_profiles"]["night"]["brightness_factor"] = bad
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_saturation_factor_must_be_in_open_unit_interval(schema, tokens):
- for bad in (0, 1.5):
- broken = copy.deepcopy(tokens)
- broken["render_profiles"]["night"]["saturation_factor"] = bad
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_maximum_corrective_delta_bounds(schema, tokens):
- for bad in (-0.01, 0.21):
- broken = copy.deepcopy(tokens)
- broken["render_profiles"]["night"]["maximum_corrective_delta"] = bad
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_corrective_step_bounds(schema, tokens):
- for bad in (0, 0.25):
- broken = copy.deepcopy(tokens)
- broken["render_profiles"]["night"]["corrective_step"] = bad
- with pytest.raises(jsonschema.ValidationError):
- jsonschema.Draft202012Validator(schema).validate(broken)
-
-
-def test_corrective_step_must_not_exceed_maximum_corrective_delta(tokens):
- """Cross-field bound the schema cannot express: step (0, max_delta].
-
- The runtime loader (task 2.5) enforces the same invariant fail-closed.
- """
- night = tokens["render_profiles"]["night"]
- assert 0 < night["corrective_step"] <= night["maximum_corrective_delta"]