Skip to content

Releases: ruegg114514/dsh-colorlab

v1.1.9 — fix boxes-inside-boxes on cards

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 14:27

Fixed

Cards grew "boxes inside boxes". The deliverable-file and plan cards showed outlined, shadowed strips across their title row, description row — and even the grid container around the cards.

The card glass rule matched [class*="flL80G_file"] — a substring match. CSS Modules give every inner element of the same component a shared prefix, so the haze, border and shadow landed on each of those rows too:

Token What it is Old *= New ~=
flL80G_file card body hit hit
flL80G_fileBody inner row wrongly hit —
flL80G_fileName file name wrongly hit —
flL80G_fileIcon icon wrongly hit —
KuQXFq_cardTitle plan title row wrongly hit —
KuQXFq_cardOpen open button wrongly hit —
KuQXFq_cards the grid container! wrongly hit —

The fix

*= → ~= (whole-word class match). CSS Modules give each element exactly one token, so flL80G_fileBody is no longer confused with flL80G_file.

bubble deliberately keeps *=: message bubbles carry per-package hashes in their token (cJsG2q_bubble, v1fjQa_bubble) that must not be hard-coded — and no bubbleXxx inner elements exist in the host, so there is no stacking risk.

Pattern worth naming

This is the third instance of the same mistake (the popover haze selector had the identical problem in v1.1.7): substring matching is unsafe against CSS Modules naming because inner elements share the owner's prefix. Wherever the exact token is known, use ~=.

Tests

13 assertions: each of the six prefixes verified to use ~= and not *=; bubble verified to keep *=. Mutation (reverting to *=) turns all 13 red.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.9

v1.1.8 — background brightness for wallpaper mode

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 07:15

Added

Background brightness now applies to wallpaper mode too. It was gradient-only before.

The two sources need different implementations but share one meaning:

Source Implementation 50 = as-is Extremes
Gradient mixTowards() shifts the generated base colour middle 0 pure black / 100 pure white (deliberate; the ambience layer fades with it)
Wallpaper CSS brightness(), on the same filter chain as blur and saturate 1.0 0 full black / 100 doubles
Setting brightness()
0 0.000
20 0.400
50 1.000
80 1.600
100 2.000

Two deliberate choices

  • Wallpaper stops at doubling, not pure white. Pure white would erase the photo's detail. Gradient reaching pure white is intentional, so the two differ at the extreme.
  • Contour mode is unaffected. Its line colour comes straight from the accent colour, so brightness would only distort it — the same reasoning that already excludes it from saturate.

The settings row is now shared by both sources instead of being gated on gradient alone, and the hint explains how each behaves.

Tests

Four new assertions on the filter chain and conversion formula, plus one on the settings guard covering both sources.

Mutation-tested:

  • making brightness a no-op → 3 assertions red
  • restricting the settings row back to gradient → 2 red

Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.8

v1.1.7 — nested haze down to an eighth

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 03:21

Changed

Nested haze reduced from a third to an eighth. v1.1.6 gave code blocks haze / 3, which combined to 10.5% — visibly denser than the 8% single-layer baseline. It is now haze / 8, combining to 8.9%, which matches the surrounding surfaces.

Divisor Child Combined
1 8.00% 15.36% two full layers — the original bug
3 2.67% 10.45% v1.1.6 — still too dense
8 1.00% 8.92% matches the baseline

What I got wrong twice

Both earlier attempts had an unclear target:

  • v1.1.5 optimised for "don't stack too thick" and landed on zero — which left that surface with no haze at all, because a code block paints its own opaque fill and hides the parent's contribution.
  • v1.1.6 optimised for "the haze should be visible" and overshot to 10.5%.

The right target is combined density close to the single-layer baseline. The child layer only needs enough to avoid reading as absent — not enough to be independently noticeable.

Tests

The bound is now two-sided: 0.005 ≤ haze < 0.02.

  • below the lower bound → effectively invisible (the v1.1.5 mistake)
  • above the upper bound → combined exceeds 9.8%, reads denser than everything else (the v1.1.6 mistake)

All three wrong versions are caught by mutation testing: zero, a third, and full.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.7

v1.1.6 — nested haze is a third, not zero

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 03:18

Fixed

Code blocks lost their frosted haze entirely. v1.1.5 removed the haze from nested surfaces to stop it stacking — but removing it is not the same as reducing it.

