Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .opencode/themes/dreamcoder.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"text": "#17120d",
"textMuted": "#352e22",
"textSubtle": "#554638",
"textPlaceholder": "#725e4c",
"textPlaceholder": "#5d4d3e",
"textAssistant": "#0f4058",
"textUser": "#724615",
"textTool": "#0d4a68",
Expand All @@ -51,17 +51,17 @@
"diffAdded": "#315b31",
"diffRemoved": "#842f24",
"diffContext": "#352e22",
"diffHunkHeader": "#57478b",
"diffHunkHeader": "#473a72",
"diffHighlightAdded": "#315b31",
"diffHighlightRemoved": "#842f24",
"diffAddedBg": "#a2ae94",
"diffRemovedBg": "#c49b8f",
"diffAddedBg": "#f9f2e4",
"diffRemovedBg": "#fbf1e4",
"diffContextBg": "none",
"diffLineNumber": "#554638",
"diffAddedLineNumberBg": "#a2ae94",
"diffRemovedLineNumberBg": "#c49b8f",
"diffAddedLineNumberBg": "#f9f2e4",
"diffRemovedLineNumberBg": "#fbf1e4",
"diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFold": "#5d4d3e",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
"markdownHeading": "#824f16",
Expand All @@ -80,7 +80,7 @@
"markdownImageText": "#17120d",
"markdownCodeBlock": "#17120d",
"markdownCodeBlockBg": "#fff7ea",
"markdownInlineCodeBg": "#547550",
"markdownInlineCodeBg": "#e7e1d2",
"syntaxComment": "#725e4c",
"syntaxKeyword": "#824f16",
"syntaxFunction": "#a7471c",
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

### Fixed

- OpenCode surfaces follow the active mode with readable syntax and interface states.
- Repository sync regression tests derive the current upstream pin instead of relying on a historical hash.
- Zsh sources Bun completions only from nonempty regular files, preserving support for symlinks to regular files.
- Pi and Gentle Shell Light use cream chrome surfaces and brown focus roles, including the medium-thinking editor border, without changing Dark.
Expand Down
10 changes: 5 additions & 5 deletions DreamcoderCodexApp/Dreamcoder-Dark.codex-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
"diffHunkHeader": "#D4B5FD",
"diffHighlightAdded": "#34D399",
"diffHighlightRemoved": "#FB8585",
"diffAddedBg": "#235241",
"diffRemovedBg": "#5e3c3c",
"diffAddedBg": "#09261c",
"diffRemovedBg": "#2d1818",
"diffContextBg": "#000000",
"diffLineNumber": "#A7A7A7",
"diffAddedLineNumberBg": "#235241",
"diffRemovedLineNumberBg": "#5e3c3c",
"diffAddedLineNumberBg": "#09261c",
"diffRemovedLineNumberBg": "#2d1818",
"diffHunkHeaderBg": "#4f4759",
"diffFold": "#D0D0D0",
"diffFoldBg": "#0D0D0F",
Expand All @@ -80,7 +80,7 @@
"markdownImageText": "#E6E6E6",
"markdownCodeBlock": "#E6E6E6",
"markdownCodeBlockBg": "#0D0D0F",
"markdownInlineCodeBg": "#52a989",
"markdownInlineCodeBg": "#0D0D0F",
"syntaxComment": "#D0D0D0",
"syntaxKeyword": "#A5B4FC",
"syntaxFunction": "#D4B5FD",
Expand Down
16 changes: 8 additions & 8 deletions DreamcoderCodexApp/Dreamcoder-Light.codex-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"text": "#17120d",
"textMuted": "#352e22",
"textSubtle": "#554638",
"textPlaceholder": "#725e4c",
"textPlaceholder": "#5d4d3e",
"textAssistant": "#0f4058",
"textUser": "#724615",
"textTool": "#0d4a68",
Expand All @@ -51,17 +51,17 @@
"diffAdded": "#315b31",
"diffRemoved": "#842f24",
"diffContext": "#352e22",
"diffHunkHeader": "#57478b",
"diffHunkHeader": "#473a72",
"diffHighlightAdded": "#315b31",
"diffHighlightRemoved": "#842f24",
"diffAddedBg": "#a2ae94",
"diffRemovedBg": "#c49b8f",
"diffAddedBg": "#f9f2e4",
"diffRemovedBg": "#fbf1e4",
"diffContextBg": "#f3eadc",
"diffLineNumber": "#554638",
"diffAddedLineNumberBg": "#a2ae94",
"diffRemovedLineNumberBg": "#c49b8f",
"diffAddedLineNumberBg": "#f9f2e4",
"diffRemovedLineNumberBg": "#fbf1e4",
"diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFold": "#5d4d3e",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
"markdownHeading": "#824f16",
Expand All @@ -80,7 +80,7 @@
"markdownImageText": "#17120d",
"markdownCodeBlock": "#17120d",
"markdownCodeBlockBg": "#fff7ea",
"markdownInlineCodeBg": "#547550",
"markdownInlineCodeBg": "#e7e1d2",
"syntaxComment": "#725e4c",
"syntaxKeyword": "#824f16",
"syntaxFunction": "#a7471c",
Expand Down
16 changes: 8 additions & 8 deletions DreamcoderCodexApp/Dreamcoder.codex-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"text": "#17120d",
"textMuted": "#352e22",
"textSubtle": "#554638",
"textPlaceholder": "#725e4c",
"textPlaceholder": "#5d4d3e",
"textAssistant": "#0f4058",
"textUser": "#724615",
"textTool": "#0d4a68",
Expand All @@ -51,17 +51,17 @@
"diffAdded": "#315b31",
"diffRemoved": "#842f24",
"diffContext": "#352e22",
"diffHunkHeader": "#57478b",
"diffHunkHeader": "#473a72",
"diffHighlightAdded": "#315b31",
"diffHighlightRemoved": "#842f24",
"diffAddedBg": "#a2ae94",
"diffRemovedBg": "#c49b8f",
"diffAddedBg": "#f9f2e4",
"diffRemovedBg": "#fbf1e4",
"diffContextBg": "#f3eadc",
"diffLineNumber": "#554638",
"diffAddedLineNumberBg": "#a2ae94",
"diffRemovedLineNumberBg": "#c49b8f",
"diffAddedLineNumberBg": "#f9f2e4",
"diffRemovedLineNumberBg": "#fbf1e4",
"diffHunkHeaderBg": "#b8acbd",
"diffFold": "#725e4c",
"diffFold": "#5d4d3e",
"diffFoldBg": "#e6d7c4",
"markdownText": "#17120d",
"markdownHeading": "#824f16",
Expand All @@ -80,7 +80,7 @@
"markdownImageText": "#17120d",
"markdownCodeBlock": "#17120d",
"markdownCodeBlockBg": "#fff7ea",
"markdownInlineCodeBg": "#547550",
"markdownInlineCodeBg": "#e7e1d2",
"syntaxComment": "#725e4c",
"syntaxKeyword": "#824f16",
"syntaxFunction": "#a7471c",
Expand Down
48 changes: 36 additions & 12 deletions src/dreamcoder_theme/renderers_opencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
user_bg = mix(c["bg"], c["accent"], 0.12)
tool_bg = mix(c["bg"], c["lavender"], 0.12)
accent_muted = mix(c["accent"], c["bg"], 0.25)
inline_code_bg = mix(c["sage"], c["border_ui"], 0.45)
inline_code_bg = c["surface1"]
mix_base = c["border_ui"]
else:
panel_bg = c["surface0"]
Expand All @@ -70,7 +70,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
user_bg = mix(c["bg"], c["accent"], 0.15)
tool_bg = mix(c["bg"], c["lavender"], 0.12)
accent_muted = mix(c["accent"], c["bg"], 0.3)
inline_code_bg = mix(c["sage"], c["bg"], 0.18)
inline_code_bg = mix(c["bg"], c["sage"], 0.06)
mix_base = c["bg"]

