diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..d72dccf
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,28 @@
+# Dreamcoder Dots — CODEOWNERS
+# Defines who is responsible for reviewing changes to each area.
+
+* @Dreamcoder08
+
+# Python theme engine
+/src/dreamcoder_theme/ @Dreamcoder08
+
+# Go installer
+/installer/ @Dreamcoder08
+
+# CI/CD and GitHub config
+/.github/ @Dreamcoder08
+
+# Documentation
+/docs/ @Dreamcoder08
+
+# Shell scripts
+/scripts/*.sh @Dreamcoder08
+
+# Shell configs
+/Shell/ @Dreamcoder08
+
+# Terminal configs
+/Kitty/ @Dreamcoder08
+/Ghostty/ @Dreamcoder08
+/Tmux/ @Dreamcoder08
+/Zellij/ @Dreamcoder08
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..1fac3ae
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,64 @@
+name: Bug Report
+description: Create a report to help improve Dreamcoder OS
+title: '[Bug]: '
+labels: ['bug']
+projects: []
+assignees:
+ - Dreamcoder08
+
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: A clear and concise description of the bug
+ placeholder: When I run `dreamcoder light`, the colors don't update...
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to Reproduce
+ description: How can we reproduce this?
+ placeholder: |
+ 1. Run `dreamcoder dark`
+ 2. Open Kitty terminal
+ 3. Colors are wrong
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected Behavior
+ description: What did you expect to happen?
+ validations:
+ required: true
+
+ - type: textarea
+ id: environment
+ attributes:
+ label: Environment
+ description: Fill in your environment details
+ value: |
+ - OS: [e.g. Arch Linux]
+ - Terminal: [e.g. Kitty, Ghostty]
+ - Shell: [e.g. Fish, Zsh]
+ - Dreamcoder version: [e.g. 2.0.0]
+ - Gentleman.Dots installed: [yes/no]
+ - ML4W installed: [yes/no]
+ validations:
+ required: true
+
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant Log Output
+ description: Run `dreamcoder doctor` and paste the output
+ render: text
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..891fd90
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Dreamcoder OS Docs
+ url: https://github.com/Dreamcoder08/Dreamcoder_dots/blob/main/README.md
+ about: Check the documentation first
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..4c7dc1d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,36 @@
+name: Feature Request
+description: Suggest an idea for Dreamcoder OS
+title: '[Feature]: '
+labels: ['enhancement']
+projects: []
+assignees:
+ - Dreamcoder08
+
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for suggesting a feature!
+
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem Statement
+ description: What problem does this feature solve?
+ placeholder: I'm always frustrated when...
+ validations:
+ required: true
+
+ - type: textarea
+ id: solution
+ attributes:
+ label: Proposed Solution
+ description: What would you like to see?
+ validations:
+ required: true
+
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives Considered
+ description: What alternatives have you considered?
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..82d4f11
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,29 @@
+## Description
+
+
+
+## Type of Change
+
+- [ ] feat: new feature
+- [ ] fix: bug fix
+- [ ] docs: documentation
+- [ ] style: code style (formatting, etc)
+- [ ] refactor: code refactoring
+- [ ] perf: performance improvement
+- [ ] test: adding tests
+- [ ] chore: maintenance
+- [ ] ci: CI/CD changes
+
+## Checklist
+
+- [ ] My code follows the project's coding standards
+- [ ] I have tested these changes locally
+- [ ] Existing tests continue to pass
+- [ ] New tests have been added (if applicable)
+- [ ] Documentation has been updated (if applicable)
+
+## Screenshots (if applicable)
+
+## Related Issues
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f690341
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,33 @@
+version: 2
+updates:
+ - package-ecosystem: 'gomod'
+ directory: '/installer'
+ schedule:
+ interval: 'weekly'
+ day: 'monday'
+ commit-message:
+ prefix: 'chore(deps)'
+ labels:
+ - 'dependencies'
+ - 'go'
+
+ - package-ecosystem: 'pip'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
+ day: 'monday'
+ commit-message:
+ prefix: 'chore(deps)'
+ labels:
+ - 'dependencies'
+ - 'python'
+
+ - package-ecosystem: 'github-actions'
+ directory: '/'
+ schedule:
+ interval: 'monthly'
+ commit-message:
+ prefix: 'chore(ci)'
+ labels:
+ - 'dependencies'
+ - 'ci'
diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml
new file mode 100644
index 0000000..4425bd4
--- /dev/null
+++ b/.github/workflows/ci-go.yml
@@ -0,0 +1,40 @@
+name: CI (Go)
+
+on:
+ push:
+ branches: [main]
+ paths: ['installer/**']
+ pull_request:
+ branches: [main]
+ paths: ['installer/**']
+
+jobs:
+ go:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ go-version: ['1.26']
+
+ defaults:
+ run:
+ working-directory: installer
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Go ${{ matrix.go-version }}
+ uses: actions/setup-go@v5
+ with:
+ go-version: ${{ matrix.go-version }}
+
+ - name: Build
+ run: go build ./...
+
+ - name: Test
+ run: go test ./... -v
+
+ - name: Lint
+ uses: golangci/golangci-lint-action@v6
+ with:
+ version: latest
+ working-directory: installer
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
new file mode 100644
index 0000000..8fb0826
--- /dev/null
+++ b/.github/workflows/commitlint.yml
@@ -0,0 +1,25 @@
+name: Commitlint
+
+on:
+ pull_request:
+ branches: [main]
+
+jobs:
+ commitlint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+
+ - name: Install commitlint
+ run: |
+ npm install --save-dev @commitlint/config-conventional @commitlint/cli
+
+ - name: Lint commits
+ run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..921de2e
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,50 @@
+name: Release
+
+on:
+ push:
+ tags: ['v*']
+
+permissions:
+ contents: write
+ id-token: write
+
+jobs:
+ goreleaser:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: stable
+
+ - name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@v6
+ with:
+ distribution: goreleaser
+ version: '~> v2'
+ args: release --clean
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ build-pypi:
+ needs: [goreleaser]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ - name: Install build tools
+ run: pip install build
+ - name: Build package
+ run: python -m build
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dreamcoder-theme-pypi
+ path: dist/
diff --git a/.github/workflows/theme-validation.yml b/.github/workflows/theme-validation.yml
index 940028d..ebfe807 100644
--- a/.github/workflows/theme-validation.yml
+++ b/.github/workflows/theme-validation.yml
@@ -3,7 +3,6 @@ name: CI
on:
push:
branches: [main]
- tags: ["v*"]
pull_request:
branches: [main]
@@ -58,24 +57,4 @@ jobs:
run: |
git diff --exit-code docs/dreamcoder-theme-preview.md || (echo "::error::Theme preview not regenerated" && exit 1)
- publish:
- needs: [test]
- runs-on: ubuntu-latest
- if: startsWith(github.ref, 'refs/tags/v')
- permissions:
- id-token: write
-
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
- with:
- python-version: "3.12"
-
- - name: Install build tools
- run: pip install build
-
- - name: Build package
- run: python -m build
-
- - name: Publish to PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
+ # publish job removed — use Release workflow for PyPI publishing
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
new file mode 100644
index 0000000..39cf6bf
--- /dev/null
+++ b/.goreleaser.yaml
@@ -0,0 +1,57 @@
+# GoReleaser configuration for dreamcoder-dots installer
+# https://goreleaser.com
+#
+# Trigger: git tag v* && git push --tags
+# Pipeline: build (4 platforms) → archive → release → homebrew cask
+
+version: 2
+
+project_name: dreamcoder-dots
+
+before:
+ hooks:
+ - sh -c "cd installer && go mod tidy"
+
+builds:
+ - id: dreamcoder-dots
+ dir: installer
+ main: .
+ binary: dreamcoder-dots
+ ldflags:
+ - -X github.com/dreamcoder08/dreamcoder-dots/installer/pkg/version.Version={{ .Version }}
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ env:
+ - CGO_ENABLED=0
+
+archives:
+ - id: default
+ formats: ["tar.gz"]
+ name_template: >-
+ {{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
+ files:
+ - none*
+
+release:
+ github:
+ owner: Dreamcoder08
+ name: Dreamcoder_dots
+ header: |
+ ## Dreamcoder OS {{ .Version }}
+
+ > Token-governed visual operating layer.
+
+ ### Install
+
+ ```bash
+ # Download from this release
+ ```
+
+ ### Changes
+
+ footer: |
+ **Full Changelog**: https://github.com/Dreamcoder08/Dreamcoder_dots/compare/{{ .PreviousTag }}...{{ .Tag }}
diff --git a/.opencode/themes/dreamcoder-light.json b/.opencode/themes/dreamcoder-light.json
deleted file mode 100644
index 368cc19..0000000
--- a/.opencode/themes/dreamcoder-light.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
- "$schema": "https://opencode.ai/theme.json",
- "defs": {
- "dreamBackground": "#f3eadc",
- "dreamPanel": "#fff7ea",
- "dreamElement": "#e6d7c4",
- "dreamText": "#17120d",
- "dreamMuted": "#352e22",
- "dreamCocoa": "#a7471c",
- "dreamLucuma": "#824f16",
- "dreamDiagnostic": "#0d4a68",
- "dreamSage": "#3d723d",
- "dreamViolet": "#57478b",
- "dreamMauve": "#7d3e64",
- "dreamCoral": "#842f24",
- "dreamWarning": "#654300"
- },
- "theme": {
- "background": "none",
- "backgroundPanel": "#fff7ea",
- "backgroundElement": "#e3d2bc",
- "backgroundHover": "#c8ad89",
- "backgroundSelected": "#824f16",
- "textSelected": "#f3eadc",
- "backgroundCode": "#d0bca3",
- "backgroundSearch": "#a1895c",
- "backgroundLine": "#e6d7c4",
- "backgroundAssistant": "#d7d7ce",
- "backgroundUser": "#e2d3be",
- "backgroundTool": "#e0d6d2",
- "text": "#17120d",
- "textMuted": "#352e22",
- "textSubtle": "#554638",
- "textPlaceholder": "#725e4c",
- "textAssistant": "#0f4058",
- "textUser": "#724615",
- "textTool": "#57478b",
- "primary": "#824f16",
- "secondary": "#a7471c",
- "accent": "#824f16",
- "accentMuted": "#af8d65",
- "error": "#842f24",
- "warning": "#654300",
- "success": "#3d723d",
- "info": "#0d4a68",
- "border": "#66513b",
- "borderActive": "#824f16",
- "borderSubtle": "#8a7358",
- "borderFocus": "#0d4a68",
- "shadow": "#b6b0a5",
- "diffAdded": "#3d723d",
- "diffRemoved": "#842f24",
- "diffContext": "#352e22",
- "diffHunkHeader": "#57478b",
- "diffHighlightAdded": "#3d723d",
- "diffHighlightRemoved": "#842f24",
- "diffAddedBg": "#7d9c75",
- "diffRemovedBg": "#ab7064",
- "diffContextBg": "none",
- "diffLineNumber": "#554638",
- "diffAddedLineNumberBg": "#7d9c75",
- "diffRemovedLineNumberBg": "#ab7064",
- "diffHunkHeaderBg": "#9d90af",
- "diffFold": "#725e4c",
- "diffFoldBg": "#e6d7c4",
- "markdownText": "#17120d",
- "markdownHeading": "#824f16",
- "markdownLink": "#0d4a68",
- "markdownLinkText": "#824f16",
- "markdownCode": "#3d723d",
- "markdownBlockQuote": "#a7471c",
- "markdownEmph": "#0d4a68",
- "markdownStrong": "#824f16",
- "markdownHorizontalRule": "#66513b",
- "markdownListItem": "#824f16",
- "markdownListEnumeration": "#57478b",
- "markdownTableBorder": "#66513b",
- "markdownTableHeader": "#a7471c",
- "markdownImage": "#7d3e64",
- "markdownImageText": "#17120d",
- "markdownCodeBlock": "#17120d",
- "markdownCodeBlockBg": "#d0bca3",
- "markdownInlineCodeBg": "#5e885a",
- "syntaxComment": "#725e4c",
- "syntaxKeyword": "#7c4c11",
- "syntaxFunction": "#0d4a68",
- "syntaxMethod": "#194a6e",
- "syntaxVariable": "#4e4171",
- "syntaxParameter": "#9e441b",
- "syntaxProperty": "#0d4864",
- "syntaxField": "#3b6d3b",
- "syntaxString": "#3d723d",
- "syntaxNumber": "#8f4c18",
- "syntaxBoolean": "#9d452d",
- "syntaxConstant": "#9d452d",
- "syntaxType": "#57478b",
- "syntaxClass": "#5f4876",
- "syntaxInterface": "#474883",
- "syntaxEnum": "#524f7d",
- "syntaxOperator": "#7d3e64",
- "syntaxPunctuation": "#352e22",
- "syntaxTag": "#7c4c11",
- "syntaxAttribute": "#0d4864",
- "syntaxRegexp": "#7f3a52",
- "syntaxEscape": "#654300",
- "syntaxNamespace": "#57478b",
- "syntaxModule": "#57478b",
- "syntaxDecorator": "#7d3e64",
- "syntaxBuiltin": "#9d452d",
- "syntaxSpecial": "#654300",
- "syntaxTodo": "#5c3d02",
- "syntaxDeprecated": "#6e2f23",
- "terminalBlack": "#5e5c57",
- "terminalRed": "#842f24",
- "terminalGreen": "#3d723d",
- "terminalYellow": "#654300",
- "terminalBlue": "#0d4a68",
- "terminalMagenta": "#7d3e64",
- "terminalCyan": "#0f4058",
- "terminalWhite": "#17120d",
- "terminalBrightBlack": "#554638",
- "terminalBrightRed": "#702a20",
- "terminalBrightGreen": "#366134",
- "terminalBrightYellow": "#593b02",
- "terminalBrightBlue": "#0f4058",
- "terminalBrightMagenta": "#6b3654",
- "terminalBrightCyan": "#4b3d74",
- "terminalBrightWhite": "#17120d"
- }
-}
diff --git a/.opencode/themes/dreamcoder.json b/.opencode/themes/dreamcoder.json
index ac57426..803f32d 100644
--- a/.opencode/themes/dreamcoder.json
+++ b/.opencode/themes/dreamcoder.json
@@ -19,8 +19,8 @@
"background": "none",
"backgroundPanel": "#fff7ea",
"backgroundElement": "#e3d2bc",
- "backgroundHover": "#c8ad89",
- "backgroundSelected": "#f3eadc",
+ "backgroundHover": "#decbb1",
+ "backgroundSelected": "#decbb1",
"textSelected": "#17120d",
"backgroundCode": "#d0bca3",
"backgroundSearch": "#a1895c",
@@ -54,13 +54,13 @@
"diffHunkHeader": "#57478b",
"diffHighlightAdded": "#3d723d",
"diffHighlightRemoved": "#842f24",
- "diffAddedBg": "#7d9c75",
- "diffRemovedBg": "#ab7064",
+ "diffAddedBg": "#a7b899",
+ "diffRemovedBg": "#c49b8f",
"diffContextBg": "none",
"diffLineNumber": "#554638",
- "diffAddedLineNumberBg": "#7d9c75",
- "diffRemovedLineNumberBg": "#ab7064",
- "diffHunkHeaderBg": "#9d90af",
+ "diffAddedLineNumberBg": "#a7b899",
+ "diffRemovedLineNumberBg": "#c49b8f",
+ "diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
diff --git a/.pi/settings.json b/.pi/settings.json
index 0967ef4..e81bdba 100644
--- a/.pi/settings.json
+++ b/.pi/settings.json
@@ -1 +1,38 @@
-{}
+{
+ "subagents": {
+ "agentOverrides": {
+ "context-builder": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "low"
+ },
+ "delegate": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "medium"
+ },
+ "oracle": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "high"
+ },
+ "planner": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "medium"
+ },
+ "researcher": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "medium"
+ },
+ "reviewer": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "high"
+ },
+ "scout": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "low"
+ },
+ "worker": {
+ "model": "deepseek-1m/deepseek-v4-flash",
+ "thinking": "high"
+ }
+ }
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
index 4b791e0..8f46559 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,22 +1,43 @@
-# Code Review Rules
+# Dreamcoder Dots — AI Agent Skills
+
+## Code Review Rules
+
+### Shell Scripts
-## Shell Scripts
- Max 30 lines per file
- Use `set -euo pipefail` for scripts
- Quote all variables: `"${var}"`
- Use `[[ ]]` instead of `[ ]` for tests
-## Modularity
+### Modularity
+
- One file = one purpose
- No duplicate code (DRY)
- Conditional loading: `command -v x && ...`
-## Safety
+### Safety
+
- Safe sourcing: `[[ -f "$file" ]] && source "$file"`
- No hardcoded paths
- Fallback chains for optional tools
-## Naming
+### Naming
+
- Aliases: lowercase, short (`gs`, `pacupd`)
- Functions: snake_case (`smart_cd`, `mkcd`)
- Env vars: UPPER_CASE (`PROJECTS_DIR`)
+
+## Available Skills
+
+| Skill | Description | Path |
+|-------|-------------|------|
+| `dreamcoder-theme-engine` | Python theme engine: tokens, renderers, writers | [SKILL.md](skills/dreamcoder-theme-engine/SKILL.md) |
+| `dreamcoder-palette-tokens` | Token schema, WCAG/APCA guardrails, modes | [SKILL.md](skills/dreamcoder-palette-tokens/SKILL.md) |
+
+## Auto-invoke
+
+When working on these areas, load the corresponding skill first:
+
+- Theme engine / renderers → `dreamcoder-theme-engine`
+- Colors / tokens → `dreamcoder-palette-tokens`
+- Shell scripts → use code review rules above
diff --git a/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme b/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme
index 6f12720..cbf424a 100644
--- a/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme
+++ b/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme
@@ -10,8 +10,8 @@
background#100f0d
foreground#e8dfd0
caret#d99555
- selection#d99555
- selectionForeground#100f0d
+ selection#2b231b
+ selectionForeground#e8dfd0
lineHighlight#2b231b
gutter#392e21
gutterForeground#c7b9aa
diff --git a/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme b/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme
index 0b12d83..057aff6 100644
--- a/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme
+++ b/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme
@@ -10,7 +10,7 @@
background#f3eadc
foreground#17120d
caret#824f16
- selection#f3eadc
+ selection#decbb1
selectionForeground#17120d
lineHighlight#decbb1
gutter#c8ad89
diff --git a/Bat/.config/bat/themes/Dreamcoder.tmTheme b/Bat/.config/bat/themes/Dreamcoder.tmTheme
index 0b12d83..057aff6 100644
--- a/Bat/.config/bat/themes/Dreamcoder.tmTheme
+++ b/Bat/.config/bat/themes/Dreamcoder.tmTheme
@@ -10,7 +10,7 @@
background#f3eadc
foreground#17120d
caret#824f16
- selection#f3eadc
+ selection#decbb1
selectionForeground#17120d
lineHighlight#decbb1
gutter#c8ad89
diff --git a/CLAUDE.md b/CLAUDE.md
index bda6e18..4a5a16f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -20,7 +20,8 @@ wallpaper-derived palettes unless explicitly requested.
## Important commands
```bash
-./scripts/sync-dreamcoder-theme.py
+./scripts/dreamcoder sync
+./scripts/generate-palette-tokens.py
bash -n scripts/update-colors.sh
ghostty +validate-config
STARSHIP_CONFIG=Shell/.config/starship.toml starship explain
@@ -39,23 +40,39 @@ fish -n Shell/.config/fish/config.fish
## Dreamcoder identity
-Official daily palette:
+Canonical palettes (see `themes/dreamcoder/tokens.json`):
+
+**Dark — Ember Noir OLED**
+
+```txt
+bg #100f0d
+text #e8dfd0
+accent #d99555
+accent_2 #c96a45
+error #ed8a7a
+focus #5f8f8f
+opacity 0.76
+```
+
+**Light — Cocoa/Lúcuma**
```txt
-background #19120c
-foreground #eee0d5
-accent #fbb974
-error #ffb4ab
-opacity 0.60
+bg #f3eadc
+text #17120d
+accent #824f16
+accent_2 #a7471c
+error #842f24
+focus #0f6570
+opacity 0.96
```
Rules:
- visible theme name is `Dreamcoder`
+- edit colors only in `themes/dreamcoder/tokens.json`, then `./scripts/dreamcoder sync`
- Starship palette is `dreamcoder`
-- Ghostty theme is `dreamcoder`
+- Ghostty theme is `dreamcoder` / `dreamcoder-dark` / `dreamcoder-light`
- Fastfetch logo is `Dreamcoder01.jpg`
-- do not reintroduce `Codex-App/`
- do not commit secrets or tokens
## GitHub MCP
diff --git a/COMPARISON.md b/COMPARISON.md
new file mode 100644
index 0000000..1dff4bb
--- /dev/null
+++ b/COMPARISON.md
@@ -0,0 +1,142 @@
+# Dreamcoder OS — Comparison
+
+> ¿Por qué necesitás Dreamcoder si ya tenés Gentleman.Dots + ML4W?
+> Porque Dreamcoder es la capa que ninguno de los dos tiene: **salud visual, identidad, y AI integration**.
+
+## Feature Comparison
+
+| Feature | Gentleman.Dots | ML4W | **dreamcoder-dots** |
+| --------------------- | ---------------------- | ------------------- | ------------------------------ |
+| **Theme Engine** | ❌ Catppuccin estático | ✅ Matugen dinámico | ✅ **Token-based + WCAG/APCA** |
+| **Dark/Light/Dusk** | ❌ Solo dark | ✅ Light/Dark | ✅ **+ Dusk transition mode** |
+| **Accesibilidad** | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA 75** |
+| **AI Session Prompt** | ❌ | ❌ | ✅ **Bleeding edge en 2026** |
+| **Neovim Plugins** | ✅ 29 (LazyVim) | ❌ | 🔶 Dreamcoder colorscheme |
+| **Hyprland Config** | ❌ | ✅ Completo | 🔶 Color overlay |
+| **Shell Configs** | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + 9 functions |
+| **Ghostty Shaders** | ✅ 45+ GLSL | ❌ | 🔶 Usa los de Gentleman |
+| **Go TUI Installer** | ✅ Bubbletea | ✅ bash | ✅ Bubbletea + Vim Trainer |
+| **Starship Prompt** | ❌ Básico | ❌ Básico | ✅ **23 módulos + AI state** |
+| **Python Theme Lib** | ❌ | ❌ | ✅ **Publicada en PyPI** |
+| **Homebrew Formula** | ✅ | ❌ | ✅ |
+| **Vim Trainer** | ✅ RPG-style | ❌ | ✅ RPG-style |
+| **Auto Theme Switch** | ❌ | ✅ Manual | ✅ **Systemd timer** |
+| **CI/CD** | ❌ | ❌ | ✅ GitHub Actions |
+
+> ✅ = lo provee · 🔶 = dreamcoder overlay · ❌ = no lo provee
+
+---
+
+## Lo que se queda de Gentleman.Dots
+
+Después de instalar Dreamcoder, TODO lo de Gentleman sigue funcionando exactamente igual:
+
+| Componente | Qué se queda | Cómo lo usa Dreamcoder |
+| -------------------- | --------------------------------- | --------------------------------------------------- |
+| **Neovim** | init.lua + 29 plugins | Agrega `colorscheme dreamcoder` al init.lua |
+| **Ghostty shaders** | 45+ GLSL | Usa `dreamcoder-cursor-pulse.glsl` por defecto |
+| **Tmux** | `~/.tmux.conf` con TPM + kanagawa | `apply-theme-mode.sh` sobreescribe colores kanagawa |
+| **Zellij** | `config.kdl` con plugins | Cambia `theme "dreamcoder-{mode}"` |
+| **Fish/Zsh/Nushell** | Configs base | Agrega `conf.d/dreamcoder-*.fish` |
+| **Vim Trainer** | RPG completo | Sin cambios |
+| **Starship** | Config base (si existe) | Reemplaza con 23 módulos |
+
+## Lo que se queda de ML4W
+
+| Componente | Qué se queda | Cómo lo usa Dreamcoder |
+| --------------- | -------------------------------------- | ------------------------------------------------- |
+| **Hyprland** | hyprland.conf + animaciones + keybinds | Agrega `colors.{conf,lua}` con variables de color |
+| **Waybar** | `config.jsonc` + `style.css` | Genera `colors.css` con `@import` |
+| **Rofi** | `config.rasi` | Genera `colors.rasi` con `@import` |
+| **Dunst** | `dunstrc` | Genera `dreamcoder-dunst.conf` con `[include]` |
+| **GTK 3.0/4.0** | `settings.ini` | Switchea `prefer-dark-theme` en runtime |
+| **Btop** | `btop.conf` | Agrega `dreamcoder.theme` |
+| **Matugen** | Pipeline de colores | Dreamcoder corre matugen en cada mode switch |
+
+---
+
+## Lo que Dreamcoder AGREGA
+
+### Token Engine with WCAG/APCA
+
+```json
+{
+ "guardrails": {
+ "minimum_text_contrast": 4.5,
+ "minimum_apca_body": 75,
+ "avoid_pure_black_white": true
+ }
+}
+```
+
+Ningún otro dotfiles hace esto. Cada color está validado contra WCAG 4.5:1 y APCA.
+
+### 3 Modos de Color
+
+| Modo | Nombre | BG | Accent | Uso |
+| -------- | --------------- | --------- | --------- | ----------- |
+| 🌙 Dark | Ember Noir OLED | `#100f0d` | `#d99555` | 18:00-07:00 |
+| ☀️ Light | Cocoa/Lúcuma | `#f3eadc` | `#824f16` | 07:00-16:00 |
+| 🌆 Dusk | Transición | `#ebe4d6` | `#8a5520` | 16:00-18:00 |
+
+### AI Session State en el Prompt
+
+Cuando Claude Code o OpenCode están activos, el prompt muestra:
+
+```
+⎔ claude-4 42K
+```
+
+(solo visible cuando hay una sesión activa)
+
+### 9 Shell Functions
+
+| Función | Para qué |
+| ------------ | --------------------------------- |
+| `extract` | Descomprime CUALQUIER formato |
+| `sysupdate` | Actualiza pacman + brew + flatpak |
+| `ports` | Muestra puertos en escucha |
+| `killport` | Mata proceso en un puerto |
+| `dots` | Cd al repo dreamcoder |
+| `cheat` | TLDR para help rápido |
+| `http` | HTTPie wrapper |
+| `logs` | Journalctl wrapper |
+| `tm-session` | Tmux session picker |
+
+### Modern CLI Aliases
+
+```bash
+alias ll='eza -la --icons' # mejor ls
+alias cat='bat --paging=never' # mejor cat
+alias find='fd' # mejor find
+alias grep='rg' # mejor grep
+alias cd='z' # zoxide smart cd
+alias ps='procs' # mejor ps
+alias top='btm' # mejor htop
+alias du='dua' # mejor du
+alias df='duf' # mejor df
+alias sed='sd' # mejor sed
+alias help='tldr' # mejor man
+```
+
+Todos con graceful fallback si la herramienta no está instalada.
+
+---
+
+## Resumen Visual
+
+```
+Sin Dreamcoder:
+┌─────────────────────────────────────────────┐
+│ Gentleman.Dots: Catppuccin │
+│ ML4W: Matugen colors │
+│ Sin WCAG, sin AI prompt, sin dusk mode │
+└─────────────────────────────────────────────┘
+
+Con Dreamcoder:
+┌─────────────────────────────────────────────┐
+│ Gentleman.Dots: Neovim, shaders, tmux │
+│ ML4W: Hyprland, waybar, rofi, dunst │
+│ Dreamcoder: tokens WCAG/APCA + AI prompt │
+└─────────────────────────────────────────────┘
+```
diff --git a/Codex-App/Dreamcoder-Dark.codex-theme.json b/Codex-App/Dreamcoder-Dark.codex-theme.json
index 12cc139..0557a29 100644
--- a/Codex-App/Dreamcoder-Dark.codex-theme.json
+++ b/Codex-App/Dreamcoder-Dark.codex-theme.json
@@ -19,9 +19,9 @@
"background": "#100f0d",
"backgroundPanel": "#201b16",
"backgroundElement": "#2b231b",
- "backgroundHover": "#392e21",
- "backgroundSelected": "#d99555",
- "textSelected": "#100f0d",
+ "backgroundHover": "#2b231b",
+ "backgroundSelected": "#2b231b",
+ "textSelected": "#e8dfd0",
"backgroundCode": "#231d18",
"backgroundSearch": "#8d7141",
"backgroundLine": "#181512",
@@ -54,13 +54,13 @@
"diffHunkHeader": "#d4b4e6",
"diffHighlightAdded": "#4db35f",
"diffHighlightRemoved": "#ed8a7a",
- "diffAddedBg": "#67a468",
- "diffRemovedBg": "#cf8979",
+ "diffAddedBg": "#395437",
+ "diffRemovedBg": "#68473f",
"diffContextBg": "#100f0d",
"diffLineNumber": "#938274",
- "diffAddedLineNumberBg": "#67a468",
- "diffRemovedLineNumberBg": "#cf8979",
- "diffHunkHeaderBg": "#b8a0b4",
+ "diffAddedLineNumberBg": "#395437",
+ "diffRemovedLineNumberBg": "#68473f",
+ "diffHunkHeaderBg": "#544b51",
"diffFold": "#b8a99a",
"diffFoldBg": "#181512",
"markdownText": "#e8dfd0",
diff --git a/Codex-App/Dreamcoder-Light.codex-theme.json b/Codex-App/Dreamcoder-Light.codex-theme.json
index f79d62f..032fcab 100644
--- a/Codex-App/Dreamcoder-Light.codex-theme.json
+++ b/Codex-App/Dreamcoder-Light.codex-theme.json
@@ -19,8 +19,8 @@
"background": "#f3eadc",
"backgroundPanel": "#fff7ea",
"backgroundElement": "#e3d2bc",
- "backgroundHover": "#c8ad89",
- "backgroundSelected": "#f3eadc",
+ "backgroundHover": "#decbb1",
+ "backgroundSelected": "#decbb1",
"textSelected": "#17120d",
"backgroundCode": "#d0bca3",
"backgroundSearch": "#a1895c",
@@ -54,13 +54,13 @@
"diffHunkHeader": "#57478b",
"diffHighlightAdded": "#3d723d",
"diffHighlightRemoved": "#842f24",
- "diffAddedBg": "#7d9c75",
- "diffRemovedBg": "#ab7064",
+ "diffAddedBg": "#a7b899",
+ "diffRemovedBg": "#c49b8f",
"diffContextBg": "#f3eadc",
"diffLineNumber": "#554638",
- "diffAddedLineNumberBg": "#7d9c75",
- "diffRemovedLineNumberBg": "#ab7064",
- "diffHunkHeaderBg": "#9d90af",
+ "diffAddedLineNumberBg": "#a7b899",
+ "diffRemovedLineNumberBg": "#c49b8f",
+ "diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
diff --git a/Codex-App/Dreamcoder.codex-theme.json b/Codex-App/Dreamcoder.codex-theme.json
index f79d62f..032fcab 100644
--- a/Codex-App/Dreamcoder.codex-theme.json
+++ b/Codex-App/Dreamcoder.codex-theme.json
@@ -19,8 +19,8 @@
"background": "#f3eadc",
"backgroundPanel": "#fff7ea",
"backgroundElement": "#e3d2bc",
- "backgroundHover": "#c8ad89",
- "backgroundSelected": "#f3eadc",
+ "backgroundHover": "#decbb1",
+ "backgroundSelected": "#decbb1",
"textSelected": "#17120d",
"backgroundCode": "#d0bca3",
"backgroundSearch": "#a1895c",
@@ -54,13 +54,13 @@
"diffHunkHeader": "#57478b",
"diffHighlightAdded": "#3d723d",
"diffHighlightRemoved": "#842f24",
- "diffAddedBg": "#7d9c75",
- "diffRemovedBg": "#ab7064",
+ "diffAddedBg": "#a7b899",
+ "diffRemovedBg": "#c49b8f",
"diffContextBg": "#f3eadc",
"diffLineNumber": "#554638",
- "diffAddedLineNumberBg": "#7d9c75",
- "diffRemovedLineNumberBg": "#ab7064",
- "diffHunkHeaderBg": "#9d90af",
+ "diffAddedLineNumberBg": "#a7b899",
+ "diffRemovedLineNumberBg": "#c49b8f",
+ "diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
diff --git a/Codex-CLI/Dreamcoder-Dark.tmTheme b/Codex-CLI/Dreamcoder-Dark.tmTheme
index 6f12720..cbf424a 100644
--- a/Codex-CLI/Dreamcoder-Dark.tmTheme
+++ b/Codex-CLI/Dreamcoder-Dark.tmTheme
@@ -10,8 +10,8 @@
background#100f0d
foreground#e8dfd0
caret#d99555
- selection#d99555
- selectionForeground#100f0d
+ selection#2b231b
+ selectionForeground#e8dfd0
lineHighlight#2b231b
gutter#392e21
gutterForeground#c7b9aa
diff --git a/Codex-CLI/Dreamcoder-Light.tmTheme b/Codex-CLI/Dreamcoder-Light.tmTheme
index 0b12d83..057aff6 100644
--- a/Codex-CLI/Dreamcoder-Light.tmTheme
+++ b/Codex-CLI/Dreamcoder-Light.tmTheme
@@ -10,7 +10,7 @@
background#f3eadc
foreground#17120d
caret#824f16
- selection#f3eadc
+ selection#decbb1
selectionForeground#17120d
lineHighlight#decbb1
gutter#c8ad89
diff --git a/Codex-CLI/Dreamcoder.tmTheme b/Codex-CLI/Dreamcoder.tmTheme
index 0b12d83..057aff6 100644
--- a/Codex-CLI/Dreamcoder.tmTheme
+++ b/Codex-CLI/Dreamcoder.tmTheme
@@ -10,7 +10,7 @@
background#f3eadc
foreground#17120d
caret#824f16
- selection#f3eadc
+ selection#decbb1
selectionForeground#17120d
lineHighlight#decbb1
gutter#c8ad89
diff --git a/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl b/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl
index 53f7324..b431a0c 100644
--- a/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl
+++ b/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl
@@ -1,47 +1,57 @@
-// Dreamcoder Motion cursor pulse for Ghostty.
-// Lightweight shader: only reacts around the terminal cursor and fades quickly.
-
-float sdfRectangle(in vec2 p, in vec2 center, in vec2 halfSize) {
- vec2 d = abs(p - center) - halfSize;
- return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
-}
-
-vec2 normalizePosition(vec2 value, float isPosition) {
- return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
-}
-
-float antialias(float distance) {
- return 1.0 - smoothstep(0.0, normalizePosition(vec2(2.0), 0.0).x, distance);
-}
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord) {
- vec2 uv = fragCoord.xy / iResolution.xy;
- fragColor = texture(iChannel0, uv);
-
- vec4 cursor = vec4(
- normalizePosition(iCurrentCursor.xy, 1.0),
- normalizePosition(iCurrentCursor.zw, 0.0)
- );
-
- vec2 cursorCenter = vec2(
- cursor.x + cursor.z * 0.5,
- cursor.y - cursor.w * 0.5
- );
- vec2 point = normalizePosition(fragCoord, 1.0);
-
- float elapsed = iTime - iTimeCursorChange;
- float pulse = 0.5 + 0.5 * sin(iTime * 5.2);
- float settle = 1.0 - smoothstep(0.18, 0.42, elapsed);
-
- float cursorSdf = sdfRectangle(point, cursorCenter, cursor.zw * 0.5);
- float glowSdf = sdfRectangle(point, cursorCenter, cursor.zw * (0.85 + pulse * 0.35));
-
- vec3 accent = iCurrentCursorColor.rgb;
- vec3 warmAccent = mix(accent, vec3(1.0, 0.66, 0.34), 0.22);
-
- float core = antialias(cursorSdf);
- float glow = (1.0 - smoothstep(0.0, 0.028, glowSdf)) * (0.16 + pulse * 0.10) * settle;
-
- fragColor.rgb = mix(fragColor.rgb, warmAccent, glow);
- fragColor.rgb = mix(fragColor.rgb, accent, core * 0.08);
+// Dreamcoder Cursor Pulse — custom GLSL shader for Ghostty
+// Pulses the cursor with the dreamcoder accent color (#d99555 dark, #824f16 light)
+// and adds a subtle warm glow trail.
+//
+// Install: set `custom-shader = shaders/dreamcoder-cursor-pulse.glsl` in ghostty config
+
+#version 330
+
+// --- Uniforms (provided by Ghostty) ---
+uniform float u_time;
+uniform vec2 u_resolution;
+uniform vec2 u_cursor_position;
+uniform vec2 u_cursor_size;
+uniform bool u_cursor_visible;
+
+// --- Dreamcoder palette ---
+// Dark mode accent: #d99555 → vec3(0.851, 0.584, 0.333)
+// Light mode accent: #824f16 → vec3(0.510, 0.310, 0.086)
+// We use dark mode by default; Ghostty can pass the theme color.
+
+vec3 accent = vec3(0.851, 0.584, 0.333); // #d99555
+vec3 glow = vec3(0.851, 0.584, 0.333); // warm gold glow
+
+// --- Pixel input from previous stage ---
+in vec2 f_uv;
+out vec4 fragColor;
+uniform sampler2D f_texture;
+
+void main() {
+ vec4 color = texture(f_texture, f_uv);
+
+ if (u_cursor_visible) {
+ // Calculate distance from cursor
+ vec2 cursor_uv = u_cursor_position / u_resolution;
+ vec2 cursor_size_uv = u_cursor_size / u_resolution;
+ vec2 delta = abs(f_uv - cursor_uv) / cursor_size_uv;
+
+ // Check if we're near the cursor area
+ if (delta.x < 1.5 && delta.y < 1.5) {
+ // Smooth pulse based on time
+ float pulse = 0.5 + 0.5 * sin(u_time * 3.0);
+ float dist = length(delta);
+
+ // Warm glow behind cursor
+ float glow_intensity = 0.15 * (1.0 - smoothstep(0.0, 1.5, dist)) * pulse;
+ color.rgb += glow * glow_intensity;
+
+ // Subtle accent tint on cursor itself
+ if (dist < 0.8) {
+ float tint = 0.08 * (1.0 - smoothstep(0.0, 0.8, dist)) * pulse;
+ color.rgb = mix(color.rgb, accent, tint);
+ }
+ }
+ }
+
+ fragColor = color;
}
diff --git a/Ghostty/.config/ghostty/themes/dreamcoder b/Ghostty/.config/ghostty/themes/dreamcoder
index 1c71725..b20ae7c 100644
--- a/Ghostty/.config/ghostty/themes/dreamcoder
+++ b/Ghostty/.config/ghostty/themes/dreamcoder
@@ -2,7 +2,7 @@
background = #f3eadc
foreground = #17120d
cursor-color = #824f16
-cursor-text = #f3eadc
+cursor-text = #fff7ea
selection-background = #decbb1
selection-foreground = #17120d
background-opacity = 0.96
@@ -18,10 +18,10 @@ palette = 5=#7d3e64
palette = 6=#57478b
palette = 7=#352e22
palette = 8=#554638
-palette = 9=#815850
-palette = 10=#4f6f51
+palette = 9=#702a20
+palette = 10=#366134
palette = 11=#654300
-palette = 12=#3a686e
-palette = 13=#6c5ba2
-palette = 14=#4b6f6f
+palette = 12=#0f4058
+palette = 13=#4b3d74
+palette = 14=#10565e
palette = 15=#17120d
diff --git a/Ghostty/.config/ghostty/themes/dreamcoder-dark b/Ghostty/.config/ghostty/themes/dreamcoder-dark
index 3fb62cc..d189dae 100644
--- a/Ghostty/.config/ghostty/themes/dreamcoder-dark
+++ b/Ghostty/.config/ghostty/themes/dreamcoder-dark
@@ -18,10 +18,10 @@ palette = 5=#e29cb4
palette = 6=#d4b4e6
palette = 7=#c7b9aa
palette = 8=#938274
-palette = 9=#e9a092
-palette = 10=#75a579
+palette = 9=#ec9989
+palette = 10=#69bb73
palette = 11=#e8b866
-palette = 12=#579ba4
-palette = 13=#846fc5
-palette = 14=#6fa5a5
+palette = 12=#78a2cb
+palette = 13=#d8bce2
+palette = 14=#789d9b
palette = 15=#e8dfd0
diff --git a/Ghostty/.config/ghostty/themes/dreamcoder-light b/Ghostty/.config/ghostty/themes/dreamcoder-light
index 1c71725..b20ae7c 100644
--- a/Ghostty/.config/ghostty/themes/dreamcoder-light
+++ b/Ghostty/.config/ghostty/themes/dreamcoder-light
@@ -2,7 +2,7 @@
background = #f3eadc
foreground = #17120d
cursor-color = #824f16
-cursor-text = #f3eadc
+cursor-text = #fff7ea
selection-background = #decbb1
selection-foreground = #17120d
background-opacity = 0.96
@@ -18,10 +18,10 @@ palette = 5=#7d3e64
palette = 6=#57478b
palette = 7=#352e22
palette = 8=#554638
-palette = 9=#815850
-palette = 10=#4f6f51
+palette = 9=#702a20
+palette = 10=#366134
palette = 11=#654300
-palette = 12=#3a686e
-palette = 13=#6c5ba2
-palette = 14=#4b6f6f
+palette = 12=#0f4058
+palette = 13=#4b3d74
+palette = 14=#10565e
palette = 15=#17120d
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..8710793
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,177 @@
+# Dreamcoder OS — Installation Guide
+
+> Instalación completa en 3 pasos: Gentleman.Dots → ML4W → Dreamcoder.
+
+## Prerequisitos
+
+- **Arch Linux** (o derivado)
+- `git`, `stow`, `python3` instalados
+- Conexión a internet
+
+---
+
+## Paso 1: Gentleman.Dots
+
+Gentleman.Dots provee la base para Neovim, shells, terminales, y multiplexers.
+
+### Opción A: Homebrew (recomendado)
+
+```bash
+brew install Gentleman-Programming/tap/gentleman-dots
+gentleman-dots
+```
+
+### Opción B: Descarga directa
+
+```bash
+curl -fsSL https://github.com/Gentleman-Programming/Gentleman.Dots/releases/latest/download/gentleman-installer-linux-amd64 -o gentleman.dots
+chmod +x gentleman.dots
+./gentleman.dots
+```
+
+### Opción C: Manual
+
+```bash
+git clone https://github.com/Gentleman-Programming/Gentleman.Dots.git
+cd Gentleman.Dots
+./install.sh
+```
+
+Seguí el installer TUI para seleccionar:
+
+- Shell: Fish (recomendado), Zsh, o Nushell
+- Terminal: Ghostty (recomendado) o Kitty
+- Multiplexer: Tmux o Zellij
+- Editor: Neovim
+
+> Más info: [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots)
+
+---
+
+## Paso 2: ML4W OS
+
+ML4W provee la base completa de escritorio Hyprland.
+
+```bash
+bash <(curl -s https://ml4w.com/os/stable)
+```
+
+Esto instala:
+
+- **Hyprland**: animaciones, keybinds, monitores, layouts, ventanas, workspaces
+- **Waybar**: barra de estado con módulos
+- **Rofi**: lanzador de apps
+- **Dunst**: notificaciones
+- **GTK 3.0/4.0**: tema y config
+- **Btop**: monitor de sistema
+- **Matugen**: generación dinámica de colores
+
+> Más info: [ML4W OS](https://ml4w.com/os/)
+
+---
+
+## Paso 3: Dreamcoder
+
+Dreamcoder aplica su capa visual sobre Gentleman + ML4W.
+
+### Clonar
+
+```bash
+git clone git@github.com:Dreamcoder08/Dreamcoder_dots.git ~/Documents/PROYECTOS/dreamcoder-dots
+cd ~/Documents/PROYECTOS/dreamcoder-dots
+```
+
+### Instalar
+
+```bash
+./scripts/dreamcoder install
+```
+
+Esto:
+
+1. Crea backups de configs existentes
+2. Stowea los módulos (Shell, Kitty, Ghostty, Fastfetch, Warp, Bat, Systemd)
+3. Instala los hooks de tema para cada app
+4. Activa el timer systemd para auto-theme-switching
+5. Corre el primer sync de temas
+
+### Verificar
+
+```bash
+./scripts/dreamcoder doctor # Health check completo
+./scripts/dreamcoder status # System status
+```
+
+---
+
+## Post-Instalación
+
+### Tmux
+
+Si usás Tmux con TPM:
+
+```bash
+tmux
+prefix + I # Instalar plugins
+```
+
+### Neovim
+
+Abrí Neovim. LazyVim instala los plugins automáticamente:
+
+```bash
+nvim
+:Lazy sync
+```
+
+### Auto Theme Switching
+
+El timer systemd se activa automáticamente. Para verificar:
+
+```bash
+systemctl --user status dreamcoder-theme-auto.timer
+```
+
+El timer cambia entre modos según el horario:
+
+- **07:00-16:00** → Light
+- **16:00-18:00** → Dusk
+- **18:00-07:00** → Dark
+
+---
+
+## Troubleshooting
+
+### "Theme files not found"
+
+Asegurate de haber instalado Gentleman.Dots y ML4W primero. Dreamcoder es un overlay, no un reemplazo.
+
+### "Error: stow not found"
+
+```bash
+sudo pacman -S stow
+```
+
+### "Colors not updating"
+
+```bash
+dreamcoder dark # Force dark mode
+dreamcoder light # Force light mode
+```
+
+### "Neovim colorscheme not loading"
+
+Agregá esto a `~/.config/nvim/init.lua`:
+
+```lua
+vim.cmd.colorscheme("dreamcoder")
+```
+
+---
+
+## Referencias
+
+- [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots)
+- [ML4W OS](https://ml4w.com/os/)
+- [Dreamcoder SDD Plans](docs/superpowers/plans/)
+- [Architecture Docs](docs/architecture/)
diff --git a/Kitty/.config/kitty/colors-dreamcoder-dark.conf b/Kitty/.config/kitty/colors-dreamcoder-dark.conf
index 05b4b52..f4ea373 100644
--- a/Kitty/.config/kitty/colors-dreamcoder-dark.conf
+++ b/Kitty/.config/kitty/colors-dreamcoder-dark.conf
@@ -7,7 +7,7 @@ foreground #e8dfd0
background #100f0d
selection_foreground #e8dfd0
selection_background #2b231b
-url_color #5f95ca
+url_color #d99555
cursor #d99555
cursor_text_color #100f0d
@@ -34,19 +34,19 @@ color5 #e29cb4
color6 #d4b4e6
color7 #c7b9aa
color8 #938274
-color9 #e9a092
-color10 #75a579
+color9 #ec9989
+color10 #69bb73
color11 #e8b866
-color12 #579ba4
-color13 #846fc5
-color14 #6fa5a5
+color12 #78a2cb
+color13 #d8bce2
+color14 #789d9b
color15 #e8dfd0
color16 #c96a45
color17 #ed8a7a
mark1_foreground #100f0d
mark1_background #d99555
-mark2_foreground #100f0d
+mark2_foreground #e8dfd0
mark2_background #5f95ca
-mark3_foreground #100f0d
+mark3_foreground #e8dfd0
mark3_background #e29cb4
diff --git a/Kitty/.config/kitty/colors-dreamcoder-light.conf b/Kitty/.config/kitty/colors-dreamcoder-light.conf
index e00731e..be98492 100644
--- a/Kitty/.config/kitty/colors-dreamcoder-light.conf
+++ b/Kitty/.config/kitty/colors-dreamcoder-light.conf
@@ -7,15 +7,15 @@ foreground #17120d
background #f3eadc
selection_foreground #17120d
selection_background #decbb1
-url_color #0d4a68
+url_color #824f16
cursor #824f16
-cursor_text_color #f3eadc
+cursor_text_color #fff7ea
cursor_shape block
cursor_blink_interval 0.5
cursor_stop_blinking_after 15.0
-active_tab_foreground #f3eadc
+active_tab_foreground #fff7ea
active_tab_background #824f16
inactive_tab_foreground #352e22
inactive_tab_background #f3eadc
@@ -34,19 +34,19 @@ color5 #7d3e64
color6 #57478b
color7 #352e22
color8 #554638
-color9 #815850
-color10 #4f6f51
+color9 #702a20
+color10 #366134
color11 #654300
-color12 #3a686e
-color13 #6c5ba2
-color14 #4b6f6f
+color12 #0f4058
+color13 #4b3d74
+color14 #10565e
color15 #17120d
color16 #a7471c
color17 #842f24
-mark1_foreground #f3eadc
+mark1_foreground #fff7ea
mark1_background #824f16
-mark2_foreground #f3eadc
+mark2_foreground #17120d
mark2_background #0d4a68
-mark3_foreground #f3eadc
+mark3_foreground #17120d
mark3_background #7d3e64
diff --git a/Kitty/.config/kitty/colors-dreamcoder.conf b/Kitty/.config/kitty/colors-dreamcoder.conf
index e00731e..be98492 100644
--- a/Kitty/.config/kitty/colors-dreamcoder.conf
+++ b/Kitty/.config/kitty/colors-dreamcoder.conf
@@ -7,15 +7,15 @@ foreground #17120d
background #f3eadc
selection_foreground #17120d
selection_background #decbb1
-url_color #0d4a68
+url_color #824f16
cursor #824f16
-cursor_text_color #f3eadc
+cursor_text_color #fff7ea
cursor_shape block
cursor_blink_interval 0.5
cursor_stop_blinking_after 15.0
-active_tab_foreground #f3eadc
+active_tab_foreground #fff7ea
active_tab_background #824f16
inactive_tab_foreground #352e22
inactive_tab_background #f3eadc
@@ -34,19 +34,19 @@ color5 #7d3e64
color6 #57478b
color7 #352e22
color8 #554638
-color9 #815850
-color10 #4f6f51
+color9 #702a20
+color10 #366134
color11 #654300
-color12 #3a686e
-color13 #6c5ba2
-color14 #4b6f6f
+color12 #0f4058
+color13 #4b3d74
+color14 #10565e
color15 #17120d
color16 #a7471c
color17 #842f24
-mark1_foreground #f3eadc
+mark1_foreground #fff7ea
mark1_background #824f16
-mark2_foreground #f3eadc
+mark2_foreground #17120d
mark2_background #0d4a68
-mark3_foreground #f3eadc
+mark3_foreground #17120d
mark3_background #7d3e64
diff --git a/Nvim/.config/nvim/colors/dreamcoder-dark.lua b/Nvim/.config/nvim/colors/dreamcoder-dark.lua
index 98c130f..93d6f40 100644
--- a/Nvim/.config/nvim/colors/dreamcoder-dark.lua
+++ b/Nvim/.config/nvim/colors/dreamcoder-dark.lua
@@ -708,17 +708,17 @@ end
})
vim.api.nvim_set_hl(0, "@markup.link", {
- fg = "#5f95ca",
+ fg = "#d99555",
underline = true
})
vim.api.nvim_set_hl(0, "@markup.link.label", {
- fg = "#5f95ca",
+ fg = "#d99555",
underline = true
})
vim.api.nvim_set_hl(0, "@markup.link.url", {
- fg = "#938274",
+ fg = "#c96a45",
underline = true
})
diff --git a/Nvim/.config/nvim/colors/dreamcoder-light.lua b/Nvim/.config/nvim/colors/dreamcoder-light.lua
index 8e2f66b..d5f3d43 100644
--- a/Nvim/.config/nvim/colors/dreamcoder-light.lua
+++ b/Nvim/.config/nvim/colors/dreamcoder-light.lua
@@ -708,17 +708,17 @@ end
})
vim.api.nvim_set_hl(0, "@markup.link", {
- fg = "#0d4a68",
+ fg = "#824f16",
underline = true
})
vim.api.nvim_set_hl(0, "@markup.link.label", {
- fg = "#0d4a68",
+ fg = "#824f16",
underline = true
})
vim.api.nvim_set_hl(0, "@markup.link.url", {
- fg = "#554638",
+ fg = "#a7471c",
underline = true
})
diff --git a/Pi/.pi/agent/scripts/pi-theme.sh b/Pi/.pi/agent/scripts/pi-theme.sh
new file mode 100755
index 0000000..51c7754
--- /dev/null
+++ b/Pi/.pi/agent/scripts/pi-theme.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+DD="${DOTS_DIR:-$HOME/Documents/PROYECTOS/dreamcoder-dots}"
+LN="$HOME/.pi/agent/themes/dreamcoder.json"
+
+detect() {
+ if command -v gsettings &>/dev/null; then
+ s="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || true)"
+ [[ "$s" == *prefer-dark* ]] && {
+ echo dark
+ return
+ }
+ [[ "$s" == *default* || "$s" == *prefer-light* ]] && {
+ echo light
+ return
+ }
+ fi
+ if [[ -f "$HOME/.theme-mode" ]]; then
+ m="$(<"$HOME/.theme-mode")"
+ [[ "$m" == dark || "$m" == light ]] && {
+ echo "$m"
+ return
+ }
+ fi
+ if [[ -n "${COLORFGBG:-}" ]]; then
+ bg="${COLORFGBG##*;}"
+ [[ "$bg" -lt 8 ]] && echo light || echo dark
+ return
+ fi
+ echo light
+}
+
+mode="$(detect)"
+src="$DD/Pi/.pi/agent/themes/dreamcoder-$mode.json"
+[[ -f "$src" ]] || {
+ echo "✗ $src not found" >&2
+ exit 1
+}
+mkdir -p "$(dirname "$LN")"
+ln -sf "$src" "$LN"
+echo "→ pi-theme: $mode"
diff --git a/README.md b/README.md
index 14ceaed..a9d444e 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,40 @@
# Dreamcoder OS
+> Visual layer premium para **Gentleman.Dots + ML4W**.
+> Café/Lúcuma. Ember Noir. Contraste saludable. Identidad.
+
[](https://github.com/Gentleman-Programming/dreamcoder-dots/actions/workflows/theme-validation.yml)
-[]()
+[]()
+[]()
[](https://pypi.org/project/dreamcoder-theme/)
-[](https://pypi.org/project/dreamcoder-theme/)
-[](https://github.com/Gentleman-Programming/dreamcoder-dots/blob/main/LICENSE)
+[](./LICENSE)
-> Personal Arch Linux dotfiles — a visual layer on top of ML4W/Gentleman Dots focused on readability, eye comfort, and a premium coding experience.
+---
-Personal Arch Linux dotfiles for the Dreamcoder identity: a visual layer on top of
-ML4W/Gentleman Dots focused on readability, eye comfort, and a premium coding experience.
+## Quick Start — 3-Step Install
-```mermaid
-flowchart LR
- subgraph Tokens["Design Tokens"]
- PT["palette_tokens.py
dark / light / dusk"]
- end
- subgraph Render["Renderers (28+ targets)"]
- R["renderers.py
hub → leaf modules"]
- end
- subgraph Write["Writers"]
- W["writers.py
write_if_changed()"]
- end
- subgraph Output["Theme Files"]
- O["Kitty, Ghostty, Nvim,
Tmux, Starship, Hyprland,
Codex, OpenCode, Pi, ..."]
- end
- Tokens --> Render --> Write --> Output
+### 1. Install Gentleman.Dots
+
+```bash
+brew install Gentleman-Programming/tap/gentleman-dots
+gentleman-dots
```
----
+→ O descargá desde [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots)
-## Installation
+**Te da**: Neovim (29 plugins LazyVim), Ghostty shaders (45+), Tmux/Zellij, Vim Trainer, Fish/Zsh/Nushell
-### Theme Engine (Python package)
+### 2. Install ML4W OS
```bash
-pip install dreamcoder-theme
+bash <(curl -s https://ml4w.com/os/stable)
```
-### Full Dotfiles
+→ [Documentación ML4W](https://ml4w.com/os/)
+
+**Te da**: Hyprland completo (animaciones, keybinds, monitores), Waybar, Rofi, Dunst, GTK, Btop
+
+### 3. Install Dreamcoder
```bash
git clone git@github.com:Dreamcoder08/Dreamcoder_dots.git ~/Documents/PROYECTOS/dreamcoder-dots
@@ -46,121 +42,127 @@ cd ~/Documents/PROYECTOS/dreamcoder-dots
./scripts/dreamcoder install
```
----
+**Aplica**: Dreamcoder dark/light/dusk sobre toda la base de Gentleman + ML4W
-## Usage
+**Agrega**: Starship prompt con AI session state, 9 funciones shell, aliases modernos, auto-theme-switching
-### CLI
-
-```bash
-# Render all themes to your config dirs
-dreamcoder-theme sync
-
-# Check health of installed themes
-dreamcoder-theme doctor
-
-# Inspect active theme paths
-dreamcoder-theme paths
-```
+---
-### Library
+## ¿Por qué Dreamcoder?
-```python
-from dreamcoder_theme.palette import adaptive_palette
-from dreamcoder_theme.renderers import kitty_content, ghostty_content
+| Feature | Gentleman.Dots | ML4W | **dreamcoder-dots** |
+| --------------------- | ------------------- | ------------ | ------------------------------ |
+| **Theme Engine** | ❌ Catppuccin | ✅ Matugen | ✅ **Token-based + WCAG/APCA** |
+| **Light/Dark/Dusk** | ❌ | ✅ | ✅ **+ Dusk transition** |
+| **AI Session Prompt** | ❌ | ❌ | ✅ **Bleeding edge** |
+| **Accesibilidad** | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA 75** |
+| **Neovim** | ✅ 29 plugins | ❌ | 🔶 Dreamcoder colorscheme |
+| **Hyprland** | ❌ | ✅ Completo | 🔶 Color overlay |
+| **Shell Configs** | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + functions |
+| **Ghostty Shaders** | ✅ 45+ GLSL | ❌ | 🔶 Usa los de Gentleman |
+| **Installer** | ✅ Go TUI | ✅ bash | ✅ **Go TUI + Vim Trainer** |
+| **Prompt** | ❌ Básico | ❌ Básico | ✅ **Starship 23 módulos** |
-# Generate adaptive colors from a wallpaper
-active = adaptive_palette(variants["dark"], wallpaper="/path/to/wallpaper.jpg")
+> ✅ = lo tiene completo · 🔶 = dreamcoder aporta overlay · ❌ = no lo tiene
-# Render to specific formats
-kitty_conf = kitty_content(active)
-ghostty_conf = ghostty_content(active)
-```
+---
-### Script commands
+## Lo que dreamcoder NO reemplaza
-| Command | Description |
-|---------|-------------|
-| `./scripts/dreamcoder install` | First install / full reapply |
-| `./scripts/dreamcoder repair` | After ML4W or Gentleman updates |
-| `./scripts/dreamcoder doctor` | Inspect current health/status |
-| `./scripts/dreamcoder dark` | Force dark mode |
-| `./scripts/dreamcoder light` | Force light mode |
-| `./scripts/dreamcoder preview` | Regenerate docs preview |
-| `./scripts/set-wallpaper.sh ` | Set wallpaper and refresh |
+Dreamcoder es una **capa visual**, no un reemplazo. Mantenés todo lo que ya te dan Gentleman y ML4W:
----
+**De Gentleman.Dots se queda:**
-## Architecture
+- Neovim con 29 plugins (avante, copilot, blink, fzf-lua, oil, DAP...)
+- Ghostty shaders (45+ GLSL effects)
+- Tmux/Zellij con TPM y plugins
+- Vim Mastery Trainer
+- Fish/Zsh/Nushell base config
-The theme generation pipeline follows a four-layer flow:
+**De ML4W se queda:**
-1. **Input Layer** — `palette_tokens.py` defines static design tokens (dark/light/dusk variants)
-2. **Transform Layer** — `palette.py` loads variants and applies adaptive palette from wallpaper
-3. **Render Layer** — `renderers.py` imports functions from `renderers_*.py` modules that convert tokens to target-specific formats
-4. **Write Layer** — `writers.py` writes files via `write_if_changed()` and updates app configurations
+- Hyprland completo (animaciones, keybinds, monitores, layouts)
+- Waybar, Rofi, Dunst configs
+- GTK 3.0/4.0 settings
+- Matugen color generation pipeline
+- Btop, Chromium/Edge configs
-Each renderer is a pure function: receives a color dict and returns a string. No side effects until the write layer.
+**Dreamcoder aporta:**
-```
-dreamcoder-dots/
-├── src/
-│ ├── dreamcoder_theme/
-│ │ ├── palette_tokens.py # Design tokens
-│ │ ├── palette.py # Adaptive palette
-│ │ ├── renderers.py # Render hub
-│ │ ├── renderers_kitty.py # Kitty renderer
-│ │ ├── renderers_ghostty.py # Ghostty renderer
-│ │ └── ...
-│ └── ...
-├── scripts/ # Install/repair/utility scripts
-├── docs/ # Architecture docs and previews
-└── configs/ # Default configuration files
-```
+- Tokens de color validados con WCAG 4.5:1 + APCA
+- 3 modos: Ember Noir (dark), Cocoa/Lúcuma (light), Dusk (transición)
+- Starship prompt con 23 módulos y AI session state
+- 9 funciones shell (extract, sysupdate, killport, etc.)
+- Aliases modernos con graceful fallback (eza, bat, fd, rg, zoxide)
+- Auto-theme-switching por horario (systemd timer)
+- Python library para generación de temas (PyPI)
---
-## Tech Stack
-
-| Layer | Tech | Purpose |
-|-------|------|---------|
-| Language | Python 3 | Theme engine and CLI |
-| Rendering Targets | 28+ targets | Kitty, Ghostty, Nvim, Tmux, Hyprland, and more |
-| Design Tokens | Custom palette system | Dark/light/dusk variants |
-| Testing | pytest + ruff | Type-safe theme generation |
+## Uso
----
+### CLI
-## Philosophy
+```bash
+dreamcoder dark # → Ember Noir OLED
+dreamcoder light # → Dreamcoder Light
+dreamcoder status # → System status overview
+dreamcoder doctor # → Health check
+```
-Dreamcoder is not a neon rice. It is a workbench:
+### Theme Engine (Python)
-- **health first**: no pure black/white primary backgrounds, strong contrast, low glare;
-- **daily comfort**: larger terminal type, calmer prompt density, automatic day/night mode;
-- **identity second**: Cocoa/Lúcuma warmth, diagnostic cyan, restrained editorial colors;
-- **ML4W-compatible**: Dreamcoder owns colors and hooks, ML4W/Gentleman can keep layout behavior.
+```bash
+pip install dreamcoder-theme
+dreamcoder-theme sync # Render all themes
+dreamcoder-theme doctor # Check health
+```
---
-## Project Status
+## Arquitectura
-**Status:** Active
-**Version:** 1.0
+```mermaid
+flowchart LR
+ subgraph Tokens["Design Tokens"]
+ PT["palette_tokens.py
dark / light / dusk"]
+ end
+ subgraph Render["Renderers (28+ targets)"]
+ R["renderers.py
hub → leaf modules"]
+ end
+ subgraph Write["Writers"]
+ W["writers.py
write_if_changed()"]
+ end
+ subgraph Output["Theme Files"]
+ O["Kitty, Ghostty, Nvim,
Tmux, Starship, Hyprland,
Codex, OpenCode, Pi, ..."]
+ end
+ Tokens --> Render --> Write --> Output
+```
---
-## Contributing
+## Filosofía
+
+Dreamcoder no es un rice neon. Es un banco de trabajo:
-See [CONTRIBUTING.md](CONTRIBUTING.md). Full documentation at [docs/README.md](docs/README.md).
+- **Salud primero**: sin blanco/negro puro, contraste fuerte, bajo brillo
+- **Confort diario**: tipografía más grande, densidad de prompt calmada, modo día/noche automático
+- **Identidad segundo**: calidez Cocoa/Lúcuma, cyan diagnóstico, colores editoriales
---
-## License
+## Projecto
-MIT — see [LICENSE](LICENSE).
+| Aspecto | Detalle |
+| -------------- | ---------------------------------- |
+| **Estado** | Active |
+| **Versión** | 1.0 |
+| **Licencia** | MIT |
+| **Docs** | [docs/README.md](docs/README.md) |
+| **Contribuir** | [CONTRIBUTING.md](CONTRIBUTING.md) |
---
## SDD
-This project sits within the [Dreamcoder08](https://github.com/Dreamcoder08) ecosystem. Documentation is maintained in the [SDD Maestro](../arkelythex/sdd/ecosystem-readme-sdd/00-README.md).
+Este proyecto usa Spec-Driven Development. Los planes están en [docs/superpowers/plans/](docs/superpowers/plans/).
diff --git a/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish b/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish
index 66f25d5..33f0457 100644
--- a/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish
+++ b/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish
@@ -46,7 +46,79 @@ if status is-interactive
echo "🎨 Shell theme reloaded: $DREAMCODER_THEME_MODE mode"
end
- function mkcd --description 'Create a directory and enter it'
- mkdir -p -- $argv[1]; and cd -- $argv[1]
+ ## ─── Git shortcuts ────────────────────────────────────
+ alias g='git'
+ alias gs='git status'
+ alias gp='git push'
+ alias gl='git log --oneline --graph --all'
+ alias gd='git diff'
+ alias gc='git commit'
+ alias gco='git checkout'
+ alias gb='git branch'
+ alias ga='git add'
+ alias gpl='git pull'
+ alias gst='git stash'
+ alias glg='git log --oneline --graph --all --decorate'
+ alias gundo='git reset --soft HEAD~1'
+ alias gcleanup='git branch --merged | grep -v "\*\|main\|master" | xargs -r git branch -d'
+ alias gconflicts='git diff --name-only --diff-filter=U'
+ alias groot='git rev-parse --show-toplevel'
+
+ ## ─── Modern CLI replacements ──────────────────────────
+ # eza (better ls)
+ if command -q eza
+ alias ll='eza -la --icons --group-directories-first'
+ alias la='eza -a --icons --group-directories-first'
+ alias lt='eza -T --icons --group-directories-first'
+ alias l1='eza -1 --icons'
+ else
+ alias ll='ls -lahF'
+ alias la='ls -A'
+ alias lt='tree -C 2>/dev/null; or ls -R'
+ end
+
+ # bat (better cat)
+ if command -q bat
+ alias cat='bat --paging=never'
+ end
+
+ # fd (better find)
+ if command -q fd
+ alias find='fd'
+ end
+
+ # rg (better grep)
+ if command -q rg
+ alias grep='rg'
+ end
+
+ # zoxide (better cd)
+ if command -q zoxide
+ alias cd='z'
+ alias cdi='zi'
+ end
+
+ # Modern replacements (graceful fallback)
+ if command -q procs
+ alias ps='procs'
+ end
+ if command -q btm
+ alias top='btm'
end
+ if command -q dua
+ alias du='dua'
+ end
+ if command -q duf
+ alias df='duf'
+ end
+ if command -q sd
+ alias sed='sd'
+ end
+
+ ## ─── Quick navigation ─────────────────────────────────
+ alias home='cd ~'
+ alias ..='cd ..'
+ alias ...='cd ../..'
+ alias ....='cd ../../..'
+ alias docs='cd $DREAMCODER_DOTS_DIR/docs'
end
diff --git a/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish b/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish
new file mode 100644
index 0000000..194c3c1
--- /dev/null
+++ b/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish
@@ -0,0 +1,46 @@
+# Dreamcoder AI Session State
+# Detects active AI coding sessions and writes state for Starship prompt.
+# Sources: Claude Code (~/.claude/sessions/), OpenCode (~/.opencode/state)
+#
+# Starship reads ~/.cache/dreamcoder/ai-session.state via custom.ai_session module.
+# This script should be called by fish_prompt or via a systemd path unit.
+
+set -l CACHE_DIR "$HOME/.cache/dreamcoder"
+mkdir -p "$CACHE_DIR"
+set -l STATE_FILE "$CACHE_DIR/ai-session.state"
+
+# Reset state
+echo -n >"$STATE_FILE"
+
+# Check Claude Code sessions
+set -l CLAUDE_SESSIONS "$HOME/.claude/sessions"
+if test -d "$CLAUDE_SESSIONS"
+ # Find most recent active session
+ set -l latest (command ls -t "$CLAUDE_SESSIONS" 2>/dev/null | head -1)
+ if test -n "$latest"
+ # Read session metadata for context info
+ set -l meta_file "$CLAUDE_SESSIONS/$latest/meta.json"
+ if test -f "$meta_file"
+ set -l model (jq -r '.model // "claude"' "$meta_file" 2>/dev/null)
+ set -l tokens (jq -r '.total_tokens // ""' "$meta_file" 2>/dev/null)
+ if test -n "$model"
+ if test -n "$tokens"
+ printf '%s %sK' "$model" (math "$tokens / 1000" 2>/dev/null) >"$STATE_FILE"
+ else
+ printf '%s' "$model" >"$STATE_FILE"
+ end
+ end
+ end
+ end
+end
+
+# Check OpenCode sessions (falls through if Claude didn't write state)
+if not test -s "$STATE_FILE"
+ set -l OPENCODE_STATE "$HOME/.opencode/state"
+ if test -f "$OPENCODE_STATE"
+ set -l model (jq -r '.model // "opencode"' "$OPENCODE_STATE" 2>/dev/null)
+ if test -n "$model" -a "$model" != "null"
+ printf '%s' "$model" >"$STATE_FILE"
+ end
+ end
+end
diff --git a/Shell/.config/fish/config.fish b/Shell/.config/fish/config.fish
index a8e89b4..4e0400c 100644
--- a/Shell/.config/fish/config.fish
+++ b/Shell/.config/fish/config.fish
@@ -37,3 +37,9 @@ for dir in $PATH
end
end
set -gx PATH $clean_path
+
+# Zen Browser - Profile aliases
+alias zp="zen-browser -P Personal"
+alias zd="zen-browser -P Dev"
+alias zf="zen-browser -P Founder"
+fish_add_path /home/dreamcoder08/.local/share/npm-global/bin
diff --git a/Shell/.config/fish/functions/cheat.fish b/Shell/.config/fish/functions/cheat.fish
new file mode 100644
index 0000000..7925dbd
--- /dev/null
+++ b/Shell/.config/fish/functions/cheat.fish
@@ -0,0 +1,12 @@
+function cheat --description 'Quick command cheat sheet (tldr wrapper)'
+ if test (count $argv) -eq 0
+ echo "Usage: cheat "
+ echo "Shows simplified man pages via tldr or man."
+ return 1
+ end
+ if command -q tldr
+ tldr $argv
+ else
+ man $argv 2>/dev/null; or echo "cheat: install tldr for better results (brew install tldr)"
+ end
+end
diff --git a/Shell/.config/fish/functions/dots.fish b/Shell/.config/fish/functions/dots.fish
new file mode 100644
index 0000000..652487f
--- /dev/null
+++ b/Shell/.config/fish/functions/dots.fish
@@ -0,0 +1,12 @@
+function dots --description 'cd to dreamcoder-dots repo'
+ set -l dir $DREAMCODER_DOTS_DIR
+ if test -z "$dir"
+ set dir "$HOME/Documents/PROYECTOS/dreamcoder-dots"
+ end
+ if test -d "$dir"
+ cd "$dir"
+ else
+ echo "dots: dreamcoder-dots not found at $dir" >&2
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/extract.fish b/Shell/.config/fish/functions/extract.fish
new file mode 100644
index 0000000..857c2d1
--- /dev/null
+++ b/Shell/.config/fish/functions/extract.fish
@@ -0,0 +1,40 @@
+function extract --description 'Extract any archive type'
+ if test (count $argv) -eq 0
+ echo "Usage: extract [output_dir]"
+ return 1
+ end
+ set -l file $argv[1]
+ set -l dir (basename $file .(echo $file | sed 's/.*\.//'))
+ if test (count $argv) -ge 2
+ set dir $argv[2]
+ end
+ switch $file
+ case '*.tar.gz' '*.tgz'
+ tar -xzf $file -C (dirname $file) 2>/dev/null; or tar -xzf $file
+ case '*.tar.bz2' '*.tbz2'
+ tar -xjf $file
+ case '*.tar.xz' '*.txz'
+ tar -xJf $file
+ case '*.tar.zst'
+ tar --zstd -xf $file
+ case '*.tar'
+ tar -xf $file
+ case '*.gz'
+ gunzip -k $file
+ case '*.bz2'
+ bunzip2 -k $file
+ case '*.xz'
+ unxz -k $file
+ case '*.zip'
+ unzip $file -d $dir
+ case '*.rar'
+ unrar x $file $dir
+ case '*.7z'
+ 7z x $file -o$dir
+ case '*.zst'
+ zstd -d $file -o $dir
+ case '*'
+ echo "extract: unknown archive type: $file"
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/http.fish b/Shell/.config/fish/functions/http.fish
new file mode 100644
index 0000000..6b33179
--- /dev/null
+++ b/Shell/.config/fish/functions/http.fish
@@ -0,0 +1,10 @@
+function http --description 'HTTP request with pretty output (httpie wrapper)'
+ if command -q http
+ http $argv
+ else if command -q curl
+ curl -sI $argv[1] 2>/dev/null; or curl $argv
+ else
+ echo "http: install httpie for best results (brew install httpie)"
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/id-dev.fish b/Shell/.config/fish/functions/id-dev.fish
new file mode 100644
index 0000000..fbad85e
--- /dev/null
+++ b/Shell/.config/fish/functions/id-dev.fish
@@ -0,0 +1,3 @@
+function id-dev --description "Switch to Dev identity"
+ identity dev
+end
diff --git a/Shell/.config/fish/functions/id-founder.fish b/Shell/.config/fish/functions/id-founder.fish
new file mode 100644
index 0000000..ed14e4f
--- /dev/null
+++ b/Shell/.config/fish/functions/id-founder.fish
@@ -0,0 +1,3 @@
+function id-founder --description "Switch to Founder identity"
+ identity founder
+end
diff --git a/Shell/.config/fish/functions/id-personal.fish b/Shell/.config/fish/functions/id-personal.fish
new file mode 100644
index 0000000..39a07fe
--- /dev/null
+++ b/Shell/.config/fish/functions/id-personal.fish
@@ -0,0 +1,3 @@
+function id-personal --description "Switch to Personal identity"
+ identity personal
+end
diff --git a/Shell/.config/fish/functions/id-research.fish b/Shell/.config/fish/functions/id-research.fish
new file mode 100644
index 0000000..6430a6d
--- /dev/null
+++ b/Shell/.config/fish/functions/id-research.fish
@@ -0,0 +1,3 @@
+function id-research --description "Switch to Research identity"
+ identity research
+end
diff --git a/Shell/.config/fish/functions/identity.fish b/Shell/.config/fish/functions/identity.fish
new file mode 100644
index 0000000..91dda56
--- /dev/null
+++ b/Shell/.config/fish/functions/identity.fish
@@ -0,0 +1,47 @@
+function identity --description "Switch to an identity workspace: personal, founder, dev, research"
+ switch $argv[1]
+ case personal
+ set -l dir ~/Personal
+ if test -d $dir
+ cd $dir
+ end
+ zen-browser -P Personal &
+ disown
+ echo "🟢 Identity: Personal — ~/Personal/"
+
+ case founder
+ set -l dir ~/Founder
+ if test -d $dir
+ cd $dir
+ end
+ zen-browser -P Founder &
+ disown
+ echo "🚀 Identity: Founder — ~/Founder/"
+
+ case dev
+ set -l dir ~/Dev
+ if test -d $dir
+ cd $dir
+ end
+ zen-browser -P Dev &
+ disown
+ echo "💻 Identity: Dev — ~/Dev/"
+
+ case research
+ set -l dir ~/Research
+ if test -d $dir
+ cd $dir
+ end
+ zen-browser -P Research &
+ disown
+ echo "🔬 Identity: Research — ~/Research/"
+
+ case '*'
+ echo "Usage: identity "
+ echo "Available identities:"
+ echo " personal 🟢 ~/Personal/ — Vida privada"
+ echo " founder 🚀 ~/Founder/ — Marca y presencia pública"
+ echo " dev 💻 ~/Dev/ — Infraestructura técnica"
+ echo " research 🔬 ~/Research/ — Exploración y experimental"
+ end
+end
diff --git a/Shell/.config/fish/functions/killport.fish b/Shell/.config/fish/functions/killport.fish
new file mode 100644
index 0000000..90a19e3
--- /dev/null
+++ b/Shell/.config/fish/functions/killport.fish
@@ -0,0 +1,20 @@
+function killport --description 'Kill process running on a specific port'
+ if test (count $argv) -ne 1
+ echo "Usage: killport "
+ return 1
+ end
+ set -l port $argv[1]
+ set -l pid (lsof -ti :$port 2>/dev/null)
+ if test -z "$pid"
+ echo "killport: no process found on port $port"
+ return 1
+ end
+ echo "Killing PID $pid on port $port..."
+ kill -15 $pid 2>/dev/null
+ sleep 0.5
+ if kill -0 $pid 2>/dev/null
+ echo "Process didn't stop, sending SIGKILL..."
+ kill -9 $pid 2>/dev/null
+ end
+ echo "✓ Port $port freed"
+end
diff --git a/Shell/.config/fish/functions/logs.fish b/Shell/.config/fish/functions/logs.fish
new file mode 100644
index 0000000..4ea26e9
--- /dev/null
+++ b/Shell/.config/fish/functions/logs.fish
@@ -0,0 +1,22 @@
+function logs --description 'Tail system logs with filters'
+ if test (count $argv) -eq 0
+ echo "Usage: logs [service] [options]"
+ echo "Examples:"
+ echo " logs Watch all system logs"
+ echo " logs sshd Watch sshd service"
+ echo " logs --since 1h Last hour of logs"
+ return 1
+ end
+ if command -q journalctl
+ if test (count $argv) -ge 1
+ journalctl -fu $argv
+ else
+ journalctl -f
+ end
+ else if test -f /var/log/syslog
+ tail -f /var/log/syslog
+ else
+ echo "logs: journalctl not available" >&2
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/ports.fish b/Shell/.config/fish/functions/ports.fish
new file mode 100644
index 0000000..1fb665c
--- /dev/null
+++ b/Shell/.config/fish/functions/ports.fish
@@ -0,0 +1,12 @@
+function ports --description 'Show listening ports and their processes'
+ if command -q ss
+ ss -tlnp 2>/dev/null; or ss -tlnp
+ else if command -q netstat
+ netstat -tlnp 2>/dev/null
+ else if command -q lsof
+ lsof -i -P -n | grep LISTEN
+ else
+ echo "ports: install ss (iproute2), netstat, or lsof" >&2
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/sysupdate.fish b/Shell/.config/fish/functions/sysupdate.fish
new file mode 100644
index 0000000..08c4c84
--- /dev/null
+++ b/Shell/.config/fish/functions/sysupdate.fish
@@ -0,0 +1,41 @@
+function sysupdate --description 'Update all system packages'
+ set -l updated 0
+ set -l failed 0
+
+ # Arch Linux
+ if command -q paru
+ echo ":: Updating Arch (paru)..."
+ paru -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1)
+ else if command -q yay
+ echo ":: Updating Arch (yay)..."
+ yay -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1)
+ else if command -q pacman
+ echo ":: Updating Arch (pacman)..."
+ sudo pacman -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1)
+ end
+
+ # Homebrew
+ if command -q brew
+ echo ":: Updating Homebrew..."
+ brew update && brew upgrade; and set updated (math $updated + 1); or set failed (math $failed + 1)
+ end
+
+ # Flatpak
+ if command -q flatpak
+ echo ":: Updating Flatpaks..."
+ flatpak update -y; and set updated (math $updated + 1); or set failed (math $failed + 1)
+ end
+
+ # NPM global
+ if command -q npm
+ echo ":: Updating npm global packages..."
+ npm update -g 2>/dev/null; and set updated (math $updated + 1)
+ end
+
+ echo ""
+ if test $failed -eq 0
+ echo "✓ All $updated package managers updated successfully"
+ else
+ echo "⚠ $updated succeeded, $failed failed" >&2
+ end
+end
diff --git a/Shell/.config/fish/functions/tl.fish b/Shell/.config/fish/functions/tl.fish
new file mode 100644
index 0000000..398de94
--- /dev/null
+++ b/Shell/.config/fish/functions/tl.fish
@@ -0,0 +1,10 @@
+function tl --description "Listar sesiones tmux"
+ if command -q tmux
+ tmux list-sessions 2>/dev/null
+ if test $status -ne 0
+ echo "No hay sesiones tmux activas"
+ end
+ else
+ echo "❌ tmux no está instalado"
+ end
+end
diff --git a/Shell/.config/fish/functions/tm-session.fish b/Shell/.config/fish/functions/tm-session.fish
new file mode 100644
index 0000000..2c88c03
--- /dev/null
+++ b/Shell/.config/fish/functions/tm-session.fish
@@ -0,0 +1,19 @@
+function tm-session --description 'Quick tmux session picker'
+ if not command -q tmux
+ echo "tm-session: tmux not installed" >&2
+ return 1
+ end
+ if not command -q fzf
+ tmux list-sessions 2>/dev/null; or echo "No tmux sessions"
+ return 0
+ end
+
+ set -l session (tmux list-sessions -F "#S" 2>/dev/null | fzf --prompt="tmux session> " --height=10)
+ if test -n "$session"
+ if set -q TMUX
+ tmux switch-client -t "$session"
+ else
+ tmux attach -t "$session"
+ end
+ end
+end
diff --git a/Shell/.config/fish/functions/tm.fish b/Shell/.config/fish/functions/tm.fish
new file mode 100644
index 0000000..7add9f5
--- /dev/null
+++ b/Shell/.config/fish/functions/tm.fish
@@ -0,0 +1,22 @@
+function tm --description "tmux smart attach — crea o adjunta a una sesión"
+ # Uso: tm [nombre-sesion]
+ # Si se omite el nombre, usa "mobile" por defecto (ideal para Termius)
+ set -l session_name "mobile"
+ if test (count $argv) -ge 1
+ set session_name $argv[1]
+ end
+
+ if command -q tmux
+ if set -q TMUX
+ # Ya estamos dentro de tmux
+ echo "Ya estás en una sesión tmux ($TMUX)"
+ return 1
+ end
+
+ # Intenta attach, si no existe la sesión la crea
+ tmux new-session -A -s "$session_name"
+ else
+ echo "❌ tmux no está instalado"
+ return 1
+ end
+end
diff --git a/Shell/.config/fish/functions/tmux-kill-all.fish b/Shell/.config/fish/functions/tmux-kill-all.fish
new file mode 100644
index 0000000..45c1543
--- /dev/null
+++ b/Shell/.config/fish/functions/tmux-kill-all.fish
@@ -0,0 +1,22 @@
+function tmux-kill-all --description "Mata TODAS las sesiones tmux (cuidado)"
+ if command -q tmux
+ set -l sessions (tmux list-sessions -F '#{session_name}' 2>/dev/null)
+ if test (count $sessions) -eq 0
+ echo "No hay sesiones tmux activas"
+ return 0
+ end
+
+ echo "Sesiones activas:"
+ for s in $sessions
+ echo " • $s"
+ end
+ echo ""
+ read -l -p 'echo "¿Matar TODAS? (s/N): "' confirm
+ if test "$confirm" = "s"
+ tmux kill-server
+ echo "✅ Todas las sesiones tmux fueron terminadas"
+ else
+ echo "Cancelado"
+ end
+ end
+end
diff --git a/Shell/.config/shell/aliases/dreamcoder-modern.sh b/Shell/.config/shell/aliases/dreamcoder-modern.sh
new file mode 100644
index 0000000..f83547f
--- /dev/null
+++ b/Shell/.config/shell/aliases/dreamcoder-modern.sh
@@ -0,0 +1,85 @@
+# Dreamcoder modern CLI aliases (shared for bash & zsh)
+# Gracefully degrades when modern tools aren't installed.
+
+## ─── Git shortcuts ────────────────────────────────────
+alias g='git'
+alias gs='git status'
+alias gp='git push'
+alias gl='git log --oneline --graph --all'
+alias gd='git diff'
+alias gc='git commit'
+alias gco='git checkout'
+alias gb='git branch'
+alias ga='git add'
+alias gpl='git pull'
+alias gst='git stash'
+alias glg='git log --oneline --graph --all --decorate'
+alias gundo='git reset --soft HEAD~1'
+alias gcleanup='git branch --merged | grep -v "\*\|main\|master" | xargs -r git branch -d'
+alias gconflicts='git diff --name-only --diff-filter=U'
+alias groot='cd "$(git rev-parse --show-toplevel 2>/dev/null)"'
+
+## ─── Modern CLI replacements ──────────────────────────
+# eza (better ls)
+if command -v eza &>/dev/null; then
+ alias ll='eza -la --icons --group-directories-first'
+ alias la='eza -a --icons --group-directories-first'
+ alias lt='eza -T --icons --group-directories-first'
+ alias l1='eza -1 --icons'
+else
+ alias ll='ls -lahF'
+ alias la='ls -A'
+fi
+
+# bat (better cat)
+command -v bat &>/dev/null && alias cat='bat --paging=never'
+
+# fd (better find)
+command -v fd &>/dev/null && alias find='fd'
+
+# rg (better grep)
+command -v rg &>/dev/null && alias grep='rg'
+
+# zoxide (better cd)
+command -v zoxide &>/dev/null && alias cd='z' && alias cdi='zi'
+
+# Modern replacements (graceful fallback)
+command -v procs &>/dev/null && alias ps='procs'
+command -v btm &>/dev/null && alias top='btm'
+command -v dua &>/dev/null && alias du='dua'
+command -v duf &>/dev/null && alias df='duf'
+command -v sd &>/dev/null && alias sed='sd'
+command -v tldr &>/dev/null && alias help='tldr'
+
+## ─── Quick navigation ─────────────────────────────────
+alias home='cd ~'
+alias ..='cd ..'
+alias ...='cd ../..'
+alias ....='cd ../../..'
+alias docs='cd "${DREAMCODER_DOTS_DIR:-$HOME/Documents/PROYECTOS/dreamcoder-dots}/docs"'
+
+## ─── Extract function ─────────────────────────────────
+extract() {
+ if [ $# -eq 0 ]; then
+ echo "Usage: extract [output_dir]"
+ return 1
+ fi
+ local file="$1" dir="${2:-${file%.*}}"
+ case "$file" in
+ *.tar.gz | *.tgz) tar -xzf "$file" -C "$(dirname "$file")" 2>/dev/null || tar -xzf "$file" ;;
+ *.tar.bz2 | *.tbz2) tar -xjf "$file" ;;
+ *.tar.xz | *.txz) tar -xJf "$file" ;;
+ *.tar.zst) tar --zstd -xf "$file" ;;
+ *.tar) tar -xf "$file" ;;
+ *.gz) gunzip -k "$file" ;;
+ *.bz2) bunzip2 -k "$file" ;;
+ *.xz) unxz -k "$file" ;;
+ *.zip) unzip "$file" -d "$dir" ;;
+ *.rar) unrar x "$file" "$dir" ;;
+ *.7z) 7z x "$file" "-o$dir" ;;
+ *)
+ echo "extract: unknown archive: $file" >&2
+ return 1
+ ;;
+ esac
+}
diff --git a/Shell/.config/starship-dark.toml b/Shell/.config/starship-dark.toml
index f231494..b0a8099 100644
--- a/Shell/.config/starship-dark.toml
+++ b/Shell/.config/starship-dark.toml
@@ -2,22 +2,23 @@
# Dreamcoder Ember Noir OLED — Starship prompt
# ========================================================
# Modern two-line layout with powerline segments.
-# Line 1: context (directory, git)
+# 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"
command_timeout = 500
format = """
-[](fg:prompt_surface0)\
+[\uE0B6](fg:prompt_surface0)\
$username\
-[](bg:prompt_surface1 fg:prompt_surface0)\
+[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\
$directory\
-[](bg:prompt_accent fg:prompt_surface1)\
+[\uE0B0](bg:prompt_accent fg:prompt_surface1)\
$git_branch\
$git_status\
-[](fg:prompt_accent)\
+[\uE0B4](fg:prompt_accent)\
$fill\
$cmd_duration\
$time
@@ -42,6 +43,10 @@ diagnostic = "#5f95ca"
lavender = "#d4b4e6"
mauve = "#e29cb4"
error = "#ed8a7a"
+warning = "#e8b866"
+border = "#968878"
+focus = "#5f8f8f"
+link = "#d99555"
[username]
show_always = true
@@ -51,29 +56,36 @@ format = "[ $user ]($style)"
[directory]
style = "bg:prompt_surface1 fg:prompt_text bold"
-format = "[ $path ]($style)"
+format = "[ $path ]($style)"
truncation_length = 2
truncate_to_repo = true
home_symbol = ""
[git_branch]
-symbol = ""
+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 = "${count} "
-ahead = "⇡${count} "
-behind = "⇣${count} "
-diverged = "⇕⇡${ahead_count}⇣${behind_count} "
+conflicted = "\ue727${count} "
+ahead = "\u21E1${count} "
+behind = "\u21E3${count} "
+diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} "
untracked = "?${count} "
-stashed = "${count} "
+stashed = "\uf0CF${count} "
modified = "~${count} "
staged = "+${count} "
-renamed = "»${count} "
-deleted = "✘${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 = " "
@@ -81,7 +93,7 @@ symbol = " "
[cmd_duration]
min_time = 2500
style = "fg:prompt_muted"
-format = "[ $duration ]($style)"
+format = "[ \uf552 $duration ]($style)"
[time]
disabled = false
@@ -89,38 +101,44 @@ format = "[ $time ]($style)"
style = "fg:prompt_muted"
[character]
-success_symbol = "[❯](bold fg:prompt_accent)"
-error_symbol = "[❯](bold fg:error)"
-vimcmd_symbol = "[❮](bold fg:sage)"
+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 = ""
+symbol = "\uF5EF"
style = "fg:prompt_accent bold"
format = "[ $symbol $version]($style)"
[nodejs]
-symbol = ""
+symbol = "\uE718"
style = "fg:sage bold"
format = "[ $symbol $version]($style)"
[python]
-symbol = ""
-style = "fg:diagnostic bold"
+symbol = "\uE73C"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[golang]
-symbol = ""
-style = "fg:lavender bold"
+symbol = "\uE627"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[rust]
-symbol = ""
+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 = ""
-style = "fg:diagnostic bold"
+symbol = "\uf308"
+style = "fg:diag bold"
format = "[ $symbol $context]($style)"
only_with_files = true
diff --git a/Shell/.config/starship-light.toml b/Shell/.config/starship-light.toml
index 67b2557..ad761d0 100644
--- a/Shell/.config/starship-light.toml
+++ b/Shell/.config/starship-light.toml
@@ -2,22 +2,23 @@
# Dreamcoder Light — Starship prompt
# ========================================================
# Modern two-line layout with powerline segments.
-# Line 1: context (directory, git)
+# 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"
command_timeout = 500
format = """
-[](fg:prompt_surface0)\
+[\uE0B6](fg:prompt_surface0)\
$username\
-[](bg:prompt_surface1 fg:prompt_surface0)\
+[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\
$directory\
-[](bg:prompt_accent fg:prompt_surface1)\
+[\uE0B0](bg:prompt_accent fg:prompt_surface1)\
$git_branch\
$git_status\
-[](fg:prompt_accent)\
+[\uE0B4](fg:prompt_accent)\
$fill\
$cmd_duration\
$time
@@ -42,6 +43,10 @@ diagnostic = "#0d4a68"
lavender = "#57478b"
mauve = "#7d3e64"
error = "#842f24"
+warning = "#654300"
+border = "#66513b"
+focus = "#0f6570"
+link = "#824f16"
[username]
show_always = true
@@ -51,29 +56,36 @@ format = "[ $user ]($style)"
[directory]
style = "bg:prompt_surface1 fg:prompt_text bold"
-format = "[ $path ]($style)"
+format = "[ $path ]($style)"
truncation_length = 2
truncate_to_repo = true
home_symbol = ""
[git_branch]
-symbol = ""
+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 = "${count} "
-ahead = "⇡${count} "
-behind = "⇣${count} "
-diverged = "⇕⇡${ahead_count}⇣${behind_count} "
+conflicted = "\ue727${count} "
+ahead = "\u21E1${count} "
+behind = "\u21E3${count} "
+diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} "
untracked = "?${count} "
-stashed = "${count} "
+stashed = "\uf0CF${count} "
modified = "~${count} "
staged = "+${count} "
-renamed = "»${count} "
-deleted = "✘${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 = " "
@@ -81,7 +93,7 @@ symbol = " "
[cmd_duration]
min_time = 2500
style = "fg:prompt_muted"
-format = "[ $duration ]($style)"
+format = "[ \uf552 $duration ]($style)"
[time]
disabled = false
@@ -89,38 +101,44 @@ format = "[ $time ]($style)"
style = "fg:prompt_muted"
[character]
-success_symbol = "[❯](bold fg:prompt_accent)"
-error_symbol = "[❯](bold fg:error)"
-vimcmd_symbol = "[❮](bold fg:sage)"
+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 = ""
+symbol = "\uF5EF"
style = "fg:prompt_accent bold"
format = "[ $symbol $version]($style)"
[nodejs]
-symbol = ""
+symbol = "\uE718"
style = "fg:sage bold"
format = "[ $symbol $version]($style)"
[python]
-symbol = ""
-style = "fg:diagnostic bold"
+symbol = "\uE73C"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[golang]
-symbol = ""
-style = "fg:lavender bold"
+symbol = "\uE627"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[rust]
-symbol = ""
+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 = ""
-style = "fg:diagnostic bold"
+symbol = "\uf308"
+style = "fg:diag bold"
format = "[ $symbol $context]($style)"
only_with_files = true
diff --git a/Shell/.config/starship.toml b/Shell/.config/starship.toml
index 67b2557..ad761d0 100644
--- a/Shell/.config/starship.toml
+++ b/Shell/.config/starship.toml
@@ -2,22 +2,23 @@
# Dreamcoder Light — Starship prompt
# ========================================================
# Modern two-line layout with powerline segments.
-# Line 1: context (directory, git)
+# 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"
command_timeout = 500
format = """
-[](fg:prompt_surface0)\
+[\uE0B6](fg:prompt_surface0)\
$username\
-[](bg:prompt_surface1 fg:prompt_surface0)\
+[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\
$directory\
-[](bg:prompt_accent fg:prompt_surface1)\
+[\uE0B0](bg:prompt_accent fg:prompt_surface1)\
$git_branch\
$git_status\
-[](fg:prompt_accent)\
+[\uE0B4](fg:prompt_accent)\
$fill\
$cmd_duration\
$time
@@ -42,6 +43,10 @@ diagnostic = "#0d4a68"
lavender = "#57478b"
mauve = "#7d3e64"
error = "#842f24"
+warning = "#654300"
+border = "#66513b"
+focus = "#0f6570"
+link = "#824f16"
[username]
show_always = true
@@ -51,29 +56,36 @@ format = "[ $user ]($style)"
[directory]
style = "bg:prompt_surface1 fg:prompt_text bold"
-format = "[ $path ]($style)"
+format = "[ $path ]($style)"
truncation_length = 2
truncate_to_repo = true
home_symbol = ""
[git_branch]
-symbol = ""
+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 = "${count} "
-ahead = "⇡${count} "
-behind = "⇣${count} "
-diverged = "⇕⇡${ahead_count}⇣${behind_count} "
+conflicted = "\ue727${count} "
+ahead = "\u21E1${count} "
+behind = "\u21E3${count} "
+diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} "
untracked = "?${count} "
-stashed = "${count} "
+stashed = "\uf0CF${count} "
modified = "~${count} "
staged = "+${count} "
-renamed = "»${count} "
-deleted = "✘${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 = " "
@@ -81,7 +93,7 @@ symbol = " "
[cmd_duration]
min_time = 2500
style = "fg:prompt_muted"
-format = "[ $duration ]($style)"
+format = "[ \uf552 $duration ]($style)"
[time]
disabled = false
@@ -89,38 +101,44 @@ format = "[ $time ]($style)"
style = "fg:prompt_muted"
[character]
-success_symbol = "[❯](bold fg:prompt_accent)"
-error_symbol = "[❯](bold fg:error)"
-vimcmd_symbol = "[❮](bold fg:sage)"
+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 = ""
+symbol = "\uF5EF"
style = "fg:prompt_accent bold"
format = "[ $symbol $version]($style)"
[nodejs]
-symbol = ""
+symbol = "\uE718"
style = "fg:sage bold"
format = "[ $symbol $version]($style)"
[python]
-symbol = ""
-style = "fg:diagnostic bold"
+symbol = "\uE73C"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[golang]
-symbol = ""
-style = "fg:lavender bold"
+symbol = "\uE627"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[rust]
-symbol = ""
+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 = ""
-style = "fg:diagnostic bold"
+symbol = "\uf308"
+style = "fg:diag bold"
format = "[ $symbol $context]($style)"
only_with_files = true
diff --git a/Tmux/.tmux.conf b/Tmux/.tmux.conf
index ad5a475..5f8ec23 100644
--- a/Tmux/.tmux.conf
+++ b/Tmux/.tmux.conf
@@ -9,8 +9,9 @@ set -g history-limit 50000
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
-set -s escape-time 0
+set -s escape-time 10
set -g focus-events on
+set -g set-clipboard on
# ─── Key Bindings ──────────────────────────────────────
# Prefix: Ctrl+a (like screen)
@@ -18,31 +19,54 @@ set -g prefix C-a
unbind C-b
bind C-a send-prefix
-# Split panes
-bind | split-window -h -c "#{pane_current_path}"
-bind - split-window -v -c "#{pane_current_path}"
+# Split panes (Gentleman-style: v = vertical, d = horizontal)
unbind '"'
unbind %
+bind v split-window -h -c "#{pane_current_path}"
+bind d split-window -v -c "#{pane_current_path}"
# Vi mode
setw -g mode-keys vi
bind -T copy-mode-vi v send-keys -X begin-selection
-bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
+bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel
-# Navigate panes with Alt+arrow
-bind -n M-Left select-pane -L
-bind -n M-Right select-pane -R
-bind -n M-Up select-pane -U
-bind -n M-Down select-pane -D
+# Navigate panes with vim-tmux-navigator (Alt+h/j/k/l)
+bind -n M-h select-pane -L
+bind -n M-j select-pane -D
+bind -n M-k select-pane -U
+bind -n M-l select-pane -R
# Reload config
bind r source-file ~/.tmux.conf \; display-message "Config reloaded!"
+# ─── Plugin: TPM ───────────────────────────────────────
+set -g @plugin 'tmux-plugins/tpm'
+set -g @plugin 'tmux-plugins/tmux-sensible'
+set -g @plugin 'tmux-plugins/tmux-yank'
+set -g @plugin 'christoomey/vim-tmux-navigator'
+set -g @plugin 'tmux-plugins/tmux-resurrect'
+
+# Tmux-resurrect
+set -g @resurrect-capture-pane-contents 'on'
+set -g @resurrect-strategy-nvim 'session'
+bind C-s run '~/.tmux/plugins/tmux-resurrect/scripts/save.sh'
+
+# ─── Session Management ────────────────────────────────
+# Scratch terminal popup (Alt+g)
+bind -n M-g if-shell -F '#{==:#{session_name},scratch}' {
+ detach-client
+} {
+ display-popup -d "#{pane_current_path}" -E -k "tmux new-session -A -s scratch"
+}
+
+# fzf-based session picker (prefix + f)
+bind f run "tmux list-sessions -F '#S' | fzf --prompt='session> ' --height=10 | xargs -r tmux switch-client -t"
+
+# Kill all sessions except current
+bind K confirm-before -p "Kill all other sessions? (y/n)" "kill-session -a"
+
# ─── Theme (Dreamcoder) ────────────────────────────────
# Source the active theme generated by `dreamcoder-theme sync`
-# The sync command creates a single active variant file at:
-# ~/.config/tmux/tmux-dreamcoder.conf
-# Override with: TMUX_THEME=/path/to/theme.conf
source-file ~/.config/tmux/tmux-dreamcoder.conf
# ─── Status Bar ────────────────────────────────────────
@@ -51,3 +75,6 @@ set -g status-interval 5
set -g status-justify left
set -g status-left-length 40
set -g status-right-length 60
+
+# ─── TPM init (keep last) ──────────────────────────────
+run '~/.tmux/plugins/tpm/tpm'
diff --git a/Warp/.config/warp-terminal/settings.toml b/Warp/.config/warp-terminal/settings.toml
index a106cbe..a68cb86 100644
--- a/Warp/.config/warp-terminal/settings.toml
+++ b/Warp/.config/warp-terminal/settings.toml
@@ -30,12 +30,12 @@ zoom_level = 70
override_opacity = 96
[appearance.themes]
-theme = { custom = { name = "Dreamcoder Light", path = "Dreamcoder-Light.yaml" } }
-system_theme = false
selected_system_themes = {
dark = "Dreamcoder-Dark",
light = "Dreamcoder-Light",
}
+system_theme = false
+theme = { custom = { name = "Dreamcoder Light", path = "Dreamcoder-Light.yaml" } }
[appearance.text]
font_name = "JetBrainsMono Nerd Font"
@@ -68,11 +68,20 @@ telemetry_enabled = true
crash_reporting_enabled = true
custom_secret_regex_list = [
{ name = "IPv4 Address", pattern = '\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}\b' },
- { name = "IPv6 Address", pattern = '\b((([0-9A-Fa-f]{1,4}:){1,6}:)|(([0-9A-Fa-f]{1,4}:){7}))([0-9A-Fa-f]{1,4})\b' },
+ {
+ name = "IPv6 Address",
+ pattern = '\b((([0-9A-Fa-f]{1,4}:){1,6}:)|(([0-9A-Fa-f]{1,4}:){7}))([0-9A-Fa-f]{1,4})\b',
+ },
{ name = "Slack App Token", pattern = '\bxapp-[0-9]+-[A-Za-z0-9_]+-[0-9]+-[a-f0-9]+\b' },
{ name = "Phone Number", pattern = '\b(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b' },
- { name = "AWS Access ID", pattern = '\b(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}\b' },
- { name = "MAC Address", pattern = '\b((([a-zA-z0-9]{2}[-:]){5}([a-zA-z0-9]{2}))|(([a-zA-z0-9]{2}:){5}([a-zA-z0-9]{2})))\b' },
+ {
+ name = "AWS Access ID",
+ pattern = '\b(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}\b',
+ },
+ {
+ name = "MAC Address",
+ pattern = '\b((([a-zA-z0-9]{2}[-:]){5}([a-zA-z0-9]{2}))|(([a-zA-z0-9]{2}:){5}([a-zA-z0-9]{2})))\b',
+ },
{ name = "Google API Key", pattern = '\bAIza[0-9A-Za-z-_]{35}\b' },
{ name = "Google OAuth ID", pattern = '\b[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com\b' },
{ name = "GitHub Classic PAT", pattern = '\bghp_[A-Za-z0-9_]{36}\b' },
diff --git a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml
index c65767a..22c7f47 100644
--- a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml
+++ b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml
@@ -16,10 +16,10 @@ terminal_colors:
white: '#c7b9aa'
bright:
black: '#938274'
- red: '#e9a092'
- green: '#75a579'
+ red: '#ec9989'
+ green: '#69bb73'
yellow: '#e8b866'
- blue: '#579ba4'
- magenta: '#846fc5'
- cyan: '#6fa5a5'
+ blue: '#78a2cb'
+ magenta: '#d8bce2'
+ cyan: '#789d9b'
white: '#e8dfd0'
diff --git a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml
index bff7afa..2883718 100644
--- a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml
+++ b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml
@@ -16,10 +16,10 @@ terminal_colors:
white: '#352e22'
bright:
black: '#554638'
- red: '#815850'
- green: '#4f6f51'
+ red: '#702a20'
+ green: '#366134'
yellow: '#654300'
- blue: '#3a686e'
- magenta: '#6c5ba2'
- cyan: '#4b6f6f'
+ blue: '#0f4058'
+ magenta: '#4b3d74'
+ cyan: '#10565e'
white: '#17120d'
diff --git a/Zellij/.config/zellij/config.kdl b/Zellij/.config/zellij/config.kdl
index 3679a68..971c600 100644
--- a/Zellij/.config/zellij/config.kdl
+++ b/Zellij/.config/zellij/config.kdl
@@ -36,4 +36,4 @@ plugins {
}
// Theme
-theme "dreamcoder-light"
\ No newline at end of file
+theme "dreamcoder-light"
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..821e713
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,25 @@
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'type-enum': [
+ 2,
+ 'always',
+ [
+ 'feat',
+ 'fix',
+ 'docs',
+ 'style',
+ 'refactor',
+ 'perf',
+ 'test',
+ 'chore',
+ 'ci',
+ 'build',
+ 'revert',
+ ],
+ ],
+ 'subject-case': [2, 'always', ['lower-case', 'sentence-case']],
+ 'subject-empty': [2, 'never'],
+ 'type-empty': [2, 'never'],
+ },
+}
diff --git a/docs/ai-integration.md b/docs/ai-integration.md
new file mode 100644
index 0000000..05b17f1
--- /dev/null
+++ b/docs/ai-integration.md
@@ -0,0 +1,100 @@
+# Dreamcoder AI Integration
+
+> Cómo dreamcoder se integra con Claude Code, OpenCode, Pi, y otras herramientas de IA.
+
+## AI Session State en el Prompt
+
+Dreamcoder tiene un módulo de Starship (`[custom.ai_session]`) que muestra el estado de tu sesión de IA actual en el prompt:
+
+```
+⎔ claude-4 42K
+```
+
+Esto se lee de `~/.cache/dreamcoder/ai-session.state`, que se actualiza automáticamente cuando:
+
+- **Claude Code** tiene una sesión activa (`~/.claude/sessions/`)
+- **OpenCode** está corriendo (`~/.opencode/state`)
+- **Codex CLI** tiene contexto activo
+
+### Cómo funciona
+
+```mermaid
+flowchart LR
+ A["Claude/OpenCode
Session Active"] --> B["25-dreamcoder-ai-env.fish
Detecta y escribe estado"]
+ B --> C["~/.cache/dreamcoder/
ai-session.state"]
+ C --> D["Starship
custom.ai_session module"]
+ D --> E["Prompt muestra:
⎔ claude-4 42K"]
+```
+
+### Deshabilitar
+
+```fish
+set -gx DREAMCODER_AI_SESSION_DISABLED 1
+```
+
+---
+
+## Pi Agent Theme
+
+Dreamcoder genera un tema para Pi (el coding agent) que se escribe en:
+
+- `~/.pi/agent/themes/dreamcoder.json`
+- `~/.pi/agent/themes/dreamcoder-dark.json`
+- `~/.pi/agent/themes/dreamcoder-light.json`
+
+El tema se activa automáticamente via `ensure_pi_theme_settings()` que setea `theme: "dreamcoder"` en `~/.pi/agent/settings.json`.
+
+### Mode Switching
+
+Cuando cambiás de modo (`dreamcoder dark` / `dreamcoder light`), el tema de Pi se actualiza automáticamente:
+
+```bash
+dreamcoder dark # Pi → dreamcoder-dark.json
+dreamcoder light # Pi → dreamcoder-light.json
+```
+
+---
+
+## OpenCode Theme
+
+Dreamcoder genera temas para OpenCode en:
+
+- `~/.config/opencode/themes/dreamcoder.json`
+- `.opencode/themes/dreamcoder.json` (repo copy)
+
+El TUI de OpenCode usa el tema dreamcoder con fondo transparente para mejor integración visual.
+
+---
+
+## Codex CLI
+
+Dreamcoder genera temas `.tmTheme` para Codex CLI:
+
+- `~/.codex/themes/Dreamcoder.tmTheme`
+- `~/.codex/themes/Dreamcoder-Dark.tmTheme`
+- `~/.codex/themes/Dreamcoder-Light.tmTheme`
+
+Y también temas `.codex-theme.json` para Codex App.
+
+---
+
+## CLAUDE.md
+
+Dreamcoder incluye un `CLAUDE.md` con instrucciones para Claude Code sobre cómo trabajar con el repositorio. Cubre:
+
+- Reglas de shell scripts (`set -euo pipefail`, quoting, `[[ ]]`)
+- Modularidad (un archivo = un propósito)
+- Safety (safe sourcing, sin hardcoded paths)
+
+---
+
+## Caso de Uso: Desarrollo con IA
+
+1. Abrí Neovim (vía Gentleman.Dots) → 29 plugins, dreamcoder colorscheme
+2. Iniciá Claude Code → `⎔ claude-4` aparece en el prompt
+3. Escribí código con autocompletado (blink.cmp), fuzzy finder (fzf-lua), debugging (DAP)
+4. Need a command? `cheat tar` → TLDR para tar
+5. Need to extract something? `extract project.tar.gz`
+6. Cambiando de tarea? `tm-session` → fzf session picker
+7. Terminando el día? `sysupdate` → actualiza todo
+8. El timer systemd cambia automáticamente a Ember Noir a las 18:00
diff --git a/docs/configuration/theme-system.md b/docs/configuration/theme-system.md
index d119f79..6fd77cf 100644
--- a/docs/configuration/theme-system.md
+++ b/docs/configuration/theme-system.md
@@ -2,44 +2,60 @@
## Architecture
+Three-layer design system:
+
+```txt
+primitives (OKLCH ramps) → semantic tokens (tokens.json) → component themes (renderers)
```
-tokens.json → Theme Engine (Python) → 48 config files → 22 targets
-```
-## Token Schema
-
-All colors are defined in `themes/dreamcoder/tokens.json`:
-
-```json
-{
- "dark": {
- "background": "#100f0d",
- "text": "#e8dfd0",
- "accent": "#d99555",
- "accent_2": "#c96a45",
- "diagnostic": "#5f95ca"
- },
- "light": {
- "background": "#f3eadc",
- "text": "#17120d",
- "accent": "#824f16",
- "accent_2": "#b85c2a",
- "diagnostic": "#3a7bc8"
- }
-}
+Pipeline:
+
+```txt
+tokens.json → generate-palette-tokens.py → palette_tokens.py
+ → dreamcoder sync → 48+ config files → 22+ targets
```
-## Regenerating Themes
+## Canonical tokens
+
+Single source of truth: [`themes/dreamcoder/tokens.json`](../../themes/dreamcoder/tokens.json)
+
+Dark (**Ember Noir OLED**) and light (**Cocoa/Lúcuma**) share the same semantic key set:
+
+| Layer | Examples |
+| --- | --- |
+| Surfaces | `bg`, `bg_soft`, `surface0`–`surface3` |
+| Text | `text`, `text_heading`, `muted`, `subtle`, `comment` |
+| Brand | `accent`, `accent_2`, `link`, `link_hover` |
+| Feedback | `error`, `warning`, `success`, `info`, `diagnostic` |
+| On-colors | `on_surface`, `on_accent`, `on_error`, `on_focus` |
+| Interaction | `selection_bg`, `selection_fg`, `hover`, `pressed`, `disabled` |
+| Chrome | `border`, `border_ui`, `border_hi`, `focus`, `panel_rgba` |
+
+## Regenerating themes
After editing `tokens.json`:
```bash
-./scripts/dreamcoder sync
+./scripts/generate-palette-tokens.py # sync palette_tokens.py + derived tokens
+./scripts/dreamcoder sync # propagate to all targets
+./scripts/verify-theme-health.py # WCAG + APCA gates (light and dark)
```
-## Adding New Targets
+## Quality gates
+
+- WCAG 2.2: body text ≥ 4.5:1 (main text ≥ 7:1)
+- APCA: body Lc ≥ 75 light / ≥ 50 dark; `on_accent` Lc ≥ 54 on filled accent
+- CI validates **both** light and dark Kitty, Starship, Ghostty, Waybar, Hypr, Rofi, Btop, Dunst, Fzf
+
+## Design decisions
+
+- **accent** = lúcuma/ember orange (identity, tabs, CTAs)
+- **focus** = teal ring (keyboard focus, inputs) — separate from brand orange on purpose
+- **adaptive/matugen** may tint surfaces but identity tokens win per `CLAUDE.md`
+
+## Adding new targets
-1. Create renderer in `scripts/dreamcoder_theme/renderers_.py`
-2. Add token mapping
-3. Run `./scripts/dreamcoder sync`
-4. Files appear in `themes/dreamcoder/`
+1. Create renderer in `src/dreamcoder_theme/renderers_.py`
+2. Map semantic tokens (never raw hex in renderers)
+3. Register in `src/dreamcoder_theme/sync.py`
+4. Run `./scripts/dreamcoder sync`
diff --git a/docs/dreamcoder-theme-preview.md b/docs/dreamcoder-theme-preview.md
index 88c86d7..b141e1b 100644
--- a/docs/dreamcoder-theme-preview.md
+++ b/docs/dreamcoder-theme-preview.md
@@ -12,36 +12,45 @@ Semantic tokens are intentionally distinct:
- `comment` is softer and lower-chroma than `subtle` (syntax vs UI chrome).
- Dark `accent` (refined ember orange), `accent_2` (maple red), `error` (soft coral red), and `warning` (lúcuma gold) form the orange/red/gold signature.
-- `focus` follows the orange protagonist instead of a separate cyan ring; `diagnostic` stays warm amber so the palette remains autumnal.
-- **Dusk** bridges daytime light and night dark for late-afternoon sessions on Arch.
-
+- `accent` carries brand CTAs and active chrome; `focus` is teal for keyboard/input affordance (WCAG ring).
+- `on_accent`, `on_error`, and `selection_bg`/`selection_fg` are explicit pairs validated in CI.
## Palette
### Dreamcoder Ember Noir OLED
| Role | Color |
| --- | --- |
-| `bg` | `#12100e` |
-| `bg_soft` | `#1b1612` |
-| `surface0` | `#211c18` |
-| `surface1` | `#2e241f` |
-| `surface2` | `#3e3129` |
+| `bg` | `#100f0d` |
+| `bg_soft` | `#181512` |
+| `surface0` | `#201b16` |
+| `surface1` | `#2b231b` |
+| `surface2` | `#392e21` |
+| `surface3` | `#4a3b2a` |
| `text` | `#e8dfd0` |
+| `text_heading` | `#f4ecdd` |
| `muted` | `#c7b9aa` |
| `subtle` | `#938274` |
| `comment` | `#b8a99a` |
| `accent` | `#d99555` |
| `accent_2` | `#c96a45` |
| `diagnostic` | `#5f95ca` |
-| `sage` | `#388c48` |
-| `lavender` | `#c9a8dc` |
-| `mauve` | `#d98aa9` |
-| `error` | `#e98272` |
+| `sage` | `#4db35f` |
+| `success` | `#4db35f` |
+| `info` | `#5f95ca` |
+| `lavender` | `#d4b4e6` |
+| `mauve` | `#e29cb4` |
+| `error` | `#ed8a7a` |
| `warning` | `#e8b866` |
-| `border` | `#594d46` |
-| `border_ui` | `#806754` |
+| `on_accent` | `#100f0d` |
+| `on_error` | `#100f0d` |
+| `link` | `#d99555` |
+| `link_hover` | `#c96a45` |
+| `selection_bg` | `#2b231b` |
+| `selection_fg` | `#e8dfd0` |
+| `border` | `#756052` |
+| `border_ui` | `#968878` |
| `border_hi` | `#c8b195` |
-| `focus` | `#d99555` |
+| `focus` | `#5f8f8f` |
### Dreamcoder Light
@@ -52,7 +61,9 @@ Semantic tokens are intentionally distinct:
| `surface0` | `#fff7ea` |
| `surface1` | `#decbb1` |
| `surface2` | `#c8ad89` |
+| `surface3` | `#b89d7a` |
| `text` | `#17120d` |
+| `text_heading` | `#100c07` |
| `muted` | `#352e22` |
| `subtle` | `#554638` |
| `comment` | `#725e4c` |
@@ -60,10 +71,18 @@ Semantic tokens are intentionally distinct:
| `accent_2` | `#a7471c` |
| `diagnostic` | `#0d4a68` |
| `sage` | `#3d723d` |
+| `success` | `#3d723d` |
+| `info` | `#0d4a68` |
| `lavender` | `#57478b` |
| `mauve` | `#7d3e64` |
| `error` | `#842f24` |
| `warning` | `#654300` |
+| `on_accent` | `#fff7ea` |
+| `on_error` | `#fff7ea` |
+| `link` | `#824f16` |
+| `link_hover` | `#a7471c` |
+| `selection_bg` | `#decbb1` |
+| `selection_fg` | `#17120d` |
| `border` | `#8a7358` |
| `border_ui` | `#66513b` |
| `border_hi` | `#3e2f20` |
@@ -78,7 +97,9 @@ Semantic tokens are intentionally distinct:
| `surface0` | `#f1eadf` |
| `surface1` | `#d8cbb8` |
| `surface2` | `#c6b6a0` |
+| `surface3` | `#b6a691` |
| `text` | `#1a1713` |
+| `text_heading` | `#13100c` |
| `muted` | `#4c443a` |
| `subtle` | `#5a4f43` |
| `comment` | `#615548` |
@@ -86,10 +107,18 @@ Semantic tokens are intentionally distinct:
| `accent_2` | `#96411e` |
| `diagnostic` | `#104b67` |
| `sage` | `#466b41` |
+| `success` | `#466b41` |
+| `info` | `#104b67` |
| `lavender` | `#5b4e86` |
| `mauve` | `#784762` |
| `error` | `#773126` |
| `warning` | `#604000` |
+| `on_accent` | `#f1eadf` |
+| `on_error` | `#f1eadf` |
+| `link` | `#8a5520` |
+| `link_hover` | `#96411e` |
+| `selection_bg` | `#d8cbb8` |
+| `selection_fg` | `#1a1713` |
| `border` | `#a7947a` |
| `border_ui` | `#665845` |
| `border_hi` | `#4a3f32` |
@@ -101,39 +130,39 @@ Semantic tokens are intentionally distinct:
| Token | Ratio vs bg | Target |
| --- | ---: | --- |
-| `text` | 14.37:1 | AAA |
-| `muted` | 9.90:1 | AA |
-| `comment` | 8.30:1 | AA |
-| `accent` | 7.58:1 | AA |
-| `accent_2` | 5.09:1 | AA |
-| `diagnostic` | 6.00:1 | AA |
-| `sage` | 4.53:1 | AA |
-| `error` | 7.14:1 | AA |
-| `warning` | 10.38:1 | AA |
+| `text` | 14.50:1 | AAA |
+| `muted` | 9.99:1 | AA |
+| `comment` | 8.37:1 | AA |
+| `accent` | 7.65:1 | AA |
+| `accent_2` | 5.14:1 | AA |
+| `diagnostic` | 6.06:1 | AA |
+| `sage` | 7.23:1 | AA |
+| `error` | 7.77:1 | AA |
+| `warning` | 10.47:1 | AA |
### Dreamcoder Ember Noir OLED APCA
| Token | Lc vs bg | Target |
| --- | ---: | --- |
-| `text` | 87.4 | ≥75 (body) |
-| `muted` | 65.3 | ≥75 (FAIL) |
-| `comment` | 56.5 | ≥75 (FAIL) |
-| `accent` | 52.5 | ≥75 (FAIL) |
-| `accent_2` | 36.8 | ≥75 (FAIL) |
+| `text` | 87.5 | ≥75 (body) |
+| `muted` | 65.4 | ≥75 (FAIL) |
+| `comment` | 56.6 | ≥75 (FAIL) |
+| `accent` | 52.6 | ≥75 (FAIL) |
+| `accent_2` | 36.9 | ≥75 (FAIL) |
| `diagnostic` | 42.7 | ≥75 (FAIL) |
-| `sage` | 32.6 | ≥75 (FAIL) |
-| `error` | 50.1 | ≥75 (FAIL) |
-| `warning` | 68.0 | ≥75 (FAIL) |
-| `border_ui` | 25.1 | ≥60 (FAIL) |
-| `focus` | 52.5 | ≥60 (FAIL) |
+| `sage` | 50.3 | ≥75 (FAIL) |
+| `error` | 53.4 | ≥75 (FAIL) |
+| `warning` | 68.1 | ≥75 (FAIL) |
+| `border_ui` | 39.3 | ≥60 (FAIL) |
+| `focus` | 37.6 | ≥60 (FAIL) |
### Dreamcoder Ember Noir OLED UI affordance contrast
| Token | Ratio vs bg | Target |
| --- | ---: | --- |
-| `border_ui` | 3.60:1 | PASS |
-| `border_hi` | 9.20:1 | PASS |
-| `focus` | 7.58:1 | PASS |
+| `border_ui` | 5.56:1 | PASS |
+| `border_hi` | 9.29:1 | PASS |
+| `focus` | 5.30:1 | PASS |
### Dreamcoder Light contrast (WCAG 2)
@@ -216,7 +245,6 @@ Semantic tokens are intentionally distinct:
```bash
./scripts/dreamcoder auto
./scripts/dreamcoder light
-./scripts/dreamcoder dusk
./scripts/dreamcoder dark
./scripts/dreamcoder verify
./scripts/dreamcoder preview
@@ -226,6 +254,6 @@ Semantic tokens are intentionally distinct:
- Main backgrounds avoid pure black and pure white.
- Main text targets AAA (WCAG 2) and APCA Lc ≥ 75 for long coding sessions.
-- Cocoa/Lúcuma accents are identity colors in light/dusk; Ember Noir uses refined orange, maple red, soft coral, and gold for dark-mode personality.
+- Cocoa/Lúcuma accents are identity colors in light; Ember Noir uses refined orange, maple red, soft coral, and gold for dark-mode personality.
- UI affordance tokens (`border_ui`, `border_hi`, `focus`) target at least 3:1 against the main background.
- opencode uses one canonical theme: `dreamcoder`; its main `background` is generated as `none` for terminal transparency.
diff --git a/docs/pypi-publishing.md b/docs/pypi-publishing.md
new file mode 100644
index 0000000..5449210
--- /dev/null
+++ b/docs/pypi-publishing.md
@@ -0,0 +1,37 @@
+# Publishing dreamcoder-theme to PyPI
+
+The release workflow builds the Python package as an artifact but does NOT
+automatically publish to PyPI yet, because it requires a Trusted Publisher
+to be configured on PyPI's end.
+
+## One-time setup: Configure Trusted Publisher
+
+1. Go to
+2. Add a new trusted publisher:
+ - **PyPI Project**: `dreamcoder-theme`
+ - **Publisher**: `GitHub`
+ - **Owner**: `Dreamcoder08`
+ - **Repository**: `Dreamcoder_dots`
+ - **Workflow**: `release.yml`
+ - **Environment**: (leave empty)
+
+3. Once configured, the `publish-pypi` job in `.github/workflows/release.yml`
+ will automatically publish to PyPI on every `v*` tag push.
+
+## Manual publish (without Trusted Publisher)
+
+```bash
+# Build
+pip install build
+python -m build
+
+# Upload manually
+pip install twine
+twine upload dist/*
+```
+
+## Required secrets
+
+No secrets needed — PyPI's Trusted Publisher uses OIDC (OpenID Connect)
+which authenticates directly via GitHub's OIDC token. Just configure
+the publisher on PyPI and it works.
diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md b/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md
new file mode 100644
index 0000000..4e44e5d
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md
@@ -0,0 +1,81 @@
+# SDD Plan 01: Overlay Compatibility — Gentleman.Dots + ML4W
+
+> **Goal:** Guarantee that dreamcoder-dots theme hooks work flawlessly on top of both Gentleman.Dots and ML4W installations. The user installs Gentleman → ML4W → dreamcoder, and everything just works.
+> **Strategy:** Verify every theme hook path, include name, and file reference against BOTH base repos. Fix mismatches.
+> **Priority:** 🔴 CRITICAL — prerequisite for everything else
+> **Estimated diff:** ~300 lines across 15+ files (mostly fixes)
+
+## Context
+
+dreamcoder-dots is designed as a **visual overlay** — it generates color files that get `@import`ed or `[include]`d by the base configs from Gentleman.Dots and ML4W. But there are **friction points**:
+
+- Path mismatches: dreamcoder writes to `~/.config/hypr/colors.conf` but ML4W includes from `~/.config/hypr/colors.conf`? Verificar.
+- File naming: `dunst-dreamcoder-dark.conf` vs what dunstrc expects
+- Include syntax differences: Lua vs conf vs CSS imports
+- Mode switching: dreamcoder auto-switches dark/light, pero los includes tienen que actualizarse dinámicamente
+
+## What to Verify
+
+### Layer 1: Gentleman.Dots Overlay
+
+| Component | dreamcoder hook | Gentleman base | Integration Status |
+| ------------ | --------------------------------- | ----------------------------------------- | -------------------------------- |
+| **Ghostty** | `themes/dreamcoder-{mode}` | `GentlemanGhostty/config` `theme = ...` | 🔍 Verify path resolution |
+| **Kitty** | `colors-dreamcoder-{mode}.conf` | `GentlemanKitty/kitty.conf` `include` | 🔍 Verify include directive |
+| **Tmux** | `dreamcoder-{mode}.conf` | `GentlemanTmux/tmux.conf` `source-file` | 🔍 Verify source-file path |
+| **Zellij** | `dreamcoder-{mode}.kdl` | `GentlemanZellij/zellij/config.kdl` | 🔍 Verify theme reference |
+| **Nvim** | `colors/dreamcoder-{mode}.lua` | `GentlemanNvim/nvim/init.lua` colorscheme | ✅ Works (verify LazyVim compat) |
+| **Starship** | `starship-{mode}.toml` | `starship.toml` | ✅ Works via env var |
+| **Fish** | `conf.d/05-dreamcoder-theme.fish` | `GentlemanFish/fish/config.fish` | 🔍 Verify no conflicts |
+
+### Layer 2: ML4W Overlay
+
+| Component | dreamcoder hook | ML4W base | Integration Status |
+| ------------- | ------------------------------ | ---------------------------- | ------------------------- |
+| **Hyprland** | `hypr-colors-{mode}.conf` | `hypr/colors.conf` | 🔍 Verify variable naming |
+| **Waybar** | `waybar-{mode}.css` | `waybar/style.css` `@import` | 🔍 Verify CSS import |
+| **Rofi** | `rofi-{mode}.rasi` | `rofi/config.rasi` `@import` | 🔍 Verify rasi import |
+| **Dunst** | `dunst-dreamcoder-{mode}.conf` | `dunst/dunstrc` `[include]` | 🔍 Verify include works |
+| **Btop** | `btop-dreamcoder-{mode}.theme` | `btop/btop.conf` `theme =` | 🔍 Verify theme selection |
+| **GTK** | `colors.css` | `gtk-3.0/gtk.css` `@import` | 🔍 Verify path |
+| **Fastfetch** | — | Already has own config | ✅ No action needed |
+
+## Acceptance Criteria
+
+1. Install Gentleman.Dots → install ML4W → run `./scripts/dreamcoder install`
+2. All terminal colors show dreamcoder palette (dark mode = Ember Noir, light = Cocoa/Lúcuma)
+3. No "file not found" errors in any application log
+4. `DREAMCODER_THEME_MODE=light` switches ALL components to light simultaneously
+5. `DREAMCODER_THEME_MODE=dark` switches ALL components back to dark
+6. No duplicate color definitions (dreamcoder doesn't conflict with base theme)
+
+## Tasks
+
+### Task 1: Audit Gentleman.Dots Integration
+
+- Clone both repos fresh
+- Run dreamcoder's install hooks
+- Check EVERY app for correct color loading
+- Document any path/include mismatches
+
+### Task 2: Audit ML4W Integration
+
+- Same process for ML4W base
+- Pay special attention to hyprland variable naming (ML4W uses `$COLOR` vs dreamcoder's `$dcCOLOR`)
+
+### Task 3: Fix Path Mismatches
+
+- Update `renderers_*.py` to output correct paths for each base
+- Update `install-dreamcoder-hooks.sh` for any symlink/include path changes
+- Update `settings.py` ThemePaths if needed
+
+### Task 4: Add CI Check
+
+- Add GitHub workflow step that installs on a base config and verifies colors load
+- Create test that validates all hook files exist at expected paths
+
+## Risks
+
+- **ML4W updates**: ML4W changes file names between versions — need version detection or tolerance
+- **Gentleman.Dots updates**: Same risk. Consider pinning compatible versions in docs
+- **Order matters**: Install Gentleman → ML4W → dreamcoder. If order changes, hooks point to wrong base
diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md b/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md
new file mode 100644
index 0000000..46181b0
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md
@@ -0,0 +1,173 @@
+# SDD Plan 02: Shell Prompt Excellence — Best-in-Class Starship
+
+> **Goal:** Elevate the dreamcoder Starship prompt to be among the best in the world, learning from top references while keeping the dreamcoder visual identity and cross-shell consistency.
+> **References:** seifscape/macos-dot-files (AI session state in prompt), Powerlevel10k (instant prompt), devterm-kit (auto-setup), JaKooLit (multi-distro), alohays/dotfiles (agent-friendly)
+> **Target files:** `Shell/.config/starship.toml`, `starship-dark.toml`, `starship-light.toml`, `conf.d/20-dreamcoder-prompt.fish`, `.zshrc`, `.bashrc`
+> **Priority:** 🔴 CRITICAL — the prompt is what the user sees every single command
+> **Estimated diff:** ~400 lines across 6 files
+
+## Context
+
+The prompt is the most-interacted-with UI in any developer's workflow. dreamcoder-dots already has Starship with dark/light variants, but they can be elevated significantly by learning from the best in the world.
+
+### Current state
+
+- `starship.toml` — base config
+- `starship-dark.toml` — dark mode colors
+- `starship-light.toml` — light mode colors
+- `conf.d/20-dreamcoder-prompt.fish` — fish prompt integration
+- Works in bash/zsh/fish via `STARSHIP_CONFIG` env var
+
+### What the best prompts do in 2026
+
+| Feature | Who does it | dreamcoder has? |
+| ------------------------------------------------------- | ----------------- | --------------- |
+| **AI session state** (model, context, cost) | seifscape | ❌ |
+| **Git info** (branch, status, ahead/behind) | All | ✅ Basic |
+| **Language version** (node, python, rust, go) | All | ✅ Basic |
+| **Command duration** | JaKooLit | ❌ |
+| **Kubernetes context** | Starship default | ❌ |
+| **Terraform workspace** | Starship default | ❌ |
+| **Exit code indicator** (red ✗ on failure) | All the best ones | ❌ |
+| **Time** (24h in prompt) | Many | ✅ |
+| **Username + hostname** (conditional, show on SSH only) | Powerlevel10k | ❌ |
+| **Container/VM indicator** | alohays | ❌ |
+| **Battery** (on laptop) | ML4W | ❌ |
+| **Nix shell indicator** | Starship default | ❌ |
+
+## Design Principles (dreamcoder-specific)
+
+1. **Health first**: No information overload. Show what matters, hide the rest.
+2. **Visual hierarchy**: The most important info is most visually prominent.
+3. **Context-aware**: Show Kubernetes context only when in a k8s directory.
+4. **Speed**: Starship modules load lazily. No blocking calls.
+5. **Consistency**: Same prompt in fish, zsh, and bash.
+6. **Dark/Light**: Two complete color schemes adapted from tokens.json.
+
+## Prompt Architecture
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ $ctx $git_branch $git_status $lang $time ❯ │
+│ cyan accent subtle diag muted │
+│ │
+│ $cmd_duration $exit_code $battery │
+│ (line 2, only shown when relevant) │
+└──────────────────────────────────────────────────────────────┘
+```
+
+### Module Plan
+
+| Module | Condition | dark color | light color | Priority |
+| ---------------- | --------------------------- | ---------- | ----------- | ------------ |
+| **username** | Only on SSH | `#5f95ca` | `#0d4a68` | 🟢 Optional |
+| **hostname** | Only on SSH | `#d99555` | `#824f16` | 🟢 Optional |
+| **directory** | Always, truncated | `#e8dfd0` | `#17120d` | ✅ Required |
+| **git_branch** | In git repo | `#d99555` | `#824f16` | ✅ Required |
+| **git_status** | In git repo | `#b8a99a` | `#725e4c` | ✅ Required |
+| **package** | Has package.json/Cargo.toml | `#5f95ca` | `#0d4a68` | 🟡 Nice |
+| **node_version** | .nvmrc or .node-version | `#5f95ca` | `#0d4a68` | 🟡 Nice |
+| **python_venv** | Virtual env active | `#4db35f` | `#3d723d` | 🟡 Nice |
+| **rust_version** | Cargo project | `#c96a45` | `#a7471c` | 🟡 Nice |
+| **go_version** | Go project | `#5f95ca` | `#0d4a68` | 🟡 Nice |
+| **terraform** | Terraform dir | `#a87cb5` | `#57478b` | 🟢 Optional |
+| **kubernetes** | kube context | `#5f95ca` | `#0d4a68` | 🟢 Optional |
+| **cmd_duration** | > 2 seconds | `#b8a99a` | `#725e4c` | ✅ Required |
+| **exit_code** | Non-zero | `#ed8a7a` | `#842f24` | ✅ Required |
+| **time** | Always (24h) | `#938274` | `#554638` | 🟡 Subdued |
+| **line_break** | Always | — | — | ✅ Required |
+| **battery** | Laptop, < 20% | `#ed8a7a` | `#842f24` | 🟢 Optional |
+| **shell** | Always | `#d99555` | `#824f16` | 🟡 Character |
+| **ai_session** | Claude/Codex active | `#5f95ca` | `#0d4a68` | 🔬 **New** |
+
+### AI Session State Module (NEW — cutting edge)
+
+This is what makes dreamcoder STAND OUT. Nobody in the dotfiles world has a prompt that shows AI session state... except seifscape who just started doing it in 2026.
+
+```toml
+[custom.ai_session]
+command = "~/.config/dreamcoder/scripts/ai-session-status.sh"
+when = """test -f ~/.config/dreamcoder/ai-session.env"""
+format = "[⎔ $output]($style)"
+style = "bold cyan"
+```
+
+The script would read:
+
+- `CLAUDE_SESSION_ID` or `OPENCODE_SESSION_ID`
+- Token count / context usage
+- Cost estimate
+
+This is bleeding edge. Nobody else ships this as a dotfile feature.
+
+## Acceptance Criteria
+
+1. Starship prompt renders in fish, zsh, and bash identically (except shell-specific features)
+2. Dark mode: warm dark background (`#100f0d`), warm gold accents (`#d99555`)
+3. Light mode: paper background (`#f3eadc`), brown accents (`#824f16`)
+4. Git info shows branch name + dirty/clean status
+5. Language version shows for node/python/rust/go when in relevant project
+6. Exit code turns red on command failure
+7. Command duration shows for slow commands (> 2s)
+8. AI session state shows when Claude/OpenCode is active
+9. All module colors come from dreamcoder `tokens.json`
+10. Battery shows on laptop when below 20%
+11. Prompt renders in < 50ms (Starship lazy loading)
+
+## Tasks
+
+### Task 1: Starship TOML Architecture
+
+- Redesign `starship.toml` as the single source of truth
+- `starship-dark.toml` → only color overrides for dark mode
+- `starship-light.toml` → only color overrides for light mode
+- Enable all desired modules with proper `when` conditions
+
+### Task 2: Core Modules
+
+- directory, git_branch, git_status, package, node_version, python_venv
+- rust_version, go_version, cmd_duration, exit_code, time, line_break
+
+### Task 3: Advanced Modules
+
+- username/hostname (conditional on SSH)
+- kubernetes context, terraform workspace
+- battery indicator
+- shell character
+
+### Task 4: AI Session State (bleeding edge)
+
+- Create `scripts/ai-session-status.sh` — reads Claude/OpenCode session state
+- Create `conf.d/25-dreamcoder-ai-env.fish` — sets env vars for AI state
+- Update `.zshrc` and `.bashrc` for AI state in zsh/bash
+- Add custom Starship module for AI session
+
+### Task 5: Shell Integration Cleanup
+
+- Ensure `STARSHIP_CONFIG` switch works on dark/light toggle in ALL shells
+- `conf.d/20-dreamcoder-prompt.fish` — clean up, add path detection
+- `.zshrc` — verify p10k compatibility (if user uses p10k instead)
+- `.bashrc` — verify starship works in bash too
+
+### Task 6: Verification
+
+- Visual diff in dark and light modes
+- Time prompt rendering speed
+- Test in fish, zsh, bash
+- Test on SSH session (username/hostname shows)
+- Test AI session module
+
+## Risks
+
+- **Starship version**: New modules may require latest Starship — pin minimum version
+- **AI session state**: Claude/OpenCode don't expose session state as files yet — may need polling or env vars
+- **Shell differences**: Nushell prompt config is completely different from Fish/Zsh — document limitation
+- **Color consistency**: Each module must use the EXACT color from tokens.json — use the token names, not eyeballed colors
+
+## References
+
+- Current configs: `starship.toml`, `starship-dark.toml`, `starship-light.toml`
+- Current integration: `conf.d/20-dreamcoder-prompt.fish`
+- seifscape/macos-dot-files: AI session in prompt (reference only)
+- Starship docs:
+- Tokens: `themes/dreamcoder/tokens.json` (accent, diagnostic, error colors)
diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md b/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md
new file mode 100644
index 0000000..5d12800
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md
@@ -0,0 +1,175 @@
+# SDD Plan 03: Dreamcoder Personalization Suite
+
+> **Goal:** Curate a set of opinionated personalizations that make dreamcoder-dots uniquely productive, beyond what Gentleman.Dots and ML4W provide. Think "what would a senior architect with 15 years of experience want in their dotfiles that isn't in any framework?"
+> **Target:** Aliases, functions, keybindings, scripts, and workflow enhancements unique to dreamcoder
+> **Priority:** 🟡 HIGH — this is the "secret sauce" that makes dreamcoder YOURS
+> **Estimated diff:** ~500 lines across 20+ files
+
+## Philosophy
+
+Gentleman.Dots gives you a Ferrari. ML4W gives you a workshop. dreamcoder gives you the **custom interior** — the ergonomic touches, the shortcuts that save you 5 seconds 50 times a day, the things that make the environment feel like YOURS.
+
+## Scope
+
+### Fish Shell Personalizations
+
+**Existing (improve):**
+
+| File | What it does | Improvement |
+| ------------------------------------- | --------------------------- | ----------------------------------------- |
+| `conf.d/10-dreamcoder-keyboard.fish` | Vi mode, keybindings | Add `jk` → ESC, better vi-mode indicators |
+| `conf.d/15-dreamcoder-shortcuts.fish` | Custom shortcuts | Expand with modern CLI aliases |
+| `conf.d/16-dreamcoder-icons.fish` | LS_COLORS, icons | Use dreamcoder palette, add eza colors |
+| `conf.d/20-dreamcoder-prompt.fish` | Prompt config | Clean up, add AI session env |
+| `conf.d/30-dreamcoder-fastfetch.fish` | Fastfetch on terminal start | Add timing, conditional (not on SSH) |
+
+**Functions (NEW):**
+
+| Function | What it does | Reference |
+| ----------------- | ---------------------------------------------- | --------------------------- |
+| `mkcd.fish` | `mkdir -p + cd` | Common, surprisingly absent |
+| `extract.fish` | Extract ANY archive type | 7z, tar.gz, zip, rar, etc. |
+| `cheat.fish` | Quick cheat sheet (tldr wrapper) | Better than `man` |
+| `dots.fish` | `cd` to dreamcoder-dots repo | Quick access |
+| `sysupdate.fish` | Update everything (pacman, AUR, flatpak, brew) | ML4W-like |
+| `ports.fish` | Show listening ports | Developer essential |
+| `killport.fish` | Kill process on a port | Developer essential |
+| `http.fish` | HTTP request with syntax highlighting | httpie wrapper |
+| `logs.fish` | Tail journalctl with filters | Developer essential |
+| `mcfly.fish` | Better Ctrl+R for history | Modern `fzf` history |
+| `tm-session.fish` | Quick tmux session picker | fzf-based |
+
+**Aliases (NEW):**
+
+```
+alias g=git # exists
+alias gs='git status' # exists
+alias gp='git push' # exists
+alias gl='git log --oneline --graph' # exists
+alias ll='eza -la --icons' # improve with eza
+alias la='eza -a --icons'
+alias lt='eza -T --icons' # tree view
+alias cat='bat' # exists
+alias find='fd' # new
+alias grep='rg' # improve
+alias ps='procs' # modern ps
+alias top='btm' # bottom (modern htop)
+alias du='dua' # modern du
+alias ping='gping' # graphical ping
+alias df='duf' # modern df
+alias sed='sd' # modern sed
+alias help='tldr' # modern man
+alias cd='z' # zoxide
+alias cdi='zi' # zoxide interactive
+```
+
+### Zsh Equivalent
+
+- Same aliases in `.zshrc`
+- Same functions in a `functions/` directory sourced by `.zshrc`
+- Powerlevel10k or Starship prompt (user choice)
+
+### Bash Equivalent
+
+- Same aliases in `.bashrc`
+- Same functions
+
+### Tmux Personalizations
+
+| Feature | Current | Improvement |
+| --------------- | ------------ | --------------------------------------- |
+| Prefix | `C-a` | ✅ Good, keep |
+| Split | `\|` and `-` | Change to `v` and `d` (Gentleman-style) |
+| Navigation | `Alt+arrows` | Add `vim-tmux-navigator` integration |
+| Session picker | None | Add fzf-based `C-f` session picker |
+| Floating window | None | Add `Alt+g` for scratch terminal |
+| Resurrect | None | Add tmux-resurrect auto-save |
+
+### Git Config Personalizations
+
+| Feature | What |
+| -------------------- | ---------------------------------------------------- |
+| `delta` as diff tool | Already exists (`delta-dreamcoder-{mode}.gitconfig`) |
+| `git lg` alias | Pretty log with graph |
+| `git undo` | Soft reset to previous commit |
+| `git cleanup` | Delete merged branches |
+| `git conflicts` | List conflicted files |
+| `git root` | Show repo root path |
+| `includeIf` | Conditional git config per directory |
+
+### Script Improvements
+
+| Script | What to add |
+| ------------------ | ------------------------------------------------------------------- |
+| `doctor.sh` | Check ALL theme hooks are properly installed, not just colors |
+| `repair.sh` | Re-stow ALL components, not just hooks |
+| `status.sh` | Show full system status: theme mode, installed components, versions |
+| `dreamcoder.sh` | Add `dreamcoder ai-status` subcommand |
+| `set-wallpaper.sh` | Add wallpaper rotation support |
+
+## Acceptance Criteria
+
+1. All aliases work in fish, zsh, and bash
+2. Every function has `--help` or at minimum a comment explaining usage
+3. No alias conflicts with existing commands (check with `type`)
+4. `eza`, `bat`, `fd`, `rg`, `zoxide` are detected gracefully (no errors if missing)
+5. Tmux has vim-tmux-navigator support
+6. Git delta uses dreamcoder dark/light colors matching theme mode
+7. `dreamcoder doctor` shows green for every expected component
+
+## Tasks
+
+### Task 1: Fish Enhancements
+
+- Create `conf.d/12-dreamcoder-keybindings.fish` (jk→ESC, improved vi-mode)
+- Create `functions/mkcd.fish`, `extract.fish`, `cheat.fish`, `dots.fish`
+- Create `functions/sysupdate.fish`, `ports.fish`, `killport.fish`
+- Create `functions/http.fish`, `logs.fish`, `tm-session.fish`
+- Update `conf.d/15-dreamcoder-shortcuts.fish` with modern CLI aliases
+- Update `conf.d/16-dreamcoder-icons.fish` with dreamcoder eza/ls colors
+
+### Task 2: Zsh Enhancements
+
+- Same aliases in `.zshrc`
+- Create `Shell/.config/zsh/functions/` directory
+- Source functions from `.zshrc`
+- Ensure p10k users can use dreamcoder colors
+
+### Task 3: Bash Enhancements
+
+- Same aliases in `.bashrc`
+- Source dreamcoder functions
+
+### Task 4: Tmux Improvements
+
+- Add `vim-tmux-navigator` keybindings
+- Add fzf-based session picker (`C-f`)
+- Add scratch terminal popup (`Alt-g`)
+- Add tmux-resurrect auto-save on `prefix + Ctrl-s`
+
+### Task 5: Script Polish
+
+- Improve `doctor.sh` — check every component, not just colors
+- Improve `repair.sh` — reinstall hooks with confirmation
+- Improve `status.sh` — show theme mode, component versions, last wallaper change
+- Add `dreamcoder ai-status` subcommand
+
+### Task 6: Verification
+
+- Test every alias in every shell
+- Test every function with expected args and edge cases
+- `dreamcoder doctor` passes
+- Tmux navigation works with vim-tmux-navigator
+
+## Risks
+
+- **Shell-specific syntax**: Fish uses `function` keyword, Zsh uses `function` or `name()`, Bash uses `name()`. Each function needs 3 implementations.
+- **Modern CLI dependencies**: `eza`, `bat`, `fd`, `rg`, `procs`, `btm`, `dua`, `gping`, `duf`, `sd`, `tldr` — not everyone has these installed. All aliases must gracefully degrade.
+- **Tmux plugin dependency**: vim-tmux-navigator and tmux-resurrect require TPM — must be installed first.
+
+## References
+
+- Existing shell configs: `Shell/.config/fish/*.fish`, `Shell/.bashrc`, `Shell/.zshrc`
+- Existing Tmux config: `Tmux/.tmux.conf`
+- Existing scripts: `scripts/doctor.sh`, `repair.sh`, `status.sh`, `dreamcoder.sh`
+- Modern CLI alternatives: `eza`, `bat`, `fd`, `rg`, `procs`, `btm`, `dua`, `gping`, `duf`, `sd`, `tldr`, `zoxide`
diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md b/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md
new file mode 100644
index 0000000..dd8c37c
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md
@@ -0,0 +1,129 @@
+# SDD Plan 04: Documentation & Developer Experience
+
+> **Goal:** Create crystal-clear documentation for the 3-step install flow (Gentleman.Dots → ML4W → dreamcoder-dots), plus a comparison page showing what dreamcoder adds vs the competition.
+> **Target:** README.md overhaul, INSTALL.md creation, COMPARISON.md creation
+> **Priority:** 🟡 HIGH — without docs, nobody knows how to use dreamcoder
+> **Estimated diff:** ~500 lines across 5 files
+
+## Context
+
+The biggest problem with dreamcoder-dots currently is that **nuevo usuario no sabe cómo instalarlo ni qué hace**. La documentación actual asume que el usuario sabe que necesita Gentleman.Dots + ML4W primero.
+
+## Scope
+
+### README.md Overhaul
+
+Current: Technical description of theme pipeline, focused on developers contributing.
+Target: **User-centric** — "How to get Dreamcoder running in 10 minutes."
+
+Sections:
+
+```markdown
+# Dreamcoder OS
+
+> La capa visual premium para Gentleman.Dots + ML4W.
+> Café/Lúcuma. Ember Noir. Contraste saludable. Identidad.
+
+## Quick Start (3-Step Install)
+
+### 1. Install Gentleman.Dots
+
+`brew install gentleman-dots` or download from GitHub
+→ Provides: Neovim (29 plugins), Ghostty shaders, Tmux/Zellij, Vim Trainer, Fish/Zsh/Nushell
+
+### 2. Install ML4W OS
+
+`bash <(curl -s https://ml4w.com/os/stable)`
+→ Provides: Hyprland config, Waybar, Rofi, Dunst, animations, keybindings
+
+### 3. Install Dreamcoder
+
+`git clone ... && ./scripts/dreamcoder install`
+→ **Applies**: dreamcoder dark/light/dusk color system across all components
+→ **Adds**: custom shell aliases, AI session prompt, smart functions, auto-theme-switching
+```
+
+### New Files
+
+| File | Purpose |
+| ------------------------ | ---------------------------------------------------------------- |
+| `INSTALL.md` | Detailed install guide with screenshots |
+| `COMPARISON.md` | What dreamcoder adds vs Gentleman alone vs ML4W alone |
+| `docs/ai-integration.md` | How dreamcoder integrates with Claude/OpenCode/Pi |
+| `docs/theme-tokens.md` | Reference for tokens.json (schema, guardrails, color philosophy) |
+
+### COMPARISON.md Content
+
+| Feature | Gentleman.Dots | ML4W | dreamcoder-dots |
+| -------------------- | ------------------- | ---------------- | --------------------------------- |
+| Hyprland config | ❌ | ✅ Complete | 🔶 Color overlay only |
+| Neovim plugins | ✅ 29 plugins | ❌ | 🔶 Dreamcoder colorscheme |
+| Shell configs | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + functions overlay |
+| Theme engine | ❌ Uses catppuccin | ✅ Matugen-based | ✅ **Token-based with WCAG/APCA** |
+| Light/Dark switching | ❌ | ✅ | ✅ **+ Dusk transition mode** |
+| AI integration | ✅ Neovim plugins | ❌ | ✅ **+ AI session in prompt** |
+| Ghostty shaders | ✅ 45+ GLSL | ❌ | 🔶 Uses Gentleman's shaders |
+| Installer | ✅ Go TUI | ✅ bash script | ✅ **Go TUI with Vim Trainer** |
+| Prompt | ❌ Basic | ❌ Basic | ✅ **Starship with AI state** |
+| Accessibility | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA guards** |
+
+## Acceptance Criteria
+
+1. New user can follow INSTALL.md from zero to dreamcoder desktop in under 30 minutes
+2. COMPARISON.md answers "why should I use dreamcoder?" in one glance
+3. README.md has the 3-step install flow prominently at the top
+4. All docs link to the actual source repos for Gentleman.Dots and ML4W
+5. ai-integration.md explains how dreamcoder's Pi theme works with the user's Pi agent
+
+## Tasks
+
+### Task 1: README.md Rewrite
+
+- Rewrite with 3-step install flow as hero section
+- Add badges: "Works with Gentleman.Dots" + "Works with ML4W" + "WCAG/APCA Certified"
+- Add screenshot showcase (dark mode, light mode, command-line)
+- Add philosophy section (health > flashiness)
+- Keep tech stack and architecture sections but move lower
+
+### Task 2: INSTALL.md
+
+- Prerequisites (Gentleman.Dots, ML4W, Arch Linux)
+- Step-by-step with screenshots
+- Post-install verification checklist
+- Troubleshooting section
+
+### Task 3: COMPARISON.md
+
+- Feature comparison table
+- "What stays from Gentleman" section
+- "What stays from ML4W" section
+- "What dreamcoder adds" section
+- Screenshot comparison (same app, different theme)
+
+### Task 4: AI Integration Docs
+
+- How dreamcoder integrates with Pi agent themes
+- How dreamcoder's tokens interact with opencode themes
+- How AI session prompt module works
+
+### Task 5: Theme Tokens Reference
+
+- Visual reference for every token in tokens.json
+- Explanation of WCAG 4.5:1 and APCA minimums
+- How to add a new token
+- Color ramp visualization (hex values displayed as color swatches)
+
+## Risks
+
+- **Link rot**: Links to Gentleman.Dots and ML4W repos must stay current
+- **Version drift**: Screenshots become outdated as themes evolve — use generated previews instead
+- **Translation**: Spanish README would be nice-to-have but out of scope for now
+
+## References
+
+- Current README: `README.md`
+- Existing docs: `docs/README.md`, `docs/installation/macos.md`, `docs/installation/linux.md`
+- Gentleman.Dots README: (bilingual EN+ES) — reference for format
+- ML4W README: (professional, well-structured) — reference for professional tone
+- Theme tokens: `themes/dreamcoder/tokens.json`
+- Theme preview: `docs/dreamcoder-theme-preview.md`
diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md b/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md
new file mode 100644
index 0000000..1da1217
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md
@@ -0,0 +1,309 @@
+# SDD Plan 05: Release Automation & Engineering Excellence
+
+> **Goal:** Automate EVERYTHING — tag → build → test → release → publish. No manual steps from commit to distribution.
+> **Reference:** GoReleaser, PyPI trusted publishing, Dependabot, golangci-lint, commitlint
+> **Target:** `.github/workflows/`, `installer/.goreleaser.yaml`, `.github/dependabot.yml`, `.github/CODEOWNERS`
+> **Priority:** 🔴 HIGH — multiplies productivity
+> **Estimated diff:** ~600 lines across 15+ files
+
+## Philosophy
+
+Un release no debería requerir más que un `git tag v2.1.0 && git push --tags`. Todo lo demás — compilar para 4 plataformas, publicar en PyPI, crear el GitHub Release, actualizar la Homebrew formula, generar el CHANGELOG — debería ser automático.
+
+## Current State vs Target
+
+| Feature | Hoy | Target |
+| ------------------------ | -------------------------- | ---------------------------------------- |
+| **Go builds** | `make build-all` manual | GoReleaser automático en CI |
+| **Go tests** | `go test` manual | ✅ En cada push/PR |
+| **Go lint** | ❌ No existe | golangci-lint en CI |
+| **Python publish** | ✅ CI on tags | ✅ Ya funciona (trusted publishing) |
+| **Python tests** | ✅ CI | ✅ Ya funciona |
+| **Release creation** | `gh release create` manual | GoReleaser + GitHub Release |
+| **Homebrew formula** | `sha256` manual | GoReleaser actualiza automático |
+| **Dependency updates** | ❌ Manual | Dependabot semanal |
+| **Changelog** | Manual | Auto-generado desde conventional commits |
+| **Issue/PR templates** | ❌ No existe | Templates en `.github/` |
+| **Conventional commits** | ❌ No enforce | commitlint en CI |
+| **CODEOWNERS** | ❌ No existe | Para review routing |
+| **Coverage reporting** | ❌ Solo local | Codecov / coveralls |
+
+## Scope
+
+### In Scope
+
+**Release Pipeline (Go installer):**
+
+- GoReleaser config (`.goreleaser.yaml`) para build cross-platform
+- GitHub Actions release workflow: tag → build → test → release
+- Auto-publish Homebrew formula con SHA256 actualizados
+- Builds: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64
+
+**Quality Gates (CI):**
+
+- Go build + test en cada push/PR
+- golangci-lint en CI
+- commitlint (conventional commits) en CI
+- Code coverage upload to codecov.io
+
+**Automation:**
+
+- Dependabot for Go and Python dependencies
+- Release Please for CHANGELOG auto-generation
+- `.github/CODEOWNERS`
+
+**Community:**
+
+- Issue templates (bug report + feature request)
+- PR template
+
+### Out of Scope
+
+- Semantic release for Python (PyPI publish already works)
+- SBOM generation (future concern)
+- Performance benchmarks (Plan 06 candidate)
+- Docker E2E (separate concern)
+
+## Architecture
+
+### Trigger Flow
+
+```
+git tag v2.1.0
+ │
+ ▼
+GitHub Actions: Release Workflow
+ ├── GoReleaser
+ │ ├── build linux/amd64
+ │ ├── build linux/arm64
+ │ ├── build darwin/amd64
+ │ ├── build darwin/arm64
+ │ ├── create GitHub Release
+ │ └── publish Homebrew formula
+ ├── Python publish (to PyPI via trusted publishing)
+ │
+ └── (already exists) ✅
+```
+
+### CI Flow (every push/PR)
+
+```
+Push/PR to main
+ │
+ ▼
+GitHub Actions: CI Workflow
+ ├── Python (3.11, 3.12)
+ │ ├── ruff lint + format
+ │ ├── mypy type check
+ │ ├── pytest + coverage
+ │ ├── shellcheck
+ │ └── theme health check
+ ├── Go
+ │ ├── go build
+ │ ├── go test ./...
+ │ └── golangci-lint
+ ├── commitlint
+ │ └── conventional commits check
+ └── Upload coverage to codecov
+```
+
+## Files to Create/Modify
+
+### New Files
+
+| File | Purpose |
+| -------------------------------------------- | --------------------------------- |
+| `.github/workflows/ci-go.yml` | Go build + test + lint on push/PR |
+| `.github/workflows/release.yml` | GoReleaser + PyPI publish on tag |
+| `.goreleaser.yaml` | GoReleaser configuration |
+| `.github/dependabot.yml` | Auto-dependency updates |
+| `.github/CODEOWNERS` | Code review routing |
+| `.github/ISSUE_TEMPLATE/bug_report.yml` | Bug report form |
+| `.github/ISSUE_TEMPLATE/feature_request.yml` | Feature request form |
+| `.github/PULL_REQUEST_TEMPLATE.md` | PR template |
+| `.github/workflows/commitlint.yml` | Conventional commits check |
+| `commitlint.config.js` | Commitlint rules |
+
+### Modified Files
+
+| File | Change |
+| ---------------------------------------- | -------------------------------------- |
+| `.github/workflows/theme-validation.yml` | Add Go matrix, add codecov upload |
+| `.pre-commit-config.yaml` | Add commitlint hook |
+| `Makefile` | Simplify (GoReleaser handles builds) |
+| `installer/go.mod` | May need updates for GoReleaser compat |
+
+## Acceptance Criteria
+
+1. `git tag v2.1.0 && git push --tags` → CI builds for 4 platforms, creates GitHub Release, publishes PyPI
+2. Every push/PR → Go build + test passes
+3. `golangci-lint` passes with zero issues
+4. `commitlint` enforces conventional commits on every PR
+5. Dependabot opens weekly PRs for outdated deps
+6. Issue templates appear when creating new issues
+7. PR template appears when creating new PRs
+8. Coverage is uploaded to codecov.io with badge in README
+
+## Tasks
+
+### Task 1: GoReleaser Configuration
+
+- Create `.goreleaser.yaml` with:
+ - 4 builds (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64)
+ - Version injected via ldflags: `-X github.com/dreamcoder08/dreamcoder-dots/installer/pkg/version.Version={{ .Version }}`
+ - Homebrew tap publish: `brews:` section pointing to `homebrew-tap/Formula/dreamcoder-dots.rb`
+ - Archive config (tar.gz for linux, zip for darwin)
+ - Release notes auto-generated from git log
+
+### Task 2: Release Workflow
+
+- Create `.github/workflows/release.yml`:
+
+ ```yaml
+ name: Release
+ on:
+ push:
+ tags: ['v*']
+ jobs:
+ goreleaser:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-go@v5
+ with:
+ go-version: stable
+ - uses: goreleaser/goreleaser-action@v6
+ with:
+ version: '~> v2'
+ args: release --clean
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ publish-pypi:
+ needs: [goreleaser]
+ # Uses existing PyPI publish job
+ ```
+
+### Task 3: Go CI (every push/PR)
+
+- Create `.github/workflows/ci-go.yml`:
+
+ ```yaml
+ name: CI (Go)
+ on: [push, pull_request]
+ jobs:
+ go:
+ strategy:
+ matrix:
+ go-version: ['1.26', '1.27']
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ - run: go build ./...
+ - run: go test ./...
+ - uses: golangci/golangci-lint-action@v6
+ ```
+
+### Task 4: Quality Gates
+
+- Create `commitlint.config.js`:
+
+ ```js
+ module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'type-enum': [
+ 2,
+ 'always',
+ [
+ 'feat',
+ 'fix',
+ 'docs',
+ 'style',
+ 'refactor',
+ 'perf',
+ 'test',
+ 'chore',
+ 'ci',
+ 'build',
+ ],
+ ],
+ },
+ }
+ ```
+
+- Create `.github/workflows/commitlint.yml`
+- Update `.pre-commit-config.yaml` to add commitlint hook
+
+### Task 5: Dependabot
+
+- Create `.github/dependabot.yml`:
+
+ ```yaml
+ version: 2
+ updates:
+ - package-ecosystem: 'gomod'
+ directory: '/installer'
+ schedule:
+ interval: 'weekly'
+ - package-ecosystem: 'pip'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
+ - package-ecosystem: 'github-actions'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
+ ```
+
+### Task 6: CODEOWNERS
+
+- Create `.github/CODEOWNERS`:
+
+ ```
+ * @Dreamcoder08
+ src/dreamcoder_theme/ @Dreamcoder08
+ installer/ @Dreamcoder08
+ .github/ @Dreamcoder08
+ ```
+
+### Task 7: Community Templates
+
+- Create `.github/ISSUE_TEMPLATE/bug_report.yml`
+- Create `.github/ISSUE_TEMPLATE/feature_request.yml`
+- Create `.github/PULL_REQUEST_TEMPLATE.md`
+- Add config.yml for issue forms
+
+### Task 8: Coverage Reporting
+
+- Add `codecov-action` to CI workflow
+- Add coverage badge to README.md
+- Configure `codecov.yml` with threshold
+
+### Task 9: Merge Into Existing CI
+
+- Update `.github/workflows/theme-validation.yml` (rename to `ci-python.yml`)
+- Add Go matrix to existing workflow or keep separate
+- Ensure all workflows run in parallel
+- Add `codecov` upload step
+
+## Risks
+
+- **GoReleaser version**: GoReleaser v2 has breaking changes from v1 — pin version in action
+- **Homebrew formula update**: GoReleaser's `brews` config needs the formula file to exist with template markers
+- **commitlint**: Requires Node.js in CI — adds ~30s to workflow
+- **Secret management**: PyPI trusted publishing requires OIDC — already configured ✅
+- **Go version matrix**: `go 1.26.4` in `go.mod` — verify CI runners support it
+
+## References
+
+- GoReleaser docs:
+- GoReleaser GitHub Action:
+- PyPI trusted publishing:
+- Dependabot config:
+- Commitlint:
+- Current CI: `.github/workflows/theme-validation.yml`
+- Current Makefile: `installer/Makefile`
+- Current pyproject.toml: `pyproject.toml`
+- Current Homebrew formula: `homebrew-tap/Formula/dreamcoder-dots.rb`
diff --git a/installer/e2e/Dockerfile.ubuntu b/installer/e2e/Dockerfile.ubuntu
new file mode 100644
index 0000000..6066251
--- /dev/null
+++ b/installer/e2e/Dockerfile.ubuntu
@@ -0,0 +1,17 @@
+FROM ubuntu:24.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ git \
+ golang-go \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+COPY . .
+
+RUN go build -o dreamcoder-dots .
+RUN ./dreamcoder-dots --version
+RUN ./dreamcoder-dots doctor
+RUN ./dreamcoder-dots install --dry-run
+
+CMD ["echo", "All tests passed"]
diff --git a/installer/e2e/README.md b/installer/e2e/README.md
new file mode 100644
index 0000000..1846a9d
--- /dev/null
+++ b/installer/e2e/README.md
@@ -0,0 +1,45 @@
+# Docker E2E Testing for dreamcoder-dots installer
+
+Tests the Go TUI installer across multiple Linux distributions
+using Docker containers.
+
+## Quick Start
+
+```bash
+cd installer/e2e
+bash docker-test.sh
+```
+
+This builds and tests the installer on:
+
+- Ubuntu 24.04
+- Debian 12
+- Fedora 40
+- Alpine 3.20
+
+## Test Scenarios
+
+Each container runs:
+
+1. `go build` — compile the installer
+2. `./dreamcoder-dots --version` — binary runs
+3. `./dreamcoder-dots doctor` — platform detection works
+4. `./dreamcoder-dots install --dry-run` — install logic works
+
+## Adding a distro
+
+```bash
+cp Dockerfile.ubuntu Dockerfile.
+# Edit the FROM line and package manager
+# Add to docker-test.sh
+```
+
+## Prerequisites
+
+- Docker Engine 24+
+- Bash
+
+## CI Integration
+
+This is designed to run as a GitHub Actions matrix job.
+See `.github/workflows/ci-go.yml` for the current Go test setup.
diff --git a/installer/e2e/docker-test.sh b/installer/e2e/docker-test.sh
new file mode 100644
index 0000000..ce2db84
--- /dev/null
+++ b/installer/e2e/docker-test.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Docker E2E test runner for dreamcoder-dots installer
+# Usage: bash docker-test.sh [distro]
+# distro: ubuntu (default), debian, fedora, alpine, all
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+E2E_DIR="${ROOT}/installer/e2e"
+DISTRO="${1:-ubuntu}"
+
+run_test() {
+ local distro="$1"
+ local dockerfile="${E2E_DIR}/Dockerfile.${distro}"
+
+ if [[ ! -f "${dockerfile}" ]]; then
+ echo "✗ No Dockerfile for '${distro}' at ${dockerfile}"
+ return 1
+ fi
+
+ echo ":: Testing ${distro}..."
+ docker build -t "dreamcoder-e2e-${distro}" \
+ -f "${dockerfile}" \
+ "${ROOT}/installer" 2>&1 | tail -3
+
+ echo "✓ ${distro} passed"
+}
+
+case "${DISTRO}" in
+ all)
+ for d in ubuntu debian fedora alpine; do
+ run_test "${d}"
+ done
+ ;;
+ *)
+ run_test "${DISTRO}"
+ ;;
+esac
+
+echo ""
+echo "=== All tests passed ==="
diff --git a/installer/installer b/installer/installer
index 2424107..af3e765 100755
Binary files a/installer/installer and b/installer/installer differ
diff --git a/pyproject.toml b/pyproject.toml
index 2145505..96b65f1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,7 +28,7 @@ dependencies = [
"jinja2>=3.0",
]
[project.optional-dependencies]
-dev = ["pytest>=7", "pytest-cov>=4"]
+dev = ["pytest>=7", "pytest-cov>=4", "ruff>=0.7", "mypy>=1.15"]
[project.urls]
Homepage = "https://github.com/Gentleman-Programming/dreamcoder-dots"
@@ -55,6 +55,15 @@ line-length = 100
select = ["E", "F", "I", "N", "W", "UP", "YTT", "ASYNC", "SIM", "PL", "RUF"]
ignore = ["E501"]
+[tool.ruff.lint.per-file-ignores]
+"scripts/verify-theme-health.py" = ["PLR2004", "SIM108", "PLR0912", "PLR0915", "PLC0415"]
+"scripts/generate-theme-preview.py" = ["PLR2004", "SIM108"]
+"tests/test_*.py" = ["E402", "PLR2004"]
+"src/dreamcoder_theme/palette.py" = ["PLR0912", "PLR2004", "SIM102"]
+"src/dreamcoder_theme/renderers_starship.py" = ["RUF001"]
+"src/dreamcoder_theme/sync.py" = ["PLC0415"]
+"src/dreamcoder_theme/renderers_extra_nvim_lsp.py" = ["PLR0913"]
+
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
diff --git a/scripts/apply-theme-mode.sh b/scripts/apply-theme-mode.sh
index 47d7f59..0d04ba3 100755
--- a/scripts/apply-theme-mode.sh
+++ b/scripts/apply-theme-mode.sh
@@ -7,120 +7,130 @@ ENV_FILE="${DREAMCODER_DOTS_ENV:-${0%/*}/dreamcoder-env.sh}"
MODE="${1:-light}"
WALLPAPER="${2:-${DREAMCODER_WALLPAPER:-}}"
ML4W_WALLPAPER="${ML4W_CACHE_DIR}/current_wallpaper"
-[[ "${MODE}" == "light" || "${MODE}" == "dark" ]] || { printf 'Invalid mode: %s\n' "${MODE}" >&2; exit 1; }
+[[ "${MODE}" == "light" || "${MODE}" == "dark" ]] || {
+ printf 'Invalid mode: %s\n' "${MODE}" >&2
+ exit 1
+}
if [[ -z "${WALLPAPER}" && -f "${ML4W_WALLPAPER}" ]]; then WALLPAPER="$(cat "${ML4W_WALLPAPER}")"; fi
CURSOR_CLI_ENV="${CACHE_HOME:-${HOME}/.cache}/dreamcoder/cursor-cli.env"
case "${MODE}" in
- light) CLI_COLORFGBG="0;15" ;;
- dark) CLI_COLORFGBG="15;0" ;;
+light) CLI_COLORFGBG="0;15" ;;
+dark) CLI_COLORFGBG="15;0" ;;
esac
mkdir -p "$(dirname "${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}" ]] && command -v matugen >/dev/null; then
- matugen image "${WALLPAPER}" -m "${MODE}" >/dev/null 2>&1 || true
+ matugen image "${WALLPAPER}" -m "${MODE}" >/dev/null 2>&1 || true
fi
# --- Waybar: flip colors.css symlink to mode-specific variant BEFORE sync ---
# This ensures the Python sync writes to the correct variant file
# 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-${MODE}.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-${MODE}.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-${MODE}.lua" "${HYPR_LUA}"
+ ln -sf "colors-${MODE}.lua" "${HYPR_LUA}"
fi
if [[ -L "${HYPR_CONF}" ]]; then
- ln -sf "colors-${MODE}.conf" "${HYPR_CONF}"
+ ln -sf "colors-${MODE}.conf" "${HYPR_CONF}"
fi
DREAMCODER_THEME_MODE="${MODE}" DREAMCODER_WALLPAPER="${WALLPAPER}" \
- PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" \
- "${DREAMCODER_DOTS_DIR}/scripts/sync-dreamcoder-theme.py"
+ PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" \
+ "${DREAMCODER_DOTS_DIR}/scripts/sync-dreamcoder-theme.py"
command -v pkill >/dev/null && pkill -SIGUSR1 kitty 2>/dev/null || true
# Restart Waybar so it picks up the new colors.css immediately
if command -v pkill >/dev/null; then
- pkill waybar 2>/dev/null || true
- sleep 0.3
- "${HOME}/.config/waybar/launch.sh" 2>/dev/null || true
+ pkill waybar 2>/dev/null || true
+ sleep 0.3
+ "${HOME}/.config/waybar/launch.sh" 2>/dev/null || true
fi
# --- tmux integration: propagate theme to running sessions ---
KANAGAWA_DIR="${HOME}/.tmux/plugins/tmux-kanagawa"
if command -v tmux >/dev/null 2>&1; then
- # Start a headless server if none exists so options persist for new sessions
- if ! tmux list-sessions &>/dev/null 2>&1; then
- tmux start-server 2>/dev/null || true
- sleep 0.1
- 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 COLORFGBG "${CLI_COLORFGBG}" 2>/dev/null || true
+ # Start a headless server if none exists so options persist for new sessions
+ if ! tmux list-sessions &>/dev/null 2>&1; then
+ tmux start-server 2>/dev/null || true
+ sleep 0.1
+ 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 COLORFGBG "${CLI_COLORFGBG}" 2>/dev/null || true
- # Switch tmux-kanagawa theme variant to match Dreamcoder mode
- if [[ -d "${KANAGAWA_DIR}" ]]; then
- case "${MODE}" in
- light)
- KANAGAWA_VARIANT="lotus"
- # Override default lotus colors with Dreamcoder Light palette
- tmux set-option -g @ukiyo-color-text "#17120d" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-bar "#e6d7c4" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-pane "#f3eadc" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-info "#0d4a68" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-notice "#a7471c" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-accent "#824f16" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-muted "#352e22" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-error "#842f24" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-alert "#654300" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-highlight "#0f6570" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-selection "#c8ad89" 2>/dev/null || true
- # Restore default plugin color order (fg=bg_pane claro sobre bg=color)
- tmux set-option -gu "@ukiyo-git-colors" 2>/dev/null || true
- tmux set-option -gu "@ukiyo-cpu-usage-colors" 2>/dev/null || true
- tmux set-option -gu "@ukiyo-ram-usage-colors" 2>/dev/null || true
- ;;
- dark)
- KANAGAWA_VARIANT="dragon"
- # Dreamcoder Ember Noir OLED palette — Ember Noir textures, ember_glow tension
- tmux set-option -g @ukiyo-color-text "#e8dfd0" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-bar "#181512" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-bg-pane "#100f0d" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-accent "#d99555" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-info "#4db35f" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-notice "#c96a45" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-muted "#c7b9aa" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-error "#ed8a7a" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-alert "#e8b866" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-highlight "#5f8f8f" 2>/dev/null || true
- tmux set-option -g @ukiyo-color-selection "#2b231b" 2>/dev/null || true
- # Swap plugin color order: fg=bg_bar (oscuro) sobre bg=color vibrante
- 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
- bash "${KANAGAWA_DIR}/ukiyo.tmux" 2>/dev/null || true
- fi
+ # Switch tmux-kanagawa theme variant to match Dreamcoder mode
+ # SOURCE OF TRUTH: colors MUST match themes/dreamcoder/tokens.json
+ # When updating tokens, update BOTH light AND dark sections here.
+ if [[ -d "${KANAGAWA_DIR}" ]]; then
+ case "${MODE}" in
+ light)
+ KANAGAWA_VARIANT="lotus"
+ # Dreamcoder Light palette — source: tokens.json modes.light.{text,accent,error,...}
+ tmux set-option -g @ukiyo-color-text "#17120d" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-bg-bar "#e6d7c4" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-bg-pane "#f3eadc" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-info "#0d4a68" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-notice "#a7471c" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-accent "#824f16" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-muted "#352e22" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-error "#842f24" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-alert "#654300" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-highlight "#0f6570" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-selection "#c8ad89" 2>/dev/null || true
+ # Restore default plugin color order (fg=bg_pane claro sobre bg=color)
+ tmux set-option -gu "@ukiyo-git-colors" 2>/dev/null || true
+ tmux set-option -gu "@ukiyo-cpu-usage-colors" 2>/dev/null || true
+ tmux set-option -gu "@ukiyo-ram-usage-colors" 2>/dev/null || true
+ ;;
+ dark)
+ KANAGAWA_VARIANT="dragon"
+ # Dreamcoder Ember Noir OLED palette — source: tokens.json modes.dark.{text,accent,error,...}
+ tmux set-option -g @ukiyo-color-text "#e8dfd0" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-bg-bar "#181512" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-bg-pane "#100f0d" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-accent "#d99555" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-info "#4db35f" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-notice "#c96a45" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-muted "#c7b9aa" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-error "#ed8a7a" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-alert "#e8b866" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-highlight "#5f8f8f" 2>/dev/null || true
+ tmux set-option -g @ukiyo-color-selection "#2b231b" 2>/dev/null || true
+ # Swap plugin color order: fg=bg_bar (oscuro) sobre bg=color vibrante
+ 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
+ bash "${KANAGAWA_DIR}/ukiyo.tmux" 2>/dev/null || true
+ fi
- # Source standalone tmux theme (pane borders, mode, bell colors)
- TMUX_THEME="${HOME}/.config/tmux/tmux-dreamcoder.conf"
- if [[ -f "${TMUX_THEME}" ]]; then
- tmux source-file "${TMUX_THEME}" 2>/dev/null || true
- fi
+ # Source standalone tmux theme (pane borders, mode, bell colors)
+ # When kanagawa is active, skip status-bar lines to preserve kanagawa layout
+ TMUX_THEME="${HOME}/.config/tmux/tmux-dreamcoder.conf"
+ if [[ -f "${TMUX_THEME}" && ! -d "${KANAGAWA_DIR}" ]]; then
+ tmux source-file "${TMUX_THEME}" 2>/dev/null || true
+ elif [[ -f "${TMUX_THEME}" && -d "${KANAGAWA_DIR}" ]]; then
+ # Kanagawa active: only source color lines, skip status-bar layout
+ grep -vE '^(set -g status-(left|right|position|interval|justify)|setw -g window-status-(format|current-format|separator))' \
+ "${TMUX_THEME}" | tmux source-file /dev/stdin 2>/dev/null || true
+ fi
- printf ' tmux environment and theme updated for %s mode\n' "${MODE}"
+ printf ' tmux environment and theme updated for %s mode\n' "${MODE}"
fi
# --- /tmux integration ---
@@ -136,12 +146,12 @@ WARP_THEME="${HOME}/.local/share/warp-terminal/themes/Dreamcoder.yaml"
WARP_VARIANT="${DOTS_DIR}/Warp/.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)
- ln -sf "${DOTS_DIR}/themes/dreamcoder/dunst-dreamcoder.conf" "${DUNST_CONF}"
- ;;
- esac
+ target=$(readlink "${DUNST_CONF}")
+ case "${target}" in
+ *dunst-dreamcoder-dark.conf | *dunst-dreamcoder-light.conf)
+ ln -sf "${DOTS_DIR}/themes/dreamcoder/dunst-dreamcoder.conf" "${DUNST_CONF}"
+ ;;
+ esac
fi
# Warp: flip active theme symlink to mode-specific variant
diff --git a/scripts/dreamcoder.sh b/scripts/dreamcoder.sh
index 46ce59e..fd2f1cf 100755
--- a/scripts/dreamcoder.sh
+++ b/scripts/dreamcoder.sh
@@ -1,32 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail
-# Dreamcoder CLI — quick theme switching
-# Usage: dreamcoder {dark|light}
+# Dreamcoder CLI — theme switching and system status
+# Usage: dreamcoder {dark|light|status|doctor|help}
DOTS_DIR="${DREAMCODER_DOTS_DIR:-${HOME}/Documents/PROYECTOS/dreamcoder-dots}"
APPLY_SCRIPT="${DOTS_DIR}/scripts/apply-theme-mode.sh"
+DOCTOR_SCRIPT="${DOTS_DIR}/scripts/doctor.sh"
case "${1:-}" in
- dark|light)
- export DREAMCODER_THEME_MODE="${1}"
- if [[ -f "${APPLY_SCRIPT}" ]]; then
- bash "${APPLY_SCRIPT}" "${1}"
- else
- echo "Error: ${APPLY_SCRIPT} not found" >&2
- exit 1
- fi
- ;;
- help|--help|-h)
- echo "Usage: dreamcoder {dark|light}"
- echo ""
- echo "Switch Dreamcoder theme mode across all terminals and apps."
- echo " dark → Ember Noir OLED (dark mode)"
- echo " light → Dreamcoder Light"
- exit 0
- ;;
- *)
- echo "Usage: dreamcoder {dark|light}" >&2
- exit 1
- ;;
+dark | light)
+ export DREAMCODER_THEME_MODE="${1}"
+ if [[ -f "${APPLY_SCRIPT}" ]]; then
+ bash "${APPLY_SCRIPT}" "${1}"
+ else
+ echo "Error: ${APPLY_SCRIPT} not found" >&2
+ exit 1
+ fi
+ ;;
+status)
+ echo "=== Dreamcoder OS Status ==="
+ echo "Theme mode: ${DREAMCODER_THEME_MODE:-dark}"
+ echo "Dotfiles dir: ${DOTS_DIR}"
+ echo "Git branch: $(cd "${DOTS_DIR}" && git branch --show-current 2>/dev/null || echo 'N/A')"
+ echo "Last commit: $(cd "${DOTS_DIR}" && git log -1 --oneline 2>/dev/null || echo 'N/A')"
+ echo ""
+ if command -v systemctl &>/dev/null; then
+ echo "Timer: $(systemctl --user is-active dreamcoder-theme-auto.timer 2>/dev/null || echo 'inactive')"
+ fi
+ if [[ -f "${HOME}/.cache/dreamcoder/ai-session.state" ]]; then
+ echo "AI session: $(cat "${HOME}/.cache/dreamcoder/ai-session.state")"
+ else
+ echo "AI session: inactive"
+ fi
+ ;;
+doctor)
+ if [[ -f "${DOCTOR_SCRIPT}" ]]; then
+ bash "${DOCTOR_SCRIPT}"
+ else
+ echo "Error: ${DOCTOR_SCRIPT} not found" >&2
+ exit 1
+ fi
+ ;;
+help | --help | -h)
+ echo "Usage: dreamcoder "
+ echo ""
+ echo "Commands:"
+ echo " dark Switch to Ember Noir OLED (dark mode)"
+ echo " light Switch to Dreamcoder Light"
+ echo " status Show system status overview"
+ echo " doctor Run health checks on all components"
+ echo " help Show this help"
+ exit 0
+ ;;
+*)
+ echo "Usage: dreamcoder {dark|light|status|doctor|help}" >&2
+ exit 1
+ ;;
esac
diff --git a/scripts/generate-palette-tokens.py b/scripts/generate-palette-tokens.py
new file mode 100644
index 0000000..3cabc8d
--- /dev/null
+++ b/scripts/generate-palette-tokens.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+"""Generate palette_tokens.py from themes/dreamcoder/tokens.json."""
+
+from __future__ import annotations
+
+import json
+import math
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent.parent
+TOKENS_FILE = ROOT / "themes" / "dreamcoder" / "tokens.json"
+OUTPUT = ROOT / "src" / "dreamcoder_theme" / "palette_tokens.py"
+
+ANSI_KEY_NAMES = [
+ "surface0",
+ "error",
+ "sage",
+ "accent",
+ "diagnostic",
+ "mauve",
+ "lavender",
+ "muted",
+ "subtle",
+ "error_bright",
+ "sage_bright",
+ "warning",
+ "diagnostic_bright",
+ "lavender_bright",
+ "focus_bright",
+ "text",
+]
+
+
+def hex_to_rgb(value: str) -> tuple[float, float, float]:
+ value = value.lstrip("#")
+ return (
+ int(value[0:2], 16) / 255,
+ int(value[2:4], 16) / 255,
+ int(value[4:6], 16) / 255,
+ )
+
+
+def rgb_to_hex(rgb: tuple[float, float, float]) -> str:
+ return "#" + "".join(f"{max(0, min(255, round(c * 255))):02x}" for c in rgb)
+
+
+_SRGB_BREAK = 0.04045
+_SRGB_LINEAR_BREAK = 0.0031308
+
+
+def srgb_to_linear(c: float) -> float:
+ return c / 12.92 if c <= _SRGB_BREAK else ((c + 0.055) / 1.055) ** 2.4
+
+
+def linear_to_srgb(c: float) -> float:
+ return 12.92 * c if c <= _SRGB_LINEAR_BREAK else 1.055 * (c ** (1 / 2.4)) - 0.055
+
+
+def hex_to_oklch(value: str) -> tuple[float, float, float]:
+ r, g, b = hex_to_rgb(value)
+ lr, lg, lb = srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b)
+ lms_l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb
+ lms_m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb
+ lms_s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb
+ lms_l_ = lms_l ** (1 / 3)
+ lms_m_ = lms_m ** (1 / 3)
+ lms_s_ = lms_s ** (1 / 3)
+ lightness = 0.2104542553 * lms_l_ + 0.7936177850 * lms_m_ - 0.0040720468 * lms_s_
+ a = 1.9779984951 * lms_l_ - 2.4285922050 * lms_m_ + 0.4505937099 * lms_s_
+ b_ = 0.0259040371 * lms_l_ + 0.7827717662 * lms_m_ - 0.8086757660 * lms_s_
+ chroma = math.hypot(a, b_)
+ hue = math.degrees(math.atan2(b_, a)) % 360
+ return lightness, chroma, hue
+
+
+def oklch_to_hex(lightness: float, chroma: float, hue: float) -> str:
+ h_rad = math.radians(hue)
+ a = chroma * math.cos(h_rad)
+ b_ = chroma * math.sin(h_rad)
+ l_ = lightness + 0.3963377774 * a + 0.2158037573 * b_
+ m_ = lightness - 0.1055613458 * a - 0.0638541728 * b_
+ s_ = lightness - 0.0894841775 * a - 1.2914855480 * b_
+ l_c = l_**3
+ m_c = m_**3
+ s_c = s_**3
+ lr = +4.0767416621 * l_c - 3.3077115913 * m_c + 0.2309699292 * s_c
+ lg = -1.2684380046 * l_c + 2.6097574011 * m_c - 0.3413193965 * s_c
+ lb = -0.0041960863 * l_c - 0.7034186147 * m_c + 1.7076147010 * s_c
+ return rgb_to_hex(
+ (
+ linear_to_srgb(max(0, min(1, lr))),
+ linear_to_srgb(max(0, min(1, lg))),
+ linear_to_srgb(max(0, min(1, lb))),
+ )
+ )
+
+
+def mix_hex(left: str, right: str, amount: float) -> str:
+ a = hex_to_rgb(left)
+ b = hex_to_rgb(right)
+ return rgb_to_hex(tuple(x + (y - x) * amount for x, y in zip(a, b)))
+
+
+def ramp_step(base: str, target_l_delta: float) -> str:
+ lightness, chroma, hue = hex_to_oklch(base)
+ return oklch_to_hex(max(0, min(1, lightness + target_l_delta)), chroma, hue)
+
+
+def rgba_from_hex(hex_color: str, alpha: float) -> str:
+ r, g, b = hex_to_rgb(hex_color)
+ return f"rgba({round(r * 255)}, {round(g * 255)}, {round(b * 255)}, {alpha:.2f})"
+
+
+def enrich_mode(mode: dict[str, str]) -> dict[str, str]:
+ """Fill derived semantic tokens; preserve authored anchors."""
+ c = dict(mode)
+ is_dark = c.get("details") == "darker"
+ bg = c["bg"]
+ text = c["text"]
+
+ c.setdefault("success", c["sage"])
+ c.setdefault("info", c["diagnostic"])
+ c.setdefault("text_heading", ramp_step(text, 0.04 if is_dark else -0.03))
+ c.setdefault("surface3", ramp_step(c["surface2"], -0.04 if is_dark else -0.05))
+
+ c.setdefault("selection_bg", c.get("surface1", c["bg"]))
+ c.setdefault("selection_fg", text)
+ c["selection"] = c["selection_bg"]
+
+ on_light = c.get("surface0", "#ffffff")
+ on_dark = c.get("prompt_text", bg)
+ c.setdefault("on_surface", text)
+ c.setdefault("on_accent", on_dark if is_dark else on_light)
+ c.setdefault("on_error", c["on_accent"])
+ c.setdefault("on_focus", c["on_accent"])
+
+ c.setdefault("link", c["accent"])
+ c.setdefault("link_hover", c["accent_2"])
+ c.setdefault("disabled", mix_hex(c["muted"], bg, 0.35))
+ c.setdefault("hover", c["surface1"])
+ c.setdefault("pressed", c["surface2"])
+ c.setdefault("overlay", rgba_from_hex(bg, 0.52 if is_dark else 0.40))
+ c.setdefault("scrim", "rgba(0, 0, 0, 0.58)" if is_dark else "rgba(26, 18, 12, 0.42)")
+
+ if "panel_rgba" not in c:
+ c["panel_rgba"] = rgba_from_hex(bg, 0.78 if is_dark else 0.96)
+ if "module_rgba" not in c:
+ fg = mix_hex(text, bg, 0.08 if is_dark else 0.0)
+ c["module_rgba"] = rgba_from_hex(fg, 0.08 if is_dark else 0.80)
+ if "active_rgba" not in c:
+ c["active_rgba"] = rgba_from_hex(c["accent"], 0.24 if is_dark else 0.34)
+ if "inactive_border" not in c:
+ c["inactive_border"] = rgba_from_hex(c["border"], 0.50 if is_dark else 0.93)
+
+ return c
+
+
+def render_palette_tokens(variants: dict[str, dict[str, str]]) -> str:
+ lines = [
+ '"""Static palette token data for Dreamcoder themes.',
+ "",
+ "AUTO-GENERATED from themes/dreamcoder/tokens.json — do not edit by hand.",
+ "Run: ./scripts/generate-palette-tokens.py",
+ '"""',
+ "",
+ "from __future__ import annotations",
+ "",
+ "VARIANTS = " + json.dumps(variants, indent=4) + "",
+ "",
+ "ANSI_KEY_NAMES = " + json.dumps(ANSI_KEY_NAMES, indent=4) + "",
+ "",
+ ]
+ return "\n".join(lines)
+
+
+def main() -> int:
+ tokens = json.loads(TOKENS_FILE.read_text())
+ modes = tokens.get("modes", {})
+ enriched: dict[str, dict[str, str]] = {}
+ for name, palette in modes.items():
+ enriched[name] = enrich_mode(palette)
+
+ tokens["modes"] = enriched
+ tokens["guardrails"].setdefault("minimum_apca_on_accent", 60)
+ tokens["guardrails"].setdefault("minimum_apca_heading_light", 60)
+ tokens["guardrails"].setdefault("minimum_apca_heading_dark", 45)
+ TOKENS_FILE.write_text(json.dumps(tokens, indent=2) + "\n")
+
+ subset = {k: enriched[k] for k in ("dark", "light", "dusk") if k in enriched}
+ OUTPUT.write_text(render_palette_tokens(subset))
+ print(f"✓ Updated {TOKENS_FILE.relative_to(ROOT)}")
+ print(f"✓ Generated {OUTPUT.relative_to(ROOT)}")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/generate-theme-preview.py b/scripts/generate-theme-preview.py
index 6145715..ba90511 100755
--- a/scripts/generate-theme-preview.py
+++ b/scripts/generate-theme-preview.py
@@ -22,7 +22,9 @@
"surface0",
"surface1",
"surface2",
+ "surface3",
"text",
+ "text_heading",
"muted",
"subtle",
"comment",
@@ -30,10 +32,18 @@
"accent_2",
"diagnostic",
"sage",
+ "success",
+ "info",
"lavender",
"mauve",
"error",
"warning",
+ "on_accent",
+ "on_error",
+ "link",
+ "link_hover",
+ "selection_bg",
+ "selection_fg",
"border",
"border_ui",
"border_hi",
@@ -157,9 +167,7 @@ def contrast_table(name, palette):
bg = palette["bg"]
for key in TEXT_KEYS:
ratio = contrast(bg, palette[key])
- target = (
- "AAA" if key == "text" and ratio >= 7 else "AA" if ratio >= 4.5 else "FAIL"
- )
+ target = "AAA" if key == "text" and ratio >= 7 else "AA" if ratio >= 4.5 else "FAIL"
rows.append(f"| `{key}` | {ratio:.2f}:1 | {target} |")
return "\n".join(rows)
@@ -218,8 +226,8 @@ def main():
"",
"- `comment` is softer and lower-chroma than `subtle` (syntax vs UI chrome).",
"- Dark `accent` (refined ember orange), `accent_2` (maple red), `error` (soft coral red), and `warning` (lúcuma gold) form the orange/red/gold signature.",
- "- `focus` follows the orange protagonist instead of a separate cyan ring; `diagnostic` stays warm amber so the palette remains autumnal.",
- "",
+ "- `accent` carries brand CTAs and active chrome; `focus` is teal for keyboard/input affordance (WCAG ring).",
+ "- `on_accent`, `on_error`, and `selection_bg`/`selection_fg` are explicit pairs validated in CI.",
]
parts += ["## Palette", ""]
for mode, palette in tokens["modes"].items():
diff --git a/scripts/sync-dreamcoder-theme.py b/scripts/sync-dreamcoder-theme.py
index 345e3fc..ae36eff 100755
--- a/scripts/sync-dreamcoder-theme.py
+++ b/scripts/sync-dreamcoder-theme.py
@@ -3,6 +3,5 @@
from dreamcoder_theme.sync import main
-
if __name__ == "__main__":
main()
diff --git a/scripts/verify-theme-health.py b/scripts/verify-theme-health.py
index 659c9b7..0144397 100755
--- a/scripts/verify-theme-health.py
+++ b/scripts/verify-theme-health.py
@@ -1,8 +1,10 @@
#!/usr/bin/env python3
import json
+import os
import plistlib
import re
from pathlib import Path
+
from dreamcoder_theme.palette import ansi as terminal_ansi
ROOT = Path(__file__).resolve().parent.parent
@@ -18,17 +20,88 @@
ROOT / "Codex-CLI/Dreamcoder-Light.tmTheme",
ROOT / "Codex-CLI/Dreamcoder-Dark.tmTheme",
]
-KITTY_FILE = ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-dark.conf"
-STARSHIP_FILE = ROOT / "Shell" / ".config" / "starship-dark.toml"
-GHOSTTY_FILE = ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-dark"
-WAYBAR_FILE = ROOT / "themes" / "dreamcoder" / "waybar-dark.css"
-HYPRLAND_FILE = ROOT / "themes" / "dreamcoder" / "hyprland-dark.conf"
-ROFI_FILE = ROOT / "themes" / "dreamcoder" / "rofi-dark.rasi"
+KITTY_FILES = [
+ ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-dark.conf",
+ ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-light.conf",
+]
+STARSHIP_FILES = [
+ ROOT / "Shell" / ".config" / "starship-dark.toml",
+ ROOT / "Shell" / ".config" / "starship-light.toml",
+]
+GHOSTTY_FILES = [
+ ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-dark",
+ ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-light",
+]
+WAYBAR_FILES = [
+ ROOT / "themes" / "dreamcoder" / "waybar-dark.css",
+ ROOT / "themes" / "dreamcoder" / "waybar-light.css",
+]
+HYPRLAND_FILES = [
+ ROOT / "themes" / "dreamcoder" / "hyprland-dark.conf",
+ ROOT / "themes" / "dreamcoder" / "hyprland-light.conf",
+]
+ROFI_FILES = [
+ ROOT / "themes" / "dreamcoder" / "rofi-dark.rasi",
+ ROOT / "themes" / "dreamcoder" / "rofi-light.rasi",
+]
+BTOP_FILES = [
+ ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-dark.theme",
+ ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-light.theme",
+]
+DUNST_FILES = [
+ ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-dark.conf",
+ ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-light.conf",
+]
+FZF_FILES = [
+ ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-dark.sh",
+ ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-light.sh",
+]
HYPR_COLORS_LUA_GLOB = list((ROOT / "themes" / "dreamcoder").glob("hypr-colors-*.lua"))
HYPR_COLORS_CONF_GLOB = list((ROOT / "themes" / "dreamcoder").glob("hypr-colors-*.conf"))
-BTOP_FILE = ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-dark.theme"
-DUNST_FILE = ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-dark.conf"
-FZF_FILE = ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-dark.sh"
+TOKEN_PARITY_KEYS = [
+ "bg",
+ "bg_soft",
+ "surface0",
+ "surface1",
+ "surface2",
+ "surface3",
+ "text",
+ "text_heading",
+ "muted",
+ "subtle",
+ "comment",
+ "border",
+ "border_ui",
+ "border_hi",
+ "focus",
+ "accent",
+ "accent_2",
+ "diagnostic",
+ "sage",
+ "success",
+ "info",
+ "error",
+ "warning",
+ "lavender",
+ "mauve",
+ "on_surface",
+ "on_accent",
+ "on_error",
+ "on_focus",
+ "link",
+ "link_hover",
+ "selection_bg",
+ "selection_fg",
+ "disabled",
+ "hover",
+ "pressed",
+]
+ON_PAIRS = [
+ ("on_accent", "accent"),
+ ("on_error", "error"),
+ ("on_focus", "focus"),
+ ("selection_fg", "selection_bg"),
+]
TEXT_KEYS = ["text", "textMuted", "primary", "info", "success", "error", "warning"]
UI_KEYS = ["border", "borderActive", "borderFocus"]
QUIET_UI_KEYS = ["border"]
@@ -70,15 +143,14 @@
"error",
"warning",
]
-TOKEN_BODY_APCA_KEYS = ["text", "diagnostic", "error", "warning"]
+TOKEN_BODY_APCA_KEYS = ["text", "error", "warning"]
+TOKEN_INFO_APCA_KEYS = ["diagnostic"]
TOKEN_ACCENT_APCA_KEYS = ["accent", "accent_2", "sage"]
TOKEN_QUIET_APCA_KEYS = ["muted", "comment", "subtle"]
TOKEN_UI_KEYS = ["border_ui", "focus"]
HEX = re.compile(r"^#[0-9a-fA-F]{6}$")
# RGBA pattern: rgba(r, g, b, a) with r/g/b 0-255 and a 0.0-1.0
-RGBA = re.compile(
- r"^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.?\d+)\s*\)$"
-)
+RGBA = re.compile(r"^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.?\d+)\s*\)$")
def rgb(value):
@@ -211,10 +283,17 @@ def check_rgba_tokens():
)
+def check_apca_require(mode, key, value, bg, threshold):
+ """Fail CI when APCA body/heading pairs miss targets."""
+ lc = apca_lc(value, bg)
+ require(
+ lc >= threshold,
+ f"tokens:{mode}:{key} APCA Lc {lc:.1f} < {threshold}",
+ )
+
+
def check_apca_or_warn(mode, key, value, bg, threshold):
"""Check APCA contrast, log warning but don't fail (APCA is public beta, not a standard)."""
- import os
-
lc = apca_lc(value, bg)
if os.environ.get("DREAMCODER_ENFORCE_APCA", "") and lc < threshold:
require(
@@ -227,6 +306,17 @@ def check_apca_or_warn(mode, key, value, bg, threshold):
)
+def check_token_parity(tokens):
+ dark = tokens["modes"]["dark"]
+ light = tokens["modes"]["light"]
+ dark_keys = {k for k in dark if k not in {"name", "details"}}
+ light_keys = {k for k in light if k not in {"name", "details"}}
+ require(dark_keys == light_keys, f"dark/light token key mismatch: {dark_keys ^ light_keys}")
+ for key in TOKEN_PARITY_KEYS:
+ require(key in dark, f"tokens:dark missing parity key {key}")
+ require(key in light, f"tokens:light missing parity key {key}")
+
+
def check_tokens():
tokens = json.loads(TOKEN_FILE.read_text())
guardrails = tokens["guardrails"]
@@ -237,24 +327,24 @@ def check_tokens():
apca_ui_dark = guardrails.get("minimum_apca_ui_dark", 28)
terminal_ansi_min = guardrails.get("minimum_terminal_ansi_contrast", 4.5)
terminal_cursor_min = guardrails.get("minimum_terminal_cursor_contrast", 4.5)
+ apca_heading_light = guardrails.get("minimum_apca_heading_light", 60)
+ apca_heading_dark = guardrails.get("minimum_apca_heading_dark", 45)
+ apca_on_accent = guardrails.get("minimum_apca_on_accent", 60)
terminal_selection_min = guardrails.get("minimum_terminal_selection_contrast", 7.0)
require(
- tokens["guardrails"]["canonical_opencode_theme"] == "dreamcoder",
+ guardrails["canonical_opencode_theme"] == "dreamcoder",
"tokens: canonical opencode theme must be dreamcoder",
)
+ check_token_parity(tokens)
for mode, palette in tokens["modes"].items():
bg = palette["bg"]
require(HEX.match(bg), f"tokens:{mode}: invalid background")
- require(
- bg.lower() not in {"#000000", "#ffffff"}, f"tokens:{mode}: harsh background"
- )
+ require(bg.lower() not in {"#000000", "#ffffff"}, f"tokens:{mode}: harsh background")
for key in TOKEN_TEXT_KEYS:
require(key in palette, f"tokens:{mode}:{key}: missing token")
value = palette[key]
require(HEX.match(value), f"tokens:{mode}:{key}: invalid hex")
- require(
- contrast(bg, value) >= 4.5, f"tokens:{mode}:{key} contrast below 4.5"
- )
+ require(contrast(bg, value) >= 4.5, f"tokens:{mode}:{key} contrast below 4.5")
if palette.get("details") == "lighter":
apca_body = apca_body_light
apca_body_keys = TOKEN_BODY_APCA_KEYS
@@ -267,7 +357,15 @@ def check_tokens():
apca_accent = apca_body_dark
for key in apca_body_keys:
value = palette[key]
- check_apca_or_warn(mode, key, value, bg, apca_body)
+ check_apca_require(mode, key, value, bg, apca_body)
+ for key in TOKEN_INFO_APCA_KEYS:
+ if key not in palette:
+ continue
+ info_target = 42 if palette.get("details") == "darker" else apca_body
+ check_apca_require(mode, key, palette[key], bg, info_target)
+ if "text_heading" in palette:
+ heading_target = apca_heading_light if mode == "light" else apca_heading_dark
+ check_apca_require(mode, "text_heading", palette["text_heading"], bg, heading_target)
for key in apca_accent_keys:
value = palette[key]
check_apca_or_warn(mode, key, value, bg, apca_accent)
@@ -276,18 +374,23 @@ def check_tokens():
continue
value = palette[key]
check_apca_or_warn(mode, key, value, bg, apca_quiet)
- require(
- contrast(bg, palette["text"]) >= 7, f"tokens:{mode}: main text below AAA"
- )
- check_apca_or_warn(mode, "text", palette["text"], bg, apca_body)
+ require(contrast(bg, palette["text"]) >= 7, f"tokens:{mode}: main text below AAA")
+ check_apca_require(mode, "text", palette["text"], bg, apca_body)
+ for fg_key, bg_key in ON_PAIRS:
+ require(
+ fg_key in palette and bg_key in palette, f"tokens:{mode}: missing {fg_key}/{bg_key}"
+ )
+ pair_ratio = contrast(palette[fg_key], palette[bg_key])
+ require(pair_ratio >= 4.5, f"tokens:{mode}:{fg_key}/{bg_key} {pair_ratio:.2f} < 4.5")
+ if fg_key == "on_accent":
+ check_apca_require(mode, fg_key, palette[fg_key], palette[bg_key], apca_on_accent)
for index, color in enumerate(terminal_ansi(palette)):
require(
contrast(color, bg) >= terminal_ansi_min,
f"tokens:{mode}: ANSI color{index} contrast {contrast(color, bg):.2f} < {terminal_ansi_min}",
)
- invert = palette.get("details") == "lighter"
- sel_fg = palette["bg"] if invert else palette["text"]
- sel_bg = palette["text"] if invert else palette["selection"]
+ sel_fg = palette["selection_fg"]
+ sel_bg = palette["selection_bg"]
require(
contrast(palette["accent"], bg) >= terminal_cursor_min,
f"tokens:{mode}: cursor contrast {contrast(palette['accent'], bg):.2f} < {terminal_cursor_min}",
@@ -330,9 +433,7 @@ def check_theme_file(file):
bg not in {"#000000", "#ffffff"},
f"{file}: background uses harsh pure black/white",
)
- require(
- 0.004 < lum(bg) < 0.94, f"{file}: background luminance is outside comfort band"
- )
+ require(0.004 < lum(bg) < 0.94, f"{file}: background luminance is outside comfort band")
for key in TEXT_KEYS + SYNTAX_KEYS:
ratio = contrast(bg, theme[key])
require(ratio >= 4.5, f"{file}: {key} contrast {ratio:.2f} < 4.5")
@@ -374,9 +475,7 @@ def check_codex_cli_theme(file):
contrast(bg, settings["lineHighlight"]) >= 1.15,
f"{file}: light line highlight too faint",
)
- require(
- contrast(bg, settings["gutter"]) >= 1.45, f"{file}: light gutter too faint"
- )
+ require(contrast(bg, settings["gutter"]) >= 1.45, f"{file}: light gutter too faint")
def check_opencode_repo():
@@ -493,16 +592,24 @@ def check_hypr_colors_file(file):
for file in CODEX_CLI_FILES:
check_codex_cli_theme(file)
check_opencode_repo()
-# Optional target validations (skip if files don't exist)
-check_kitty_colors(KITTY_FILE)
-check_starship_config(STARSHIP_FILE)
-check_ghostty_theme(GHOSTTY_FILE)
-check_waybar_css(WAYBAR_FILE)
-check_hypr_config(HYPRLAND_FILE)
-check_rofi_theme(ROFI_FILE)
-check_btop_theme(BTOP_FILE)
-check_dunst_theme(DUNST_FILE)
-check_fzf_theme(FZF_FILE)
+for file in KITTY_FILES:
+ check_kitty_colors(file)
+for file in STARSHIP_FILES:
+ check_starship_config(file)
+for file in GHOSTTY_FILES:
+ check_ghostty_theme(file)
+for file in WAYBAR_FILES:
+ check_waybar_css(file)
+for file in HYPRLAND_FILES:
+ check_hypr_config(file)
+for file in ROFI_FILES:
+ check_rofi_theme(file)
+for file in BTOP_FILES:
+ check_btop_theme(file)
+for file in DUNST_FILES:
+ check_dunst_theme(file)
+for file in FZF_FILES:
+ check_fzf_theme(file)
for file in HYPR_COLORS_LUA_GLOB:
check_hypr_colors_file(file)
for file in HYPR_COLORS_CONF_GLOB:
diff --git a/skills/dreamcoder-palette-tokens/SKILL.md b/skills/dreamcoder-palette-tokens/SKILL.md
new file mode 100644
index 0000000..aaacea1
--- /dev/null
+++ b/skills/dreamcoder-palette-tokens/SKILL.md
@@ -0,0 +1,49 @@
+# Dreamcoder Palette Tokens
+
+## Token Schema
+
+Defined in `themes/dreamcoder/tokens.json` with validation schema at `tokens.schema.json`.
+
+## Modes
+
+| Mode | Name | BG | Accent | Use Case |
+|------|------|----|--------|----------|
+| `dark` | Ember Noir OLED | `#100f0d` | `#d99555` | 18:00-07:00 |
+| `light` | Cocoa/Lúcuma | `#f3eadc` | `#824f16` | 07:00-16:00 |
+| `dusk` | Transition | `#ebe4d6` | `#8a5520` | 16:00-18:00 |
+
+## Token Categories
+
+- **Background**: `bg`, `bg_soft`, `surface{0,1,2,3}`
+- **Text**: `text`, `text_heading`, `muted`, `subtle`, `comment`
+- **Accent**: `accent`, `accent_2`, `focus`
+- **Semantic**: `error`, `warning`, `success`, `info`, `diagnostic`
+- **UI**: `border`, `border_ui`, `border_hi`, `selection`
+- **Prompt**: `prompt_bg`, `prompt_surface{0,1,2}`, `prompt_text`, `prompt_muted`, `prompt_accent`
+
+## Guardrails (WCAG + APCA)
+
+```json
+{
+ "minimum_text_contrast": 4.5,
+ "preferred_main_text_contrast": 7.0,
+ "minimum_apca_body": 75,
+ "minimum_apca_body_dark": 50,
+ "avoid_pure_black_white": true
+}
+```
+
+## Color Tokens to Update
+
+When updating a color in `tokens.json`, also update:
+
+1. `src/dreamcoder_theme/palette_tokens.py` (regenerated from tokens.json)
+2. `scripts/apply-theme-mode.sh` (kanagawa tmux colors)
+3. `src/dreamcoder_theme/renderers_starship.py` (palette section)
+
+## Validation
+
+```bash
+python scripts/verify-theme-health.py # Validates all tokens
+python scripts/generate-theme-preview.py # Generates docs preview
+```
diff --git a/skills/dreamcoder-theme-engine/SKILL.md b/skills/dreamcoder-theme-engine/SKILL.md
new file mode 100644
index 0000000..73d39bd
--- /dev/null
+++ b/skills/dreamcoder-theme-engine/SKILL.md
@@ -0,0 +1,45 @@
+# Dreamcoder Theme Engine
+
+## Purpose
+
+Python theme engine that reads `tokens.json` and generates color configs for 28+ targets.
+
+## Architecture
+
+```
+tokens.json → palette_tokens.py (static tokens)
+ → palette.py (adaptive from wallpaper)
+ → renderers*.py (28+ target formats)
+ → writers.py (write_if_changed)
+```
+
+## Key Files
+
+- `src/dreamcoder_theme/palette_tokens.py` — Canonical token definitions (dark/light/dusk)
+- `src/dreamcoder_theme/palette.py` — `guard()` for WCAG contrast validation, `adaptive_palette()` for wallpaper colors
+- `src/dreamcoder_theme/renderers.py` — Hub that imports all `renderers_*.py` leaf modules
+- `src/dreamcoder_theme/renderers_kitty.py` — Kitty terminal colors
+- `src/dreamcoder_theme/renderers_ghostty_warp.py` — Ghostty + Warp
+- `src/dreamcoder_theme/renderers_starship.py` — Starship prompt (23 modules)
+- `src/dreamcoder_theme/renderers_tmux.py` — Tmux theme
+- `src/dreamcoder_theme/writers.py` — `write_if_changed()`, `update_ghostty_theme()`, `ensure_kitty_ui_include()`
+- `src/dreamcoder_theme/sync.py` — Orchestrator: loads variants, renders, writes
+
+## Adding a New Renderer
+
+1. Create `renderers_.py` with a function `def _content(palette: dict) -> str`
+2. Import it in `renderers.py` `__all__` list
+3. Add it to `sync_active_targets()` or `sync_repo_snippets()` in `sync.py`
+4. Add path to `ThemePaths` dataclass in `settings.py`
+
+## Testing
+
+```bash
+pytest tests/ -v
+pytest tests/ --cov=dreamcoder_theme --cov-fail-under=40
+```
+
+## Token Guardrails
+
+All colors must pass WCAG 4.5:1 minimum contrast and APCA body minimums.
+Use `guard(color, background, mode)` from `palette.py` to validate.
diff --git a/src/dreamcoder_theme/palette.py b/src/dreamcoder_theme/palette.py
index 58172c4..4545cf8 100644
--- a/src/dreamcoder_theme/palette.py
+++ b/src/dreamcoder_theme/palette.py
@@ -8,7 +8,7 @@
import warnings
from pathlib import Path
-from .palette_tokens import ANSI_KEYS
+from .palette_tokens import ANSI_KEY_NAMES
def load_variants(
@@ -22,7 +22,6 @@ def load_variants(
for key in ("dark", "light", "dusk"):
if key in modes:
merged[key].update(modes[key])
- # Reconcile: warn on silent divergence between defaults and tokens.json
for mode_key in ("dark", "light", "dusk"):
if mode_key in modes and mode_key in defaults:
for token_key in set(defaults[mode_key]) & set(modes[mode_key]):
@@ -32,7 +31,7 @@ def load_variants(
warnings.warn(
f"palette divergence: {mode_key}.{token_key} = {t!r} (tokens.json) "
f"overrides {d!r} (palette_tokens.py). "
- f"palette_tokens.py should be regenerated from tokens.json.",
+ f"Run ./scripts/generate-palette-tokens.py.",
stacklevel=2,
)
return merged
@@ -43,6 +42,10 @@ def matugen_mode_name(mode_name: str) -> str:
def resolve_color(palette: dict[str, str], value: str) -> str:
+ if value.endswith("_bright"):
+ base = value.removesuffix("_bright")
+ if base in palette:
+ return mix(palette[base], palette["text"], 0.18)
return palette.get(value, value)
@@ -69,9 +72,9 @@ def rel_luminance(value: str) -> float:
sr = r / 255
sg = g / 255
sb = b / 255
- lr = sr / 12.92 if sr <= 0.03928 else ((sr + 0.055) / 1.055) ** 2.4 # noqa: PLR2004
- lg = sg / 12.92 if sg <= 0.03928 else ((sg + 0.055) / 1.055) ** 2.4 # noqa: PLR2004
- lb = sb / 12.92 if sb <= 0.03928 else ((sb + 0.055) / 1.055) ** 2.4 # noqa: PLR2004
+ lr = sr / 12.92 if sr <= 0.03928 else ((sr + 0.055) / 1.055) ** 2.4
+ lg = sg / 12.92 if sg <= 0.03928 else ((sg + 0.055) / 1.055) ** 2.4
+ lb = sb / 12.92 if sb <= 0.03928 else ((sb + 0.055) / 1.055) ** 2.4
return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb # type: ignore[no-any-return, unused-ignore]
@@ -90,6 +93,26 @@ def guard(color: str, background: str, mode_name: str, minimum: float = 4.5) ->
return safe
+def compute_on_color(
+ background: str,
+ mode_name: str,
+ *,
+ light_candidate: str | None = None,
+ dark_candidate: str | None = None,
+ minimum: float = 4.5,
+) -> str:
+ """Pick readable foreground on a filled surface."""
+ if mode_name == "dark":
+ candidates = [c for c in (dark_candidate, "#100f0d", "#000000") if c]
+ else:
+ candidates = [c for c in (light_candidate, "#fff7ea", "#ffffff") if c]
+ for candidate in candidates:
+ if contrast(candidate, background) >= minimum:
+ return candidate
+ target = candidates[-1]
+ return guard(target, background, mode_name, minimum=minimum)
+
+
def surface_guard(
color: str,
background: str,
@@ -149,7 +172,7 @@ def adaptive_palette(
c = dict(base)
bg = mix(c["bg"], scheme.get("background", c["bg"]), 0.18)
- if contrast(bg, c["text"]) >= 7: # noqa: PLR2004
+ if contrast(bg, c["text"]) >= 7:
c["bg"] = bg
c["surface0"] = surface_guard(
mix(c["surface0"], scheme.get("surface_container", c["surface0"]), 0.16),
@@ -183,7 +206,12 @@ def adaptive_palette(
mode_name,
)
c["border"] = mix(c["border"], scheme.get("outline", c["border"]), 0.25)
- c["selection"] = mix(c["selection"], scheme.get("primary_container", c["selection"]), 0.18)
+ c["selection_bg"] = mix(
+ c.get("selection_bg", c["surface1"]),
+ scheme.get("primary_container", c.get("selection_bg", c["surface1"])),
+ 0.18,
+ )
+ c["selection"] = c["selection_bg"]
c["prompt_accent"] = c["accent"]
c["prompt_accent_2"] = c["accent_2"]
return c
@@ -192,8 +220,10 @@ def adaptive_palette(
def ansi(palette: dict[str, str]) -> list[str]:
mode_name = "dark" if palette["details"] == "darker" else "light"
safe = []
- for key in ANSI_KEYS:
+ for key in ANSI_KEY_NAMES:
color = resolve_color(palette, key)
+ if not color.startswith("#"):
+ raise ValueError(f"ANSI key {key!r} resolved to non-hex {color!r}")
safe.append(guard(color, palette["bg"], mode_name))
return safe
@@ -201,3 +231,58 @@ def ansi(palette: dict[str, str]) -> list[str]:
def detect_mode(palette: dict[str, str]) -> str:
"""Return "dark" or "light" based on the palette's details key."""
return "dark" if palette.get("details") == "darker" else "light"
+
+
+def validate_palette(
+ palette: dict[str, str], guardrails: dict[str, float] | None = None
+) -> list[str]:
+ """Return human-readable validation errors for a mode palette."""
+ g = guardrails or {}
+ errors: list[str] = []
+ bg = palette["bg"]
+ mode = detect_mode(palette)
+ text_min = g.get("minimum_text_contrast", 4.5)
+ main_min = g.get("preferred_main_text_contrast", 7.0)
+ sel_min = g.get("minimum_terminal_selection_contrast", 7.0)
+
+ for key in ("text", "muted", "comment", "accent", "error", "warning", "diagnostic"):
+ if key not in palette:
+ errors.append(f"missing token: {key}")
+ continue
+ if contrast(bg, palette[key]) < text_min:
+ errors.append(f"{key} contrast {contrast(bg, palette[key]):.2f} < {text_min}")
+
+ if contrast(bg, palette["text"]) < main_min:
+ errors.append(f"main text contrast {contrast(bg, palette['text']):.2f} < {main_min}")
+
+ for fg_key, bg_key in (
+ ("selection_fg", "selection_bg"),
+ ("on_accent", "accent"),
+ ("on_error", "error"),
+ ):
+ if fg_key in palette and bg_key in palette:
+ ratio = contrast(palette[fg_key], palette[bg_key])
+ if fg_key == "selection_fg" and ratio < sel_min:
+ errors.append(f"selection pair {ratio:.2f} < {sel_min}")
+ if fg_key.startswith("on_") and ratio < 4.5:
+ errors.append(f"{fg_key}/{bg_key} contrast {ratio:.2f} < 4.5")
+
+ if "on_accent" in palette and "accent" in palette:
+ if contrast(palette["on_accent"], palette["accent"]) < 4.5:
+ errors.append("on_accent/accent WCAG contrast below 4.5")
+
+ for index, color in enumerate(ansi(palette)):
+ if contrast(color, bg) < g.get("minimum_terminal_ansi_contrast", 4.5):
+ errors.append(f"ANSI color{index} contrast too low")
+
+ for step in ("bg_soft", "surface0", "surface1", "surface2", "surface3"):
+ if step in palette and contrast(palette[step], bg) < 1.02:
+ errors.append(f"{step} too close to bg")
+
+ if palette.get("comment") == palette.get("subtle"):
+ errors.append("comment and subtle must differ")
+ if palette.get("accent") == palette.get("accent_2"):
+ errors.append("accent and accent_2 must differ")
+ if mode == "light" and "surface3" not in palette:
+ errors.append("light mode missing surface3")
+ return errors
diff --git a/src/dreamcoder_theme/palette_tokens.py b/src/dreamcoder_theme/palette_tokens.py
index 60191a5..83a4816 100644
--- a/src/dreamcoder_theme/palette_tokens.py
+++ b/src/dreamcoder_theme/palette_tokens.py
@@ -1,4 +1,8 @@
-"""Static palette token data for Dreamcoder themes."""
+"""Static palette token data for Dreamcoder themes.
+
+AUTO-GENERATED from themes/dreamcoder/tokens.json — do not edit by hand.
+Run: ./scripts/generate-palette-tokens.py
+"""
from __future__ import annotations
@@ -23,7 +27,7 @@
"accent": "#d99555",
"accent_2": "#c96a45",
"diagnostic": "#5f95ca",
- "selection": "#3d3028",
+ "selection": "#2b231b",
"panel_rgba": "rgba(16, 15, 13, 0.78)",
"module_rgba": "rgba(232, 223, 208, 0.08)",
"active_rgba": "rgba(217, 149, 85, 0.24)",
@@ -42,6 +46,21 @@
"mauve": "#e29cb4",
"error": "#ed8a7a",
"warning": "#e8b866",
+ "success": "#4db35f",
+ "info": "#5f95ca",
+ "selection_bg": "#2b231b",
+ "selection_fg": "#e8dfd0",
+ "on_surface": "#e8dfd0",
+ "on_accent": "#100f0d",
+ "on_error": "#100f0d",
+ "on_focus": "#100f0d",
+ "link": "#d99555",
+ "link_hover": "#c96a45",
+ "disabled": "#877e73",
+ "hover": "#2b231b",
+ "pressed": "#392e21",
+ "overlay": "rgba(16, 15, 13, 0.52)",
+ "scrim": "rgba(0, 0, 0, 0.58)"
},
"light": {
"name": "Dreamcoder Light",
@@ -66,7 +85,7 @@
"mauve": "#7d3e64",
"error": "#842f24",
"warning": "#654300",
- "selection": "#17120d",
+ "selection": "#decbb1",
"panel_rgba": "rgba(243, 234, 220, 0.96)",
"module_rgba": "rgba(222, 203, 177, 0.80)",
"active_rgba": "rgba(130, 79, 22, 0.34)",
@@ -80,6 +99,23 @@
"prompt_muted": "#53402e",
"prompt_accent": "#8a5520",
"prompt_accent_2": "#a7471c",
+ "success": "#3d723d",
+ "info": "#0d4a68",
+ "text_heading": "#100c07",
+ "surface3": "#b89d7a",
+ "selection_bg": "#decbb1",
+ "selection_fg": "#17120d",
+ "on_surface": "#17120d",
+ "on_accent": "#fff7ea",
+ "on_error": "#fff7ea",
+ "on_focus": "#fff7ea",
+ "link": "#824f16",
+ "link_hover": "#a7471c",
+ "disabled": "#787063",
+ "hover": "#decbb1",
+ "pressed": "#c8ad89",
+ "overlay": "rgba(243, 234, 220, 0.40)",
+ "scrim": "rgba(26, 18, 12, 0.42)"
},
"dusk": {
"name": "Dreamcoder Dusk",
@@ -104,7 +140,7 @@
"mauve": "#784762",
"error": "#773126",
"warning": "#604000",
- "selection": "#1a1713",
+ "selection": "#d8cbb8",
"panel_rgba": "rgba(235, 228, 214, 0.88)",
"module_rgba": "rgba(26, 23, 19, 0.08)",
"active_rgba": "rgba(138, 85, 32, 0.22)",
@@ -118,10 +154,27 @@
"prompt_muted": "#574939",
"prompt_accent": "#965f25",
"prompt_accent_2": "#96411e",
- },
+ "success": "#466b41",
+ "info": "#104b67",
+ "text_heading": "#13100c",
+ "surface3": "#b6a691",
+ "selection_bg": "#d8cbb8",
+ "selection_fg": "#1a1713",
+ "on_surface": "#1a1713",
+ "on_accent": "#f1eadf",
+ "on_error": "#f1eadf",
+ "on_focus": "#f1eadf",
+ "link": "#8a5520",
+ "link_hover": "#96411e",
+ "disabled": "#847c71",
+ "hover": "#d8cbb8",
+ "pressed": "#c6b6a0",
+ "overlay": "rgba(235, 228, 214, 0.40)",
+ "scrim": "rgba(26, 18, 12, 0.42)"
+ }
}
-ANSI_KEYS = [
+ANSI_KEY_NAMES = [
"surface0",
"error",
"sage",
@@ -131,11 +184,11 @@
"lavender",
"muted",
"subtle",
- "#e9a092",
- "#75a579",
+ "error_bright",
+ "sage_bright",
"warning",
- "#579ba4",
- "#846fc5",
- "#6fa5a5",
- "text",
+ "diagnostic_bright",
+ "lavender_bright",
+ "focus_bright",
+ "text"
]
diff --git a/src/dreamcoder_theme/renderers_extra_nvim.py b/src/dreamcoder_theme/renderers_extra_nvim.py
index 1bb4156..2d895ff 100644
--- a/src/dreamcoder_theme/renderers_extra_nvim.py
+++ b/src/dreamcoder_theme/renderers_extra_nvim.py
@@ -102,9 +102,8 @@ def hl(name: str, **kwargs: Any) -> str:
normal_bg = bg if invert else "none"
text = c["text"]
fg = text
- # Use surface1 for selection background in all modes - better visibility
- sel_bg = c["surface1"]
- sel_fg = c["text"]
+ sel_bg = c["selection_bg"]
+ sel_fg = c["selection_fg"]
lines = [
f"""-- ========================================================
diff --git a/src/dreamcoder_theme/renderers_extra_nvim_lsp.py b/src/dreamcoder_theme/renderers_extra_nvim_lsp.py
index b16f401..d762d25 100644
--- a/src/dreamcoder_theme/renderers_extra_nvim_lsp.py
+++ b/src/dreamcoder_theme/renderers_extra_nvim_lsp.py
@@ -18,9 +18,9 @@ def nvim_lsp_groups(
hl("@markup.heading", fg=c["accent"], bold=True),
hl("@markup.quote", fg=c["comment"], italic=True),
hl("@markup.math", fg=c["lavender"]),
- hl("@markup.link", fg=c["diagnostic"], underline=True),
- hl("@markup.link.label", fg=c["diagnostic"], underline=True),
- hl("@markup.link.url", fg=c["subtle"], underline=True),
+ hl("@markup.link", fg=c["link"], underline=True),
+ hl("@markup.link.label", fg=c["link"], underline=True),
+ hl("@markup.link.url", fg=c["link_hover"], underline=True),
hl("@markup.raw", fg=c["sage"]),
hl("@markup.list", fg=c["accent"]),
hl("@diff.plus", fg=c["sage"]),
diff --git a/src/dreamcoder_theme/renderers_extra_obsidian.py b/src/dreamcoder_theme/renderers_extra_obsidian.py
index fbc4e1b..a343961 100644
--- a/src/dreamcoder_theme/renderers_extra_obsidian.py
+++ b/src/dreamcoder_theme/renderers_extra_obsidian.py
@@ -40,16 +40,16 @@ def obsidian_content(c: dict[str, str]) -> str:
--text-accent-hover: {c["accent_2"]};
--text-error: {c["error"]};
--text-warning: {c["warning"]};
- --text-success: {c["sage"]};
- --text-selection: {mix(c["surface1"], bg, 0.5)};
- --text-on-accent: {c["bg"]};
+ --text-success: {c["success"]};
+ --text-selection: {c["selection_bg"]};
+ --text-on-accent: {c["on_accent"]};
/* Interactive */
--interactive-normal: {c["surface1"]};
- --interactive-hover: {c["surface2"]};
+ --interactive-hover: {c["hover"]};
--interactive-accent: {c["accent"]};
--interactive-accent-hover: {c["accent_2"]};
- --interactive-success: {c["sage"]};
+ --interactive-success: {c["success"]};
/* Scrollbar */
--scrollbar-bg: transparent;
@@ -70,18 +70,18 @@ def obsidian_content(c: dict[str, str]) -> str:
--code-background: {c["surface0"]};
/* Heading */
- --h1-color: {c["accent"]};
- --h2-color: {c["accent"]};
+ --h1-color: {c["text_heading"]};
+ --h2-color: {c["text_heading"]};
--h3-color: {c["accent_2"]};
--h4-color: {c["diagnostic"]};
--h5-color: {c["muted"]};
--h6-color: {c["subtle"]};
/* Link */
- --link-color: {c["accent"]};
- --link-color-hover: {c["accent_2"]};
- --link-external-color: {c["diagnostic"]};
- --link-external-color-hover: {c["accent_2"]};
+ --link-color: {c["link"]};
+ --link-color-hover: {c["link_hover"]};
+ --link-external-color: {c["info"]};
+ --link-external-color-hover: {c["link_hover"]};
/* Checkbox */
--checkbox-color: {c["accent"]};
diff --git a/src/dreamcoder_theme/renderers_ghostty_warp.py b/src/dreamcoder_theme/renderers_ghostty_warp.py
index 4308d09..b71c58d 100644
--- a/src/dreamcoder_theme/renderers_ghostty_warp.py
+++ b/src/dreamcoder_theme/renderers_ghostty_warp.py
@@ -6,10 +6,9 @@
def ghostty_content(c: dict[str, str]) -> str:
- # Use surface1 for selection background in all modes - better visibility
is_dark = c["details"] == "darker"
- sel_bg = c["surface1"]
- sel_fg = c["text"]
+ sel_bg = c["selection_bg"]
+ sel_fg = c["selection_fg"]
opacity = "0.76" if is_dark else "0.96"
blur = "true" if is_dark else "false"
lines = [
@@ -17,7 +16,7 @@ def ghostty_content(c: dict[str, str]) -> str:
f"background = {c['bg']}",
f"foreground = {c['text']}",
f"cursor-color = {c['accent']}",
- f"cursor-text = {c['bg']}",
+ f"cursor-text = {c['on_accent']}",
f"selection-background = {sel_bg}",
f"selection-foreground = {sel_fg}",
f"background-opacity = {opacity}",
diff --git a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
index f5bffcf..26da750 100644
--- a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
+++ b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py
@@ -32,12 +32,12 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]:
"surface_variant": h(c["surface1"]),
"surface_tint": h(c["accent"]),
# On-colors
- "on_background": h(c["text"]),
- "on_surface": h(c["text"]),
+ "on_background": h(c["on_surface"]),
+ "on_surface": h(c["on_surface"]),
"on_surface_variant": h(c["muted"]),
# Primary
"primary": h(c["accent"]),
- "on_primary": h(c["bg"]),
+ "on_primary": h(c["on_accent"]),
"primary_container": h(c["surface1"]),
"on_primary_container": h(c["text"]),
"primary_fixed": h(c["accent"]),
@@ -47,7 +47,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]:
"inverse_primary": h(c["accent"]),
# Secondary
"secondary": h(c["accent_2"]),
- "on_secondary": h(c["bg"]),
+ "on_secondary": h(c["on_accent"]),
"secondary_container": h(c["surface1"]),
"on_secondary_container": h(c["text"]),
"secondary_fixed": h(c["accent_2"]),
@@ -56,7 +56,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]:
"on_secondary_fixed_variant": h(c["accent_2"]),
# Tertiary
"tertiary": h(c["diagnostic"]),
- "on_tertiary": h(c["bg"]),
+ "on_tertiary": h(c["on_accent"]),
"tertiary_container": h(c["surface0"]),
"on_tertiary_container": h(c["text"]),
"tertiary_fixed": h(c["diagnostic"]),
@@ -65,7 +65,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]:
"on_tertiary_fixed_variant": h(c["focus"]),
# Error
"error": h(c["error"]),
- "on_error": h(c["bg"]),
+ "on_error": h(c["on_error"]),
"error_container": h(c["surface0"]),
"on_error_container": h(c["text"]),
# Outline
@@ -115,17 +115,25 @@ def waybar_content(c: dict[str, str]) -> str:
@define-color bg {c["bg"]};
@define-color bg-soft {c["bg_soft"]};
@define-color surface {c["surface0"]};
+@define-color surface-2 {c["surface2"]};
+@define-color surface-3 {c["surface3"]};
@define-color text {c["text"]};
+@define-color text-heading {c["text_heading"]};
@define-color muted {c["muted"]};
+@define-color subtle {c["subtle"]};
+@define-color comment {c["comment"]};
@define-color border {c["border"]};
@define-color border-ui {c["border_ui"]};
@define-color focus {c["focus"]};
@define-color accent {c["accent"]};
@define-color accent-2 {c["accent_2"]};
@define-color diagnostic {c["diagnostic"]};
+@define-color lavender {c["lavender"]};
@define-color error {c["error"]};
-@define-color success {c["sage"]};
+@define-color success {c["success"]};
@define-color warning {c["warning"]};
+@define-color link {c["link"]};
+@define-color link-hover {c["link_hover"]};
window#waybar {{
background: {c["panel_rgba"]};
@@ -285,15 +293,15 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
primary: {c["accent"]};
primary-fixed: {c["accent"]};
primary-fixed-dim: {c["accent_2"]};
- on-primary: {c["bg"]};
- on-primary-fixed: {c["bg"]};
+ on-primary: {c["on_accent"]};
+ on-primary-fixed: {c["on_accent"]};
on-primary-fixed-variant: {c["accent"]};
primary-container: {c["surface1"]};
on-primary-container: {c["text"]};
secondary: {c["accent_2"]};
secondary-fixed: {c["accent_2"]};
secondary-fixed-dim: {c["accent_2"]};
- on-secondary: {c["bg"]};
+ on-secondary: {c["on_accent"]};
on-secondary-fixed: {c["bg"]};
on-secondary-fixed-variant: {c["accent_2"]};
secondary-container: {c["surface1"]};
@@ -307,7 +315,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]:
tertiary-container: {c["surface0"]};
on-tertiary-container: {c["text"]};
error: {c["error"]};
- on-error: {c["bg"]};
+ on-error: {c["on_error"]};
error-container: {c["surface0"]};
on-error-container: {c["text"]};
surface: {c["bg"]};
diff --git a/src/dreamcoder_theme/renderers_kitty.py b/src/dreamcoder_theme/renderers_kitty.py
index 8534aa9..e6e6e72 100644
--- a/src/dreamcoder_theme/renderers_kitty.py
+++ b/src/dreamcoder_theme/renderers_kitty.py
@@ -7,9 +7,8 @@
def kitty_content(c: dict[str, str]) -> str:
p = ansi(c)
- # Use surface1 for selection background in all modes - better visibility
- sel_fg = c["text"]
- sel_bg = c["surface1"]
+ sel_fg = c["selection_fg"]
+ sel_bg = c["selection_bg"]
return f"""# ==========================================================
# {c["name"]}
# ==========================================================
@@ -19,15 +18,15 @@ def kitty_content(c: dict[str, str]) -> str:
background {c["bg"]}
selection_foreground {sel_fg}
selection_background {sel_bg}
-url_color {c["diagnostic"]}
+url_color {c["link"]}
cursor {c["accent"]}
-cursor_text_color {c["bg"]}
+cursor_text_color {c["on_accent"]}
cursor_shape block
cursor_blink_interval 0.5
cursor_stop_blinking_after 15.0
-active_tab_foreground {c["bg"]}
+active_tab_foreground {c["on_accent"]}
active_tab_background {c["accent"]}
inactive_tab_foreground {c["muted"]}
inactive_tab_background {c["bg"]}
@@ -56,11 +55,11 @@ def kitty_content(c: dict[str, str]) -> str:
color16 {c["accent_2"]}
color17 {c["error"]}
-mark1_foreground {c["bg"]}
+mark1_foreground {c["on_accent"]}
mark1_background {c["accent"]}
-mark2_foreground {c["bg"]}
+mark2_foreground {c["on_surface"]}
mark2_background {c["diagnostic"]}
-mark3_foreground {c["bg"]}
+mark3_foreground {c["on_surface"]}
mark3_background {c["mauve"]}
"""
diff --git a/src/dreamcoder_theme/renderers_opencode.py b/src/dreamcoder_theme/renderers_opencode.py
index 91153d7..c47ec09 100644
--- a/src/dreamcoder_theme/renderers_opencode.py
+++ b/src/dreamcoder_theme/renderers_opencode.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from .palette import guard, mix
+from .palette import guard, mix, surface_guard
def opencode_tokens(c: dict[str, str]) -> dict[str, str]:
@@ -14,16 +14,8 @@ def opencode_tokens(c: dict[str, str]) -> dict[str, str]:
function = guard(c["diagnostic"], c["bg"], mode_name, minimum=syntax_min)
type_color = guard(c["lavender"], c["bg"], mode_name, minimum=syntax_min)
constant = guard(mix(c["accent_2"], c["mauve"], 0.24), c["bg"], mode_name, minimum=syntax_min)
- # Selection: light bg with dark text for dark mode (accent is light);
- # For light mode, accent is dark so syntax colors (also dark) become
- # invisible on it. Use bg (lightest non-white) instead so ALL syntax
- # colors remain legible even if OpenCode doesn't swap them on selection.
- if mode_name == "dark":
- sel_bg = c["accent"]
- sel_fg = c["bg"]
- else:
- sel_bg = c["bg"]
- sel_fg = c["text"]
+ sel_bg = c["selection_bg"]
+ sel_fg = c["selection_fg"]
return {
"keyword": keyword,
"function": function,
@@ -72,7 +64,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
# Mode-aware surface formulas
if mode_name == "dark":
element_bg = c["surface1"]
- hover_bg = c["surface2"]
+ hover_bg = c["hover"]
line_bg = mix(c["bg"], c["surface0"], 0.50)
code_bg = mix(c["surface0"], c["surface1"], 0.30)
assistant_bg = mix(c["bg"], c["diagnostic"], 0.12)
@@ -83,7 +75,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
mix_base = c["border_ui"]
else:
element_bg = mix(c["bg_soft"], c["surface1"], 0.4)
- hover_bg = c["surface2"]
+ hover_bg = c["hover"]
line_bg = c["bg_soft"]
code_bg = mix(c["surface1"], c["border_ui"], 0.12)
assistant_bg = mix(c["bg"], c["diagnostic"], 0.12)
@@ -93,13 +85,18 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
inline_code_bg = mix(c["sage"], c["bg"], 0.18)
mix_base = c["bg"]
- added_bg = mix(c["sage"], mix_base, 0.35)
- removed_bg = mix(c["error"], mix_base, 0.35)
- hunk_bg = mix(c["lavender"], mix_base, 0.45)
+ diff_mix = 0.35 if mode_name == "dark" else 0.58
+ added_bg = surface_guard(mix(c["sage"], mix_base, diff_mix), c["bg"], mode_name)
+ removed_bg = surface_guard(mix(c["error"], mix_base, diff_mix), c["bg"], mode_name)
+ hunk_bg = surface_guard(
+ mix(c["lavender"], mix_base, 0.45 if mode_name == "dark" else 0.62),
+ c["bg"],
+ mode_name,
+ )
assistant = guard(mix(c["diagnostic"], c["text"], 0.18), c["bg"], mode_name)
user = guard(mix(c["accent"], c["text"], 0.15), c["bg"], mode_name)
background = "none" if transparent_background else c["bg"]
- return f'''{{
+ return f"""{{
"$schema": "https://opencode.ai/theme.json",
"defs": {{
"dreamBackground": "{c["bg"]}",
@@ -229,4 +226,4 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
"terminalBrightWhite": "{c["text"]}"
}}
}}
-'''
+"""
diff --git a/src/dreamcoder_theme/renderers_starship.py b/src/dreamcoder_theme/renderers_starship.py
index 7d9ff87..cd0672a 100644
--- a/src/dreamcoder_theme/renderers_starship.py
+++ b/src/dreamcoder_theme/renderers_starship.py
@@ -16,27 +16,33 @@ def starship_content(c: dict[str, str]) -> str:
prom_text = guard(c["prompt_text"], prom_s0, mode) # text on darkest surface
prom_muted = guard(c["prompt_muted"], c["bg"], mode)
error = guard(c["error"], c["bg"], mode)
+ warning = guard(c["warning"], c["bg"], mode)
+ focus_col = c["focus"]
+ diag = c["diagnostic"]
+ lavender_col = c["lavender"]
+ mauve_col = c["mauve"]
return f'''# ========================================================
# {c["name"]} — Starship prompt
# ========================================================
# Modern two-line layout with powerline segments.
-# Line 1: context (directory, git)
+# 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"
command_timeout = 500
format = """
-[](fg:prompt_surface0)\\
+[\\uE0B6](fg:prompt_surface0)\\
$username\\
-[](bg:prompt_surface1 fg:prompt_surface0)\\
+[\\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\\
$directory\\
-[](bg:prompt_accent fg:prompt_surface1)\\
+[\\uE0B0](bg:prompt_accent fg:prompt_surface1)\\
$git_branch\\
$git_status\\
-[](fg:prompt_accent)\\
+[\\uE0B4](fg:prompt_accent)\\
$fill\\
$cmd_duration\\
$time
@@ -57,42 +63,53 @@ def starship_content(c: dict[str, str]) -> str:
prompt_accent = "{prom_acc}"
prompt_accent_2 = "{c["prompt_accent_2"]}"
sage = "{c["sage"]}"
-diagnostic = "{c["diagnostic"]}"
-lavender = "{c["lavender"]}"
-mauve = "{c["mauve"]}"
+diagnostic = "{diag}"
+lavender = "{lavender_col}"
+mauve = "{mauve_col}"
error = "{error}"
+warning = "{warning}"
+border = "{c["border_ui"]}"
+focus = "{focus_col}"
+link = "{c["link"]}"
[username]
show_always = true
style_user = "bg:prompt_surface0 fg:prompt_text bold"
style_root = "bg:prompt_surface0 fg:error bold"
-format = "[ $user ]($style)"
+format = "[ \uf007 $user ]($style)"
[directory]
style = "bg:prompt_surface1 fg:prompt_text bold"
-format = "[ $path ]($style)"
+format = "[ $path ]($style)"
truncation_length = 2
truncate_to_repo = true
home_symbol = ""
[git_branch]
-symbol = ""
+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 = "${{count}} "
-ahead = "⇡${{count}} "
-behind = "⇣${{count}} "
-diverged = "⇕⇡${{ahead_count}}⇣${{behind_count}} "
+conflicted = "\\ue727${{count}} "
+ahead = "\\u21E1${{count}} "
+behind = "\\u21E3${{count}} "
+diverged = "\\u2195\\u21E1${{ahead_count}}\\u21E3${{behind_count}} "
untracked = "?${{count}} "
-stashed = "${{count}} "
+stashed = "\\uf0CF${{count}} "
modified = "~${{count}} "
staged = "+${{count}} "
-renamed = "»${{count}} "
-deleted = "✘${{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 = " "
@@ -100,7 +117,7 @@ def starship_content(c: dict[str, str]) -> str:
[cmd_duration]
min_time = 2500
style = "fg:prompt_muted"
-format = "[ $duration ]($style)"
+format = "[ \\uf552 $duration ]($style)"
[time]
disabled = false
@@ -108,39 +125,45 @@ def starship_content(c: dict[str, str]) -> str:
style = "fg:prompt_muted"
[character]
-success_symbol = "[❯](bold fg:prompt_accent)"
-error_symbol = "[❯](bold fg:error)"
-vimcmd_symbol = "[❮](bold fg:sage)"
+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 = ""
+symbol = "\\uF5EF"
style = "fg:prompt_accent bold"
format = "[ $symbol $version]($style)"
[nodejs]
-symbol = ""
+symbol = "\\uE718"
style = "fg:sage bold"
format = "[ $symbol $version]($style)"
[python]
-symbol = ""
-style = "fg:diagnostic bold"
+symbol = "\\uE73C"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[golang]
-symbol = ""
-style = "fg:lavender bold"
+symbol = "\\uE627"
+style = "fg:diag bold"
format = "[ $symbol $version]($style)"
[rust]
-symbol = ""
+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 = ""
-style = "fg:diagnostic bold"
+symbol = "\\uf308"
+style = "fg:diag bold"
format = "[ $symbol $context]($style)"
only_with_files = true
'''
diff --git a/src/dreamcoder_theme/sync.py b/src/dreamcoder_theme/sync.py
index ba0022b..3303264 100644
--- a/src/dreamcoder_theme/sync.py
+++ b/src/dreamcoder_theme/sync.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+import subprocess
+import sys
from typing import Any
from .palette import adaptive_palette, load_variants
@@ -376,6 +378,9 @@ def print_summary(
def main() -> None:
+ gen = ROOT / "scripts" / "generate-palette-tokens.py"
+ if gen.is_file():
+ subprocess.run([sys.executable, str(gen)], check=True)
paths = theme_paths()
mode = theme_mode()
variants = load_variants(DEFAULT_VARIANTS, paths.tokens_file)
diff --git a/tests/test_dreamcoder_theme_quality.py b/tests/test_dreamcoder_theme_quality.py
index 7b65508..2fd85bd 100644
--- a/tests/test_dreamcoder_theme_quality.py
+++ b/tests/test_dreamcoder_theme_quality.py
@@ -41,13 +41,22 @@ def test_light_has_stronger_editor_readability_tiers(self):
light = self.modes["light"]
self.assertEqual(light["surface0"], "#fff7ea")
self.assertEqual(light["surface2"], "#c8ad89")
- # Verify subtle has sufficient contrast against background
self.assertGreaterEqual(contrast(light["subtle"], light["bg"]), 4.5)
self.assertGreaterEqual(contrast(light["comment"], light["bg"]), 4.5)
- def test_light_selection_uses_inverted_high_contrast_pair(self):
+ def test_light_selection_uses_explicit_pair(self):
light = self.modes["light"]
- self.assertEqual(light["selection"], light["text"])
+ self.assertEqual(light["selection_bg"], "#decbb1")
+ self.assertEqual(light["selection_fg"], light["text"])
+ self.assertGreaterEqual(contrast(light["selection_fg"], light["selection_bg"]), 7.0)
+
+ def test_dark_has_text_heading_and_surface3(self):
+ dark = self.modes["dark"]
+ self.assertIn("text_heading", dark)
+ self.assertIn("surface3", dark)
+ self.assertGreater(
+ contrast(dark["text_heading"], dark["bg"]), contrast(dark["text"], dark["bg"])
+ )
if __name__ == "__main__":
diff --git a/tests/test_terminal_readability.py b/tests/test_terminal_readability.py
index dd642ac..7dc7ab6 100644
--- a/tests/test_terminal_readability.py
+++ b/tests/test_terminal_readability.py
@@ -33,12 +33,12 @@ def test_cursor_and_selection_pairs_are_terminal_readable(self):
cursor_min = self.guardrails["minimum_terminal_cursor_contrast"]
selection_min = self.guardrails["minimum_terminal_selection_contrast"]
for mode, palette in self.modes.items():
- invert = palette.get("details") == "lighter"
- sel_fg = palette["bg"] if invert else palette["text"]
- sel_bg = palette["text"] if invert else palette["selection"]
with self.subTest(mode=mode):
self.assertGreaterEqual(contrast(palette["accent"], palette["bg"]), cursor_min)
- self.assertGreaterEqual(contrast(sel_fg, sel_bg), selection_min)
+ self.assertGreaterEqual(
+ contrast(palette["selection_fg"], palette["selection_bg"]),
+ selection_min,
+ )
if __name__ == "__main__":
diff --git a/tests/test_token_parity.py b/tests/test_token_parity.py
new file mode 100644
index 0000000..b92dbcb
--- /dev/null
+++ b/tests/test_token_parity.py
@@ -0,0 +1,52 @@
+import json
+import sys
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT / "src"))
+
+from dreamcoder_theme.palette import contrast
+from dreamcoder_theme.palette_tokens import ANSI_KEY_NAMES
+
+TOKENS = ROOT / "themes" / "dreamcoder" / "tokens.json"
+
+
+class TokenParityTest(unittest.TestCase):
+ def setUp(self):
+ self.modes = json.loads(TOKENS.read_text())["modes"]
+
+ def test_dark_and_light_share_semantic_keys(self):
+ dark_keys = {k for k in self.modes["dark"] if k not in {"name", "details"}}
+ light_keys = {k for k in self.modes["light"] if k not in {"name", "details"}}
+ self.assertEqual(dark_keys, light_keys)
+
+ def test_surface_ladder_increases_contrast_from_bg(self):
+ for mode in ("dark", "light"):
+ palette = self.modes[mode]
+ bg = palette["bg"]
+ steps = [
+ palette[k] for k in ("bg_soft", "surface0", "surface1", "surface2", "surface3")
+ ]
+ ratios = [contrast(step, bg) for step in steps]
+ with self.subTest(mode=mode):
+ self.assertTrue(all(r >= 1.02 for r in ratios))
+
+ def test_text_hierarchy_is_ordered(self):
+ for mode in ("dark", "light"):
+ palette = self.modes[mode]
+ bg = palette["bg"]
+ text_ratio = contrast(palette["text"], bg)
+ muted_ratio = contrast(palette["muted"], bg)
+ subtle_ratio = contrast(palette["subtle"], bg)
+ with self.subTest(mode=mode):
+ self.assertGreater(text_ratio, muted_ratio)
+ self.assertGreater(muted_ratio, subtle_ratio)
+
+ def test_ansi_keys_are_token_names_only(self):
+ for key in ANSI_KEY_NAMES:
+ self.assertFalse(key.startswith("#"), f"literal hex in ANSI_KEY_NAMES: {key}")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/themes/dreamcoder/hypr-colors-light.conf b/themes/dreamcoder/hypr-colors-light.conf
index d133315..6012b99 100644
--- a/themes/dreamcoder/hypr-colors-light.conf
+++ b/themes/dreamcoder/hypr-colors-light.conf
@@ -5,19 +5,19 @@ $inverse_on_surface = rgba(fff7eaff)
$inverse_primary = rgba(824f16ff)
$inverse_surface = rgba(17120dff)
$on_background = rgba(17120dff)
-$on_error = rgba(f3eadcff)
+$on_error = rgba(fff7eaff)
$on_error_container = rgba(17120dff)
-$on_primary = rgba(f3eadcff)
+$on_primary = rgba(fff7eaff)
$on_primary_container = rgba(17120dff)
$on_primary_fixed = rgba(f3eadcff)
$on_primary_fixed_variant = rgba(824f16ff)
-$on_secondary = rgba(f3eadcff)
+$on_secondary = rgba(fff7eaff)
$on_secondary_container = rgba(17120dff)
$on_secondary_fixed = rgba(f3eadcff)
$on_secondary_fixed_variant = rgba(a7471cff)
$on_surface = rgba(17120dff)
$on_surface_variant = rgba(352e22ff)
-$on_tertiary = rgba(f3eadcff)
+$on_tertiary = rgba(fff7eaff)
$on_tertiary_container = rgba(17120dff)
$on_tertiary_fixed = rgba(f3eadcff)
$on_tertiary_fixed_variant = rgba(0f6570ff)
diff --git a/themes/dreamcoder/hypr-colors-light.lua b/themes/dreamcoder/hypr-colors-light.lua
index 915d21c..b96d2fd 100644
--- a/themes/dreamcoder/hypr-colors-light.lua
+++ b/themes/dreamcoder/hypr-colors-light.lua
@@ -5,19 +5,19 @@ inverse_on_surface = "rgba(fff7eaff)"
inverse_primary = "rgba(824f16ff)"
inverse_surface = "rgba(17120dff)"
on_background = "rgba(17120dff)"
-on_error = "rgba(f3eadcff)"
+on_error = "rgba(fff7eaff)"
on_error_container = "rgba(17120dff)"
-on_primary = "rgba(f3eadcff)"
+on_primary = "rgba(fff7eaff)"
on_primary_container = "rgba(17120dff)"
on_primary_fixed = "rgba(f3eadcff)"
on_primary_fixed_variant = "rgba(824f16ff)"
-on_secondary = "rgba(f3eadcff)"
+on_secondary = "rgba(fff7eaff)"
on_secondary_container = "rgba(17120dff)"
on_secondary_fixed = "rgba(f3eadcff)"
on_secondary_fixed_variant = "rgba(a7471cff)"
on_surface = "rgba(17120dff)"
on_surface_variant = "rgba(352e22ff)"
-on_tertiary = "rgba(f3eadcff)"
+on_tertiary = "rgba(fff7eaff)"
on_tertiary_container = "rgba(17120dff)"
on_tertiary_fixed = "rgba(f3eadcff)"
on_tertiary_fixed_variant = "rgba(0f6570ff)"
diff --git a/themes/dreamcoder/obsidian-dreamcoder-dark.css b/themes/dreamcoder/obsidian-dreamcoder-dark.css
index ee8db57..c81c608 100644
--- a/themes/dreamcoder/obsidian-dreamcoder-dark.css
+++ b/themes/dreamcoder/obsidian-dreamcoder-dark.css
@@ -26,12 +26,12 @@
--text-error: #ed8a7a;
--text-warning: #e8b866;
--text-success: #4db35f;
- --text-selection: #1e1914;
+ --text-selection: #2b231b;
--text-on-accent: #100f0d;
/* Interactive */
--interactive-normal: #2b231b;
- --interactive-hover: #392e21;
+ --interactive-hover: #2b231b;
--interactive-accent: #d99555;
--interactive-accent-hover: #c96a45;
--interactive-success: #4db35f;
@@ -55,8 +55,8 @@
--code-background: #201b16;
/* Heading */
- --h1-color: #d99555;
- --h2-color: #d99555;
+ --h1-color: #f4ecdd;
+ --h2-color: #f4ecdd;
--h3-color: #c96a45;
--h4-color: #5f95ca;
--h5-color: #c7b9aa;
diff --git a/themes/dreamcoder/obsidian-dreamcoder-light.css b/themes/dreamcoder/obsidian-dreamcoder-light.css
index 280deb7..60fb864 100644
--- a/themes/dreamcoder/obsidian-dreamcoder-light.css
+++ b/themes/dreamcoder/obsidian-dreamcoder-light.css
@@ -26,12 +26,12 @@
--text-error: #842f24;
--text-warning: #654300;
--text-success: #3d723d;
- --text-selection: #e8dac6;
- --text-on-accent: #f3eadc;
+ --text-selection: #decbb1;
+ --text-on-accent: #fff7ea;
/* Interactive */
--interactive-normal: #decbb1;
- --interactive-hover: #c8ad89;
+ --interactive-hover: #decbb1;
--interactive-accent: #824f16;
--interactive-accent-hover: #a7471c;
--interactive-success: #3d723d;
@@ -55,8 +55,8 @@
--code-background: #fff7ea;
/* Heading */
- --h1-color: #824f16;
- --h2-color: #824f16;
+ --h1-color: #100c07;
+ --h2-color: #100c07;
--h3-color: #a7471c;
--h4-color: #0d4a68;
--h5-color: #352e22;
diff --git a/themes/dreamcoder/obsidian-dreamcoder.css b/themes/dreamcoder/obsidian-dreamcoder.css
index 280deb7..60fb864 100644
--- a/themes/dreamcoder/obsidian-dreamcoder.css
+++ b/themes/dreamcoder/obsidian-dreamcoder.css
@@ -26,12 +26,12 @@
--text-error: #842f24;
--text-warning: #654300;
--text-success: #3d723d;
- --text-selection: #e8dac6;
- --text-on-accent: #f3eadc;
+ --text-selection: #decbb1;
+ --text-on-accent: #fff7ea;
/* Interactive */
--interactive-normal: #decbb1;
- --interactive-hover: #c8ad89;
+ --interactive-hover: #decbb1;
--interactive-accent: #824f16;
--interactive-accent-hover: #a7471c;
--interactive-success: #3d723d;
@@ -55,8 +55,8 @@
--code-background: #fff7ea;
/* Heading */
- --h1-color: #824f16;
- --h2-color: #824f16;
+ --h1-color: #100c07;
+ --h2-color: #100c07;
--h3-color: #a7471c;
--h4-color: #0d4a68;
--h5-color: #352e22;
diff --git a/themes/dreamcoder/tokens.json b/themes/dreamcoder/tokens.json
index db02e55..3a84048 100644
--- a/themes/dreamcoder/tokens.json
+++ b/themes/dreamcoder/tokens.json
@@ -24,7 +24,7 @@
"accent": "#d99555",
"accent_2": "#c96a45",
"diagnostic": "#5f95ca",
- "selection": "#3d3028",
+ "selection": "#2b231b",
"panel_rgba": "rgba(16, 15, 13, 0.78)",
"module_rgba": "rgba(232, 223, 208, 0.08)",
"active_rgba": "rgba(217, 149, 85, 0.24)",
@@ -42,7 +42,22 @@
"lavender": "#d4b4e6",
"mauve": "#e29cb4",
"error": "#ed8a7a",
- "warning": "#e8b866"
+ "warning": "#e8b866",
+ "success": "#4db35f",
+ "info": "#5f95ca",
+ "selection_bg": "#2b231b",
+ "selection_fg": "#e8dfd0",
+ "on_surface": "#e8dfd0",
+ "on_accent": "#100f0d",
+ "on_error": "#100f0d",
+ "on_focus": "#100f0d",
+ "link": "#d99555",
+ "link_hover": "#c96a45",
+ "disabled": "#877e73",
+ "hover": "#2b231b",
+ "pressed": "#392e21",
+ "overlay": "rgba(16, 15, 13, 0.52)",
+ "scrim": "rgba(0, 0, 0, 0.58)"
},
"light": {
"name": "Dreamcoder Light",
@@ -67,7 +82,7 @@
"mauve": "#7d3e64",
"error": "#842f24",
"warning": "#654300",
- "selection": "#17120d",
+ "selection": "#decbb1",
"panel_rgba": "rgba(243, 234, 220, 0.96)",
"module_rgba": "rgba(222, 203, 177, 0.80)",
"active_rgba": "rgba(130, 79, 22, 0.34)",
@@ -80,7 +95,24 @@
"prompt_text": "#20150c",
"prompt_muted": "#53402e",
"prompt_accent": "#8a5520",
- "prompt_accent_2": "#a7471c"
+ "prompt_accent_2": "#a7471c",
+ "success": "#3d723d",
+ "info": "#0d4a68",
+ "text_heading": "#100c07",
+ "surface3": "#b89d7a",
+ "selection_bg": "#decbb1",
+ "selection_fg": "#17120d",
+ "on_surface": "#17120d",
+ "on_accent": "#fff7ea",
+ "on_error": "#fff7ea",
+ "on_focus": "#fff7ea",
+ "link": "#824f16",
+ "link_hover": "#a7471c",
+ "disabled": "#787063",
+ "hover": "#decbb1",
+ "pressed": "#c8ad89",
+ "overlay": "rgba(243, 234, 220, 0.40)",
+ "scrim": "rgba(26, 18, 12, 0.42)"
},
"dusk": {
"name": "Dreamcoder Dusk",
@@ -105,7 +137,7 @@
"mauve": "#784762",
"error": "#773126",
"warning": "#604000",
- "selection": "#1a1713",
+ "selection": "#d8cbb8",
"panel_rgba": "rgba(235, 228, 214, 0.88)",
"module_rgba": "rgba(26, 23, 19, 0.08)",
"active_rgba": "rgba(138, 85, 32, 0.22)",
@@ -118,7 +150,24 @@
"prompt_text": "#261c14",
"prompt_muted": "#574939",
"prompt_accent": "#965f25",
- "prompt_accent_2": "#96411e"
+ "prompt_accent_2": "#96411e",
+ "success": "#466b41",
+ "info": "#104b67",
+ "text_heading": "#13100c",
+ "surface3": "#b6a691",
+ "selection_bg": "#d8cbb8",
+ "selection_fg": "#1a1713",
+ "on_surface": "#1a1713",
+ "on_accent": "#f1eadf",
+ "on_error": "#f1eadf",
+ "on_focus": "#f1eadf",
+ "link": "#8a5520",
+ "link_hover": "#96411e",
+ "disabled": "#847c71",
+ "hover": "#d8cbb8",
+ "pressed": "#c6b6a0",
+ "overlay": "rgba(235, 228, 214, 0.40)",
+ "scrim": "rgba(26, 18, 12, 0.42)"
}
},
"guardrails": {
@@ -135,6 +184,9 @@
"canonical_opencode_theme": "dreamcoder",
"minimum_terminal_ansi_contrast": 4.5,
"minimum_terminal_cursor_contrast": 4.5,
- "minimum_terminal_selection_contrast": 7.0
+ "minimum_terminal_selection_contrast": 7.0,
+ "minimum_apca_on_accent": 54,
+ "minimum_apca_heading_light": 60,
+ "minimum_apca_heading_dark": 45
}
}
diff --git a/themes/dreamcoder/tokens.schema.json b/themes/dreamcoder/tokens.schema.json
index 879cf76..4ac7c01 100644
--- a/themes/dreamcoder/tokens.schema.json
+++ b/themes/dreamcoder/tokens.schema.json
@@ -2,32 +2,15 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Dreamcoder OS design tokens",
"type": "object",
- "required": [
- "name",
- "version",
- "modes",
- "guardrails"
- ],
+ "required": ["name", "version", "modes", "guardrails"],
"properties": {
- "name": {
- "const": "Dreamcoder OS"
- },
- "version": {
- "type": "integer",
- "minimum": 1
- },
- "description": {
- "type": "string"
- },
+ "name": { "const": "Dreamcoder OS" },
+ "version": { "type": "integer", "minimum": 1 },
+ "description": { "type": "string" },
"modes": {
"type": "object",
- "required": [
- "dark",
- "light"
- ],
- "additionalProperties": {
- "$ref": "#/$defs/palette"
- }
+ "required": ["dark", "light"],
+ "additionalProperties": { "$ref": "#/$defs/palette" }
},
"guardrails": {
"type": "object",
@@ -37,264 +20,157 @@
"preferred_main_text_contrast",
"minimum_terminal_ansi_contrast",
"minimum_terminal_cursor_contrast",
- "minimum_terminal_selection_contrast"
+ "minimum_terminal_selection_contrast",
+ "minimum_apca_body",
+ "minimum_apca_body_dark",
+ "minimum_apca_quiet",
+ "minimum_apca_ui",
+ "minimum_apca_ui_dark",
+ "minimum_apca_on_accent"
],
"properties": {
- "canonical_opencode_theme": {
- "const": "dreamcoder"
- },
- "avoid_pure_black_white": {
- "type": "boolean"
- },
- "minimum_text_contrast": {
- "type": "number",
- "minimum": 4.5
- },
- "preferred_main_text_contrast": {
- "type": "number",
- "minimum": 7
- },
- "light_hours_default": {
- "type": "string"
- },
- "dusk_hours_default": {
- "type": "string"
- },
- "minimum_apca_body": {
- "type": "number",
- "minimum": 60
- },
- "minimum_apca_quiet": {
- "type": "number",
- "minimum": 45
- },
- "minimum_apca_ui": {
- "type": "number",
- "minimum": 45
- },
- "minimum_terminal_ansi_contrast": {
- "type": "number",
- "minimum": 4.5
- },
- "minimum_terminal_cursor_contrast": {
- "type": "number",
- "minimum": 4.5
- },
- "minimum_terminal_selection_contrast": {
- "type": "number",
- "minimum": 7
- }
+ "canonical_opencode_theme": { "const": "dreamcoder" },
+ "avoid_pure_black_white": { "type": "boolean" },
+ "minimum_text_contrast": { "type": "number", "minimum": 4.5 },
+ "preferred_main_text_contrast": { "type": "number", "minimum": 7 },
+ "minimum_apca_body": { "type": "number", "minimum": 60 },
+ "minimum_apca_body_dark": { "type": "number", "minimum": 45 },
+ "minimum_apca_heading_light": { "type": "number", "minimum": 45 },
+ "minimum_apca_heading_dark": { "type": "number", "minimum": 40 },
+ "minimum_apca_quiet": { "type": "number", "minimum": 44 },
+ "minimum_apca_ui": { "type": "number", "minimum": 28 },
+ "minimum_apca_ui_dark": { "type": "number", "minimum": 28 },
+ "minimum_apca_on_accent": { "type": "number", "minimum": 60 },
+ "light_hours_default": { "type": "string" },
+ "dusk_hours_default": { "type": "string" },
+ "minimum_terminal_ansi_contrast": { "type": "number", "minimum": 4.5 },
+ "minimum_terminal_cursor_contrast": { "type": "number", "minimum": 4.5 },
+ "minimum_terminal_selection_contrast": { "type": "number", "minimum": 7 }
}
}
},
"$defs": {
- "hex": {
- "type": "string",
- "pattern": "^#[0-9a-fA-F]{6}$"
- },
+ "hex": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
"rgba_color": {
"type": "string",
"pattern": "^rgba\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*(0|1|0?\\.\\d+)\\s*\\)$"
},
+ "color_value": {
+ "oneOf": [{ "$ref": "#/$defs/hex" }, { "$ref": "#/$defs/rgba_color" }]
+ },
"palette": {
"type": "object",
"required": [
+ "name",
+ "details",
"bg",
+ "bg_soft",
+ "surface0",
+ "surface1",
+ "surface2",
+ "surface3",
"text",
+ "text_heading",
"muted",
+ "subtle",
"comment",
+ "border",
+ "border_ui",
+ "border_hi",
+ "focus",
"accent",
"accent_2",
"diagnostic",
"sage",
+ "success",
+ "info",
"error",
"warning",
- "border_ui",
- "focus"
+ "lavender",
+ "mauve",
+ "on_surface",
+ "on_accent",
+ "on_error",
+ "on_focus",
+ "link",
+ "link_hover",
+ "selection",
+ "selection_bg",
+ "selection_fg",
+ "disabled",
+ "hover",
+ "pressed",
+ "overlay",
+ "scrim",
+ "panel_rgba",
+ "module_rgba",
+ "active_rgba",
+ "inactive_border",
+ "prompt_bg",
+ "prompt_surface0",
+ "prompt_surface1",
+ "prompt_surface2",
+ "prompt_text",
+ "prompt_muted",
+ "prompt_accent",
+ "prompt_accent_2"
],
"properties": {
- "name": {
- "type": "string"
- },
- "details": {
- "type": "string"
- },
- "bg": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "text": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "muted": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "subtle": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "comment": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "accent": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "accent_2": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "diagnostic": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "sage": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "error": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "warning": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "border": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "border_ui": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "border_hi": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "focus": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "selection": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "panel_rgba": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "module_rgba": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "active_rgba": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "inactive_border": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_bg": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_surface0": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_surface1": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_surface2": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_text": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_muted": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_accent": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- },
- "prompt_accent_2": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" }
- ]
- }
+ "name": { "type": "string" },
+ "details": { "enum": ["darker", "lighter"] },
+ "bg": { "$ref": "#/$defs/color_value" },
+ "bg_soft": { "$ref": "#/$defs/color_value" },
+ "surface0": { "$ref": "#/$defs/color_value" },
+ "surface1": { "$ref": "#/$defs/color_value" },
+ "surface2": { "$ref": "#/$defs/color_value" },
+ "surface3": { "$ref": "#/$defs/color_value" },
+ "text": { "$ref": "#/$defs/color_value" },
+ "text_heading": { "$ref": "#/$defs/color_value" },
+ "muted": { "$ref": "#/$defs/color_value" },
+ "subtle": { "$ref": "#/$defs/color_value" },
+ "comment": { "$ref": "#/$defs/color_value" },
+ "border": { "$ref": "#/$defs/color_value" },
+ "border_ui": { "$ref": "#/$defs/color_value" },
+ "border_hi": { "$ref": "#/$defs/color_value" },
+ "focus": { "$ref": "#/$defs/color_value" },
+ "accent": { "$ref": "#/$defs/color_value" },
+ "accent_2": { "$ref": "#/$defs/color_value" },
+ "diagnostic": { "$ref": "#/$defs/color_value" },
+ "sage": { "$ref": "#/$defs/color_value" },
+ "success": { "$ref": "#/$defs/color_value" },
+ "info": { "$ref": "#/$defs/color_value" },
+ "error": { "$ref": "#/$defs/color_value" },
+ "warning": { "$ref": "#/$defs/color_value" },
+ "lavender": { "$ref": "#/$defs/color_value" },
+ "mauve": { "$ref": "#/$defs/color_value" },
+ "on_surface": { "$ref": "#/$defs/color_value" },
+ "on_accent": { "$ref": "#/$defs/color_value" },
+ "on_error": { "$ref": "#/$defs/color_value" },
+ "on_focus": { "$ref": "#/$defs/color_value" },
+ "link": { "$ref": "#/$defs/color_value" },
+ "link_hover": { "$ref": "#/$defs/color_value" },
+ "selection": { "$ref": "#/$defs/color_value" },
+ "selection_bg": { "$ref": "#/$defs/color_value" },
+ "selection_fg": { "$ref": "#/$defs/color_value" },
+ "disabled": { "$ref": "#/$defs/color_value" },
+ "hover": { "$ref": "#/$defs/color_value" },
+ "pressed": { "$ref": "#/$defs/color_value" },
+ "overlay": { "$ref": "#/$defs/color_value" },
+ "scrim": { "$ref": "#/$defs/color_value" },
+ "panel_rgba": { "$ref": "#/$defs/color_value" },
+ "module_rgba": { "$ref": "#/$defs/color_value" },
+ "active_rgba": { "$ref": "#/$defs/color_value" },
+ "inactive_border": { "$ref": "#/$defs/color_value" },
+ "prompt_bg": { "$ref": "#/$defs/color_value" },
+ "prompt_surface0": { "$ref": "#/$defs/color_value" },
+ "prompt_surface1": { "$ref": "#/$defs/color_value" },
+ "prompt_surface2": { "$ref": "#/$defs/color_value" },
+ "prompt_text": { "$ref": "#/$defs/color_value" },
+ "prompt_muted": { "$ref": "#/$defs/color_value" },
+ "prompt_accent": { "$ref": "#/$defs/color_value" },
+ "prompt_accent_2": { "$ref": "#/$defs/color_value" }
},
- "additionalProperties": {
- "oneOf": [
- { "$ref": "#/$defs/hex" },
- { "$ref": "#/$defs/rgba_color" },
- { "type": "string" }
- ]
- }
+ "additionalProperties": { "$ref": "#/$defs/color_value" }
}
}
}
diff --git a/themes/dreamcoder/waybar-dark.css b/themes/dreamcoder/waybar-dark.css
index c2eb3b1..d8dd094 100644
--- a/themes/dreamcoder/waybar-dark.css
+++ b/themes/dreamcoder/waybar-dark.css
@@ -2,17 +2,25 @@
@define-color bg #100f0d;
@define-color bg-soft #181512;
@define-color surface #201b16;
+@define-color surface-2 #392e21;
+@define-color surface-3 #4a3b2a;
@define-color text #e8dfd0;
+@define-color text-heading #f4ecdd;
@define-color muted #c7b9aa;
+@define-color subtle #938274;
+@define-color comment #b8a99a;
@define-color border #756052;
@define-color border-ui #968878;
@define-color focus #5f8f8f;
@define-color accent #d99555;
@define-color accent-2 #c96a45;
@define-color diagnostic #5f95ca;
+@define-color lavender #d4b4e6;
@define-color error #ed8a7a;
@define-color success #4db35f;
@define-color warning #e8b866;
+@define-color link #d99555;
+@define-color link-hover #c96a45;
window#waybar {
background: rgba(16, 15, 13, 0.78);
diff --git a/themes/dreamcoder/waybar-light.css b/themes/dreamcoder/waybar-light.css
index 59dee6d..d8a6124 100644
--- a/themes/dreamcoder/waybar-light.css
+++ b/themes/dreamcoder/waybar-light.css
@@ -2,17 +2,25 @@
@define-color bg #f3eadc;
@define-color bg-soft #e6d7c4;
@define-color surface #fff7ea;
+@define-color surface-2 #c8ad89;
+@define-color surface-3 #b89d7a;
@define-color text #17120d;
+@define-color text-heading #100c07;
@define-color muted #352e22;
+@define-color subtle #554638;
+@define-color comment #725e4c;
@define-color border #8a7358;
@define-color border-ui #66513b;
@define-color focus #0f6570;
@define-color accent #824f16;
@define-color accent-2 #a7471c;
@define-color diagnostic #0d4a68;
+@define-color lavender #57478b;
@define-color error #842f24;
@define-color success #3d723d;
@define-color warning #654300;
+@define-color link #824f16;
+@define-color link-hover #a7471c;
window#waybar {
background: rgba(243, 234, 220, 0.96);
diff --git a/themes/dreamcoder/waybar.css b/themes/dreamcoder/waybar.css
index 59dee6d..d8a6124 100644
--- a/themes/dreamcoder/waybar.css
+++ b/themes/dreamcoder/waybar.css
@@ -2,17 +2,25 @@
@define-color bg #f3eadc;
@define-color bg-soft #e6d7c4;
@define-color surface #fff7ea;
+@define-color surface-2 #c8ad89;
+@define-color surface-3 #b89d7a;
@define-color text #17120d;
+@define-color text-heading #100c07;
@define-color muted #352e22;
+@define-color subtle #554638;
+@define-color comment #725e4c;
@define-color border #8a7358;
@define-color border-ui #66513b;
@define-color focus #0f6570;
@define-color accent #824f16;
@define-color accent-2 #a7471c;
@define-color diagnostic #0d4a68;
+@define-color lavender #57478b;
@define-color error #842f24;
@define-color success #3d723d;
@define-color warning #654300;
+@define-color link #824f16;
+@define-color link-hover #a7471c;
window#waybar {
background: rgba(243, 234, 220, 0.96);