The haze is a white overlay, so two layers combine as 1-(1-a)(1-b):

Parent Child Combined Look
8% 8% 15.4% too heavy (the original bug)
8% 0% 8% child does not whiten at all — haze is gone
8% 2.7% 10.2% matches the surrounding surfaces

Why zero does not work

A code block paints its own fairly opaque surface, which covers the parent's haze. Without a layer of its own, that surface genuinely has no haze — the parent's contribution is hidden underneath it.

The value: a third

Nested surfaces now get haze / 3. A third rather than half because a code block's own fill is one tier more opaque than a card's, so the haze carries less visual weight on it:

  • full value → reads too white
  • zero → reads as absent
  • a third → matches the surrounding surfaces
Constant Haze Used by
glassSurface haze outermost — columns, cards, dialogs, popovers, composer
glassSurfaceFlat haze / 3 nested — code blocks, pre, inline code

Note

The name glassSurfaceFlat is now slightly misleading, since the surface it produces is no longer flat. It was kept to avoid touching every call site; the comment above it states what it actually produces.

Tests

The assertion changed from "code block has no haze" to "code block has haze, but clearly less than its parent":

  • codeHaze !== null — must not be none
  • 0 < codeHaze < 0.05 — present, and faint

Mutation-tested in both directions:

  • child set to 0 → 2 assertions red (reproduces the v1.1.5 mistake)
  • child set to full value → 2 assertions red (reproduces the original bug)

Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.6

v1.1.5 — stop stacking the frosted haze

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 03:10

Fixed

Code blocks looked noticeably whiter and duller than other glass surfaces.

The frosted haze is a translucent white gradient defined on the shared glassSurface constant, which every glass surface used — including nested ones. A code block sits inside a card or column, so the haze stacked:

card / column        ← glassSurface  (haze 8%)
  └ code block       ← glassSurface  (haze 8%)   ← stacked again

Combined density is 1 - (1-0.08)² ≈ 15.4%, nearly double the configured value. That is why code blocks read as whiter and more muted than everything around them.

The fix: split by nesting level

Constant Haze Used by
glassSurface yes outermost surfaces — columns, cards, dialogs, popovers, composer
glassSurfaceFlat no nested surfaces — code blocks, pre, inline code

Both keep the box-shadow and border. Those draw on the element's own edges and do not accumulate brightness, so stacking them is harmless.

Lowering the global haze value would have been the wrong fix — it exists to give the outermost layer its material feel, and thinning it everywhere would flatten single-layer surfaces.

How it was found

It is not visible in the source: the constant looks correct in isolation. Listing which selectors actually end up carrying the haze gradient in the generated stylesheet made the duplication obvious — the code-block rule and the card rule both had it.

Tests

  • the code-block rule must not carry the white gradient, but must keep its shadow and border
  • the outermost card rule must still carry the haze

Mutation-tested: switching the code block back to glassSurface turns 2 assertions red.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.5

v1.1.4 — pass detected popover classes through

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 02:52

Fixed

The turn-preview popover had no blur again. A one-line wiring bug introduced in v1.1.3.

buildGlassSheet gained a sixth parameter for the detected popover class names, but the real call site never passed it. The edit had been applied to an identically-worded string in the module's export block rather than to the function that actually builds the stylesheet.

The rule-generation logic was correct and its unit tests passed — the rule simply never received any class names at runtime, so it was never emitted. That is why the previous release's tests were green while the feature did nothing.

All call sites now pass the parameter, and a test asserts that every runtime call site includes it.

Also fixed

  • Detection now also runs inside syncGlassSheet, not only syncCardSheet. syncCardSheet runs early in startup, when the host may not have injected all of its stylesheets yet; detection would return nothing and there was no second attempt. syncGlassSheet now retries — bounded at 60 attempts, after which the result is cached — so a late-arriving stylesheet still gets picked up.
  • The status object now exposes popovers — the number of popover classes detected. 0 means detection has not succeeded yet, which makes this class of failure visible instead of silent.

Four releases, four different failures

Version What broke
v1.1.1 selector too narrow — missed xpvNua_preview
v1.1.2 selector too broad — hit JSON-tree colouring, diff went solid
v1.1.3 detection correct, but the call site never passed the result
v1.1.4 wiring fixed; detection also retries and is observable