diff_mix = 0.35 if mode_name == "dark" else 0.58
Expand All @@ -81,9 +81,33 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
c["bg"],
mode_name,
)
if mode_name == "dark":
# Quiet tinted canvases preserve syntax and gutter ink without whitening.
added_bg = surface_guard(mix(c["bg"], c["sage"], 0.18), c["bg"], mode_name)
removed_bg = surface_guard(mix(c["bg"], c["error"], 0.18), c["bg"], mode_name)
if mode_name == "light":
# Diff bodies retain syntax ink: use pale canonical code canvases rather
# than surface_guard, whose surface delta makes Light fills too dark.
added_bg = mix(c["surface0"], c["sage"], 0.03)
removed_bg = mix(c["surface0"], c["error"], 0.03)
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"]
muted = c["muted"]
subtle = guard(c["subtle"], c["bg"], mode_name, minimum=5.5)
placeholder = c["comment"]
added, removed = c["sage"], c["error"]
hunk, fold, markdown_code = c["lavender"], c["comment"], c["sage"]
if mode_name == "light":
# Body-text roles must clear their actual rendered fill, not just canvas.
placeholder = guard(placeholder, element_bg, mode_name, minimum=4.5)
markdown_code = guard(markdown_code, inline_code_bg, mode_name, minimum=4.5)
added = guard(added, added_bg, mode_name, minimum=4.5)
removed = guard(removed, removed_bg, mode_name, minimum=4.5)
hunk = guard(hunk, hunk_bg, mode_name, minimum=4.5)
fold = guard(fold, line_bg, mode_name, minimum=4.5)
for role in ("comment", "function", "method", "operator"):
t[role] = guard(t[role], code_bg, mode_name, minimum=4.5)
return f"""{{
"$schema": "https://opencode.ai/theme.json",
"defs": {{
Expand Down Expand Up @@ -115,9 +139,9 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
"backgroundUser": "{user_bg}",
"backgroundTool": "{tool_bg}",
"text": "{c["text"]}",
"textMuted": "{c["muted"]}",
"textSubtle": "{guard(c["subtle"], c["bg"], mode_name, minimum=5.5)}",
"textPlaceholder": "{c["comment"]}",
"textMuted": "{muted}",
"textSubtle": "{subtle}",
"textPlaceholder": "{placeholder}",
"textAssistant": "{assistant}",
"textUser": "{user}",
"textTool": "{t["type"]}",
Expand All @@ -134,26 +158,26 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) ->
"borderSubtle": "{c["border"]}",
"borderFocus": "{c["focus"]}",
"shadow": "{mix(c["bg"], "#000000", 0.25)}",
"diffAdded": "{c["sage"]}",
"diffRemoved": "{c["error"]}",
"diffAdded": "{added}",
"diffRemoved": "{removed}",
"diffContext": "{c["muted"]}",
"diffHunkHeader": "{c["lavender"]}",
"diffHighlightAdded": "{c["sage"]}",
"diffHighlightRemoved": "{c["error"]}",
"diffHunkHeader": "{hunk}",
"diffHighlightAdded": "{added}",
"diffHighlightRemoved": "{removed}",
"diffAddedBg": "{added_bg}",
"diffRemovedBg": "{removed_bg}",
"diffContextBg": "{background}",
"diffLineNumber": "{c["subtle"]}",
"diffAddedLineNumberBg": "{added_bg}",
"diffRemovedLineNumberBg": "{removed_bg}",
"diffHunkHeaderBg": "{hunk_bg}",
"diffFold": "{c["comment"]}",
"diffFold": "{fold}",
"diffFoldBg": "{line_bg}",
"markdownText": "{c["text"]}",
"markdownHeading": "{c["accent"]}",
"markdownLink": "{c["diagnostic"]}",
"markdownLinkText": "{c["accent"]}",
"markdownCode": "{c["sage"]}",
"markdownCode": "{markdown_code}",
"markdownBlockQuote": "{c["accent_2"]}",
"markdownEmph": "{c["diagnostic"]}",
"markdownStrong": "{c["accent"]}",
Expand Down
81 changes: 81 additions & 0 deletions tests/test_dark_surface_readability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Dark emitted text/surface regressions; no speculative token cross-product."""

