From da517f1c102583281e3200695c60e9f739945d11 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Mon, 26 Oct 2026 20:28:54 +0100 Subject: [PATCH 1/4] feat: publish a semantic host palette contract - material you shells export semantic roles, not terminal colors - the contract maps each template token to a role, so hosts stop hard-coding it - the contrast suite covers dux shell's default theme in dark and light mode Closes #4 --- README.md | 15 ++++++---- integration/host-palette.json | 41 ++++++++++++++++++++++++++ tests/host-palette.test.mjs | 34 ++++++++++++++++++++++ tests/omarchy-contrast.spec.mjs | 51 +++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 integration/host-palette.json create mode 100644 tests/host-palette.test.mjs diff --git a/README.md b/README.md index b2157b7..3cb1cc4 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,20 @@ discovery and relays disabled, and stop them on exit. Set `app/` and `client/` own the application; `static/` owns its external assets; `themes/` owns base palettes; `integration/` owns the optional Omarchy CSS -template and browser refresh helper. Tests assert the current API, rendering and -formatting contracts directly. +template, its browser refresh helper and `host-palette.json`, which maps a +shell's semantic colors onto that template. Tests assert the current API, +rendering and formatting contracts directly. ## Plugin integration The plugin imports the same compiled archive, pins its checksum, and supplies -Omarchy palette values to the included template. Its Go bridge provides optional -local file actions. Without a plugin grant, ordinary Syncthing operations remain -available and local file actions report unavailable access. +Omarchy palette values to the included template. Material You shells such as Dux +Shell export semantic roles instead (background, surfaces, foreground, muted, +border, accent, success, warning, danger, info); `host-palette.json` defines how +those fill the template, and the contrast suite covers Dux Shell's default +theme. The plugin's Go bridge provides optional local file actions. Without a +plugin grant, ordinary Syncthing operations remain available and local file +actions report unavailable access. This is a third-party Syncthing interface. Original authors and dependency licenses remain credited in About and in the release. diff --git a/integration/host-palette.json b/integration/host-palette.json new file mode 100644 index 0000000..b010bd3 --- /dev/null +++ b/integration/host-palette.json @@ -0,0 +1,41 @@ +{ + "format": 1, + "template": "omarchy-theme.css.in", + "roles": [ + "mode", + "background", + "surface", + "surfaceVariant", + "foreground", + "muted", + "accent", + "success", + "warning", + "danger", + "border", + "info" + ], + "constants": { + "palette": "portable" + }, + "tokens": { + "mode": "mode", + "background": "background", + "surface": "surface", + "surface_dark": "surfaceVariant", + "selection": "surfaceVariant", + "foreground": "foreground", + "foreground_light": "foreground", + "foreground_dark": "muted", + "muted": "border", + "accent": "accent", + "blue": "accent", + "magenta": "accent", + "green": "success", + "yellow": "warning", + "bright_yellow": "warning", + "orange": "warning", + "red": "danger", + "cyan": "info" + } +} diff --git a/tests/host-palette.test.mjs b/tests/host-palette.test.mjs new file mode 100644 index 0000000..e701faa --- /dev/null +++ b/tests/host-palette.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const contract = JSON.parse( + await readFile( + new URL('../integration/host-palette.json', import.meta.url), + 'utf8', + ), +); +const template = await readFile( + new URL(`../integration/${contract.template}`, import.meta.url), + 'utf8', +); + +test('the host palette contract resolves every template token', () => { + const placeholders = new Set( + [...template.matchAll(/{{(\w+)}}/g)].map((match) => match[1]), + ); + const resolved = new Set([ + ...Object.keys(contract.tokens), + ...Object.keys(contract.constants), + ]); + assert.deepEqual([...placeholders].sort(), [...resolved].sort()); + for (const key of Object.keys(contract.constants)) + assert.ok(!(key in contract.tokens), `${key} is both constant and role`); +}); + +test('every host role is used and every token names a role', () => { + assert.equal(contract.format, 1); + assert.equal(new Set(contract.roles).size, contract.roles.length); + const used = new Set(Object.values(contract.tokens)); + assert.deepEqual([...used].sort(), [...contract.roles].sort()); +}); diff --git a/tests/omarchy-contrast.spec.mjs b/tests/omarchy-contrast.spec.mjs index bb12f2f..099299e 100644 --- a/tests/omarchy-contrast.spec.mjs +++ b/tests/omarchy-contrast.spec.mjs @@ -431,11 +431,62 @@ const palettes = { orange: '#4a4a4a', }, }; + +const hostPalette = JSON.parse( + await readFile( + new URL('../integration/host-palette.json', import.meta.url), + 'utf8', + ), +); +// Material You shells export semantic roles, not terminal colors; these are +// Dux Shell's default purple theme with its fixed success, warning and info. +const hostPalettes = { + 'dux-purple-dark': { + mode: 'dark', + background: '#141218', + surface: '#211f24', + surfaceVariant: '#2b292f', + foreground: '#e6e0e9', + muted: '#cac4cf', + accent: '#d0bcff', + success: '#4caf50', + warning: '#ff9800', + danger: '#f2b8b5', + border: '#948f99', + info: '#2196f3', + }, + 'dux-purple-light': { + mode: 'light', + background: '#fef7ff', + surface: '#f2ecf4', + surfaceVariant: '#ece6ee', + foreground: '#1d1b20', + muted: '#49454e', + accent: '#6750a4', + success: '#4caf50', + warning: '#ff9800', + danger: '#f2b8b5', + border: '#7a757f', + info: '#2196f3', + }, +}; +for (const [name, roles] of Object.entries(hostPalettes)) + palettes[name] = Object.fromEntries( + Object.entries(hostPalette.tokens).map(([token, role]) => [ + token, + roles[role], + ]), + ); const derivedText = { 'rose-pine': 'color(srgb 0.102353 0.0964706 0.142353)', 'catppuccin-latte': 'color(srgb 0.0596078 0.0619608 0.0823529)', }; const semanticText = { + 'dux-purple-light': { + success: '#1d1b20', + warning: '#1d1b20', + danger: '#1d1b20', + }, ethereal: { danger: '#ffcead' }, gruvbox: { danger: '#d4be98' }, kanagawa: { success: '#dcd7ba', danger: '#dcd7ba' }, From 20dd0d8a18a5b280788c290c666d4e5ec12d3550 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Thu, 29 Oct 2026 10:24:12 +0100 Subject: [PATCH 2/4] ci: give the test job 30 minutes the browser suite needs about four minutes, but setup has run past the old limit --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ed7efb..99931d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: test: if: github.event_name != 'workflow_dispatch' || !inputs.verify_handoff runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 From 089744d3fe3f1ebd866df244b910de6e46b46b49 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Thu, 29 Oct 2026 10:24:12 +0100 Subject: [PATCH 3/4] ci: install chromium without apt packages the ubuntu runner already ships chrome's libraries; the apt step alone took over twelve minutes --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99931d4..d49b130 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,9 @@ jobs: echo "f929eb8e5b72a85543eeeefb2c38f34a68e0c530e70758a2905b78840c76602c $RUNNER_TEMP/syncthing.tar.gz" | sha256sum --check tar -xzf "$RUNNER_TEMP/syncthing.tar.gz" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/syncthing-linux-amd64-v2.1.3" >> "$GITHUB_PATH" - - run: npx playwright install --with-deps chromium + # The runner image already has Chrome's libraries; apt mirrors have taken + # over twelve minutes to deliver them again. + - run: npx playwright install chromium - name: Test installed release run: bash scripts/test-release.sh "$PWD"/release/*.tar.gz - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a From 0df72e1df4d08593552025e06ed70356c73f2c62 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Thu, 29 Oct 2026 10:30:36 +0100 Subject: [PATCH 4/4] ci: keep the 15 minute timeout now that the job takes 4 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d49b130..07445fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: test: if: github.event_name != 'workflow_dispatch' || !inputs.verify_handoff runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020