The recurring pattern: the unit-tested piece kept passing while the integration point silently dropped the data. The test added here asserts the call-site wiring directly rather than only the builder's behaviour.

Tests

Mutation-tested: removing the argument from the call site turns 2 assertions red.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.4

v1.1.3 — detect popovers at runtime

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 02:48

Fixed

Diff regions turned solid, and the popover text gained dark boxes around it. A regression from v1.1.2.

v1.1.2 widened the popover selector to [class*="preview"] so it would cover both class naming formats. But the host has eight class names containing "preview", and only two of them are popovers:

Class What it is Has background?
_preview_38jqx_52 hover file preview yes — popover
xpvNua_preview turn preview yes — popover
_preview_1alm6_221 JSON-tree syntax colouring no — only color
_previewProperty_1alm6_225 same no
_previewEllipsis_1alm6_229 same no
xpvNua_previewPrompt / previewResponse text inside the popover no
_floatingCell_6nhg2_156 dockkit 0×0 positioning cell no

The JSON-tree ones live inside diff regions, so painting them as surfaces put a dark box under every JSON value.

The fix: detect at runtime instead of matching names

The substring selector is gone. detectPopoverClasses() scans the stylesheets and accepts a class only if both conditions hold:

  1. the name matches a popover naming pattern (either _preview_<hash>_<n> or <hash>_preview)
  2. the rule declares a background

A popover has to paint its own surface; an inner text block does not. That criterion follows the host implementation rather than a class-name list, so a new previewXxx class in a future host build cannot silently break it again.

Generated rules now target exact class names:

html[data-dsh-glass] ._preview_38jqx_52,
html[data-dsh-glass] .xpvNua_preview,
html[data-dsh-glass] ._float_6nhg2_156 { … }

The contain override uses the same detected names. Also fixed the format-A regex — it was missing the leading underscore, so ._preview_38jqx_52 never matched and only format-B names were found.

Three iterations

Version Selector Outcome
v1.1.1 [class*="_preview_"] missed xpvNua_preview (no leading underscore)
v1.1.2 [class*="preview"] matched JSON-tree colouring, diff went solid
v1.1.3 runtime detection + exact names both cases covered

Substring matching on class names was the wrong tool: too narrow and it misses, too broad and it hits unrelated elements. The fix was to write the definition of "is a popover" as an executable condition instead of enumerating names.

Tests

Detection is now tested against a real host CSS fragment: three popovers must be found (covering both naming formats), seven non-popovers must be excluded.

Mutation-tested:

  • removing the background criterion → 2 assertions red (reproduces the diff bug)
  • dropping the leading underscore from the regex → 3 assertions red
  • deleting the contain override → 3 assertions red

Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.3

v1.1.2 — blur the turn-preview popover

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 02:33

Fixed

The turn-preview popover still was not blurred. v1.1.1 injected the right rule — verified in the page's stylesheet — but it had no effect on this popover, for two independent reasons.

1. Class name format

The rule matched [class*="_preview_"], which only covers CSS Modules hashed names like _preview_38jqx_52. This popover's class is xpvNua_preview — module name, then element name, no leading underscore — so the selector never matched it.

Format Example Old selector
CSS Modules hash _preview_38jqx_52 matches
module_element xpvNua_preview misses

Now uses [class*="preview"] to cover both, with :not([class*="dmt-"]) to exclude the plugin's own settings-page elements.

2. contain: layout blocked the backdrop sampling

Its parent nav carries contain: layout, which establishes a new containing block and confines backdrop-filter sampling to that box — and the box is 28px wide. The blur had nothing to sample.

Verified side by side (preview-contain-test.html):

Group Structure Blurred?
A parent has contain:layout no — flat translucent
B parent without contain yes
C position:fixed escaped yes

A new rule clears contain on any ancestor that directly contains a popover, using :has(). The hashed class name cannot be hard-coded since it changes on every build. :has() is supported well before this Electron version; if it were unsupported the rule is ignored and the popover degrades to "translucent but not frosted", exactly as before.

Note on the previous release

v1.1.1 was verified by checking that the CSS rule reached the page — it did. That turned out not to be sufficient: contain makes backdrop-filter fail silently while background-color still applies, so the popover looked translucent but unfrosted and the injection check passed.