import json

import pytest

from dreamcoder_theme.palette import apca_lc, contrast
from dreamcoder_theme.palette_tokens import VARIANTS
from dreamcoder_theme.renderers_opencode import opencode_content, opencode_tokens

DARK = VARIANTS["dark"]


def readable(theme, fg, bg, floor=4.5):
background = DARK["bg"] if theme[bg] == "none" else theme[bg]
ratio = contrast(theme[fg], background)
assert ratio >= floor, f"{fg}/{bg}: {ratio:.3f} < {floor}"


@pytest.fixture(params=(False, True), ids=("opaque", "transparent"))
def theme(request):
original = DARK.copy()
result = json.loads(opencode_content(DARK, request.param))["theme"]
assert original == DARK
return result


def test_dark_inline_code_preserves_sage_on_subtle_fill(theme):
assert theme["markdownCode"] == DARK["sage"]
readable(theme, "markdownCode", "markdownInlineCodeBg")
readable(theme, "markdownInlineCodeBg", "background", 1.05)


@pytest.mark.parametrize("kind", ("Added", "Removed"))
def test_dark_diff_semantics_syntax_and_gutter(theme, kind):
bg = f"diff{kind}Bg"
fields = [key for key in theme if key.startswith("syntax")]
assert len(fields) == 29
fields += [f"diff{kind}", f"diffHighlight{kind}", "diffLineNumber"]
failures = [
(fg, contrast(theme[fg], theme[bg]))
for fg in fields
if contrast(theme[fg], theme[bg]) < 4.5
]
assert not failures, failures
assert theme[bg] == theme[f"diff{kind}LineNumberBg"]
assert theme["diffAddedBg"] != theme["diffRemovedBg"]
# Retain green/red identity as well as the app's existing +/- markers.
channels = tuple(int(theme[bg][i : i + 2], 16) for i in (1, 3, 5))
assert channels[1 if kind == "Added" else 0] == max(channels)
for surface in (bg, f"diff{kind}LineNumberBg", "diffHunkHeaderBg"):
readable(theme, surface, "background", 1.05)
assert contrast(theme[surface], DARK["bg"]) <= 2.4
readable(theme, "diffHunkHeader", "diffHunkHeaderBg")


def test_dark_existing_main_messages_and_selection(theme):
assert theme["background"] in ("none", "#000000")
for bg in (
"background",
"backgroundPanel",
"backgroundElement",
"backgroundCode",
"backgroundAssistant",
"backgroundUser",
"backgroundTool",
):
readable(theme, "text", bg, 7.0)
for role in ("Assistant", "User", "Tool"):
readable(theme, f"text{role}", f"background{role}")
readable(theme, "textSelected", "backgroundSelected", 7.0)
assert abs(apca_lc(theme["text"], DARK["bg"])) >= 50
tokens = opencode_tokens(DARK)
for field, token in (
("syntaxKeyword", "keyword"),
("syntaxTag", "keyword"),
("syntaxDeprecated", "deprecated"),
):
assert theme[field] == tokens[token]
for surface in ("backgroundElement", "backgroundCode", "backgroundLine"):
readable(theme, surface, "background", 1.05)
Loading
Loading