The first diagnostic script also missed it, because it filtered for position:fixed and this popover is position:absolute.

Tests

Six new assertions covering both halves of the fix. Mutation-tested:

  • reverting to the _preview_ selector → 2 assertions red
  • deleting the contain override → 3 assertions red

Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.2

v1.1.1 — glass the transient popovers

Choose a tag to compare

@ruegg114514 ruegg114514 released this 24 Oct 02:11

Fixed

Hover popovers were not glassed at all.

The file-change diff popover and the conversation-history popover — both position:fixed elements with no role attribute — fell outside the [role="dialog"] / [role="menu"] rules, so they rendered as flat opaque boxes over live text. The only code that touched them lived behind the Solid floats switch (off by default), and it pinned them fully opaque rather than glassing them.

They now follow the glass in two layers:

State Behaviour
Default Same delta-alpha compositing and blur as dialogs and menus, with a slightly higher +0.03 readability compensation — these are transient and you read them immediately
Solid floats on Still pins them opaque; its rule wins by source order

The HoverCard bubble had the same problem in a different shape: its background is the hard-coded --dsw-hovercard-bg: #2C2C2E, which no token override can reach. It is now overridden at the variable itself with a translucent glass value, resolved through runtime class detection so the hashed class name keeps working across builds.

Why it took a separate fix

The three affected popovers are all position:fixed divs with no role, so the whole role-based rule set silently skipped them. Nothing errored — they just stayed opaque.

One constraint worth noting: _card_ is a generic local name the host also uses for message cards and plan cards, so the HoverCard fix could not use a substring selector without pinning ordinary content cards too. It targets the detected hash class directly instead.

Tests

Rewrote 8 assertions that encoded the old "write no rule by default" behaviour, and added 6 new ones:

  • default tier must be rgba( and carry backdrop-filter:blur(
  • solid tier must be 6-digit hex with no rgba(
  • solid tier must come after the glass tier (same specificity, order decides)

Mutation-tested: deleting the popover rule → 6 assertions red; deleting the HoverCard rule → 6 red; turning the glass tier opaque → 2 red.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.1

v1.1.0 — background ground rework, settings rebuilt

Choose a tag to compare

@ruegg114514 ruegg114514 released this 23 Oct 17:54

Background ground is now a compositor of its own

Three modes, every colour picked by you:

  • Glow — 2–6 light sources, each with its own colour, strength and X/Y position, plus overall density (0–100%) and spread (40–140%).
  • Bands — 2–6 colour strips, each its own colour, a cutting angle anywhere from 0° to 360°, and an edge-softness slider.
  • Blend — bands underneath, glow on top.

Glow density ceiling raised from 70% to 100%. Gradient mode no longer applies the wallpaper veil, blur, saturation or upscale — those were designed for photo wallpapers and were eating roughly a third of the glow.

Gradient background with layered glow

Settings page rebuilt: 11 groups → 5

The Glass group now keeps only two rows permanently visible (master switch and background source). Everything else folds into Common settings and Current background only — the latter renders just the parameters for the source you picked, and returns null (not display:none) for the other two.

Procedural contour background

All colour controls — syntax palette, prose roles, process rows, accent, interface surfaces, 9 prose slots, 18-slot palette — now live in a single Colour group.

Removed

Settings that existed but did nothing meaningful: apply scope, background variant, accent presets, prose brightness, and all three preview blocks. Plus dead code: glassGlow, glassAmbientAccent and its palette table, OPTIONS.glassBandDir, previewVars.

Fixed

  • Wallpaper local-path input and fit dropdown had gone missing — a regression from earlier refactors. The field and its read path were intact, so nothing errored; you just could not fill it in.
  • Light-source sliders overflowed the panel — .dmt-slider is a fixed 132px and .dmt-sliderRow is flex:none.
  • Band-mode glow still looked like a hard cut — the glow branch was accidentally including the band layer.
  • Gradient glow looked much weaker than the preview — the CSS was byte-identical; the difference was the veil and filter layers above it.

Tests

~50 new assertions, mutation-tested. Every new assertion was verified to go red when the behaviour it guards is broken. Three false-green assertions were caught and rewritten this way.


Full changelog: CHANGELOG.md

Install: dsh plugin add ruegg114514/dsh-colorlab
Pin this version: dsh plugin add ruegg114514/dsh-colorlab#v1.1.0