Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: jq '.name = "pierre-theme"' package.json > tmp.json && mv tmp.json package.json

- name: Swap in VSCE README
run: cp README.package.md README.md
run: cp scripts/README.package.md README.md

- name: Publish to VS Marketplace
env:
Expand Down
36 changes: 27 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,37 @@ jobs:

- name: Verify theme files exist
run: |
if [ ! -f "themes/pierre-light.json" ]; then
echo "❌ pierre-light.json not generated"
exit 1
fi
if [ ! -f "themes/pierre-dark.json" ]; then
echo "❌ pierre-dark.json not generated"
exit 1
fi
for theme in \
pierre-light \
pierre-light-protanopia-deuteranopia \
pierre-light-soft \
pierre-light-tritanopia \
pierre-light-vibrant \
pierre-dark \
pierre-dark-protanopia-deuteranopia \
pierre-dark-soft \
pierre-dark-tritanopia \
pierre-dark-vibrant; do
if [ ! -f "themes/${theme}.json" ]; then
echo "❌ ${theme}.json not generated"
exit 1
fi
done
echo "✅ All theme files generated successfully"

- name: Verify ESM wrapper modules exist
run: |
for theme in pierre-dark pierre-light pierre-dark-vibrant pierre-light-vibrant; do
for theme in \
pierre-light \
pierre-light-protanopia-deuteranopia \
pierre-light-soft \
pierre-light-tritanopia \
pierre-light-vibrant \
pierre-dark \
pierre-dark-protanopia-deuteranopia \
pierre-dark-soft \
pierre-dark-tritanopia \
pierre-dark-vibrant; do
if [ ! -f "dist/${theme}.mjs" ]; then
echo "❌ dist/${theme}.mjs not generated"
exit 1
Expand Down
8 changes: 4 additions & 4 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
"tasks": [
{
"type": "npm",
"script": "start",
"script": "dev",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "npm: start",
"detail": "nodemon --watch src src/index.js"
"label": "npm: dev",
"detail": "nodemon --watch src --watch scripts --ext ts --exec \"npm run build && npm run preview\""
},
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": [],
"label": "npm: build",
"detail": "node src/index.js"
"detail": "ts-node scripts/build.ts"
}
]
}
133 changes: 133 additions & 0 deletions ACCESSIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Accessibility

## Color-Vision-Deficiency (CVD) themes

Pierre ships four themes designed for people with a **color vision deficiency
(CVD)**, the condition commonly called "color blindness." This document explains
what CVD is and how the themes are engineered around it.

| `name` | label | type | targets |
|---|---|---|---|
| `pierre-light-protanopia-deuteranopia` | Pierre Light Protanopia & Deuteranopia | light | red-green |
| `pierre-light-tritanopia` | Pierre Light Tritanopia | light | blue-yellow |
| `pierre-dark-protanopia-deuteranopia` | Pierre Dark Protanopia & Deuteranopia | dark | red-green |
| `pierre-dark-tritanopia` | Pierre Dark Tritanopia | dark | blue-yellow |

---

### What is CVD?

The retina senses color with three cone types — **L** (long / "red"), **M**
(medium / "green") and **S** (short / "blue") wavelengths. When one type is
missing or shifted, colors that differ mainly along that cone's axis collapse
into the same perceived color. CVD affects roughly **8% of men and 0.5% of
women**.

The three dichromacies (the strongest, "complete" forms) we target:

| Type | Missing cone | Confuses | **Preserved** axis (what can still be told apart) |
|---|---|---|---|
| **Protanopia** | L ("red") | red ↔ green | **blue ↔ orange/yellow** + luminance |
| **Deuteranopia** | M ("green") | red ↔ green | **blue ↔ orange/yellow** + luminance |
| **Tritanopia** | S ("blue") | blue ↔ green (and yellow ↔ violet) | **red ↔ cyan/teal** + luminance |

> Tritanopia is loosely called "blue-yellow," but blue and yellow differ a lot in
> *luminance* (which is preserved), so they stay apparent. The pair that truly
> collapses is **blue ↔ green**.

The key consequence for a code editor: a normal theme encodes the most important
signals — **added vs deleted**, **pass vs fail**, **error vs warning** — as
**red vs green**. To a protanope or deuteranope (the most common CVD), red and
green look nearly identical, so those signals become ambiguous.

---

### How the themes are engineered

Four principles, each enforced or made checkable by the build:

1. **Identical chrome = family fit.** Each CVD theme reuses the base
`light`/`dark` roles for `bg`, `fg`, and `border` **verbatim**. Windows, text,
and borders are pixel-for-pixel identical to Pierre Light / Pierre Dark — that
is what keeps them recognizably "Pierre." Only the chromatic roles
(`accent`, `states`, `syntax`, `ansi`) change.

2. **Signals ride the preserved axis.** Every meaningful color is re-mapped onto
the hue axis that the target deficiency keeps:
- **Protan/Deutan:** positive/added → **blue**, negative/deleted → **orange**.
- **Tritan:** positive/added → **teal/cyan**, negative/deleted → **red/vermillion**.

3. **Luminance is the backup channel.** Under dichromacy there are only ~2 usable
hue poles + luminance, but ~20 chromatic roles. Where two roles must share a
pole (e.g. several "cool" syntax tokens), they are separated by **luminance**
(different palette stops) — the channel CVD users rely on most.

4. **Reuse the existing palette.** All colors come from scales already in
`src/palettes.ts` (`blue`, `orange`, `teal`, `vermillion`, `magenta`, …). No
off-brand hues were invented.

#### Role mapping (the actual choices)

Stops shift lighter on dark backgrounds (mirroring how `dark` shifts vs `light`).

**Protan/Deutan — axis blue ↔ orange:**

| Role | Light | Dark | Why |
|---|---|---|---|
| `accent.primary` / `link` | blue 500 | blue 500 | keep Pierre blue (brand) |
| `success` (added) | blue 700 | blue 300 | positive → blue, luminance-split from accent |
| `danger` (deleted/error) | orange 700 | orange 400 | negative → orange |
| `warn` | yellow 500 | yellow 300 | bright caution; big luminance gap from danger |
| `info` | cyan 700 | cyan 400 | cool side; pairs against merge |
| `merge` | violet 700 | violet 400 | blue-violet conflict color |
| `ansi.red` / `ansi.green` | orange / blue | orange / blue | terminal pass/fail separable |
| `syntax.string` (=inserted) | blue 800 | blue 300 | added pole |
| `syntax.tag` (=deleted) | orange 700 | orange 400 | deleted pole |

**Tritanopia — axis red ↔ cyan/teal:**

| Role | Light | Dark | Why |
|---|---|---|---|
| `accent.primary` / `link` | blue 500 | blue 500 | reads cyan-blue, clearly ≠ red |
| `success` (added) | teal 700 | teal 300 | positive → teal/cyan |
| `danger` (deleted/error) | vermillion 600 | vermillion 400 | negative → red (preserved) |
| `warn` | amber 600 | amber 400 | caution; ΔE-separated from danger |
| `info` | blue 600 | blue 400 | cyan side |
| `merge` | magenta 700 | magenta 400 | reddish-purple — tritan-safe, far from blue *and* red |
| `ansi.red` / `ansi.green` | vermillion / teal | vermillion / teal | terminal pass/fail separable |
| `syntax.string` (=inserted) | teal 700 | teal 300 | added pole |
| `syntax.tag` (=deleted) | vermillion 600 | vermillion 400 | deleted pole |

---

### The objective test

A hard gate in `test/` simulates every chromatic role for each deficiency — under
*both* the linear-RGB and gamma-sRGB Machado (2009) conventions — and fails the
build if any must-distinguish pair stops being separable (CIEDE2000 ΔE) or legible
(WCAG contrast). It enforces the tiers and contrast policy below. For how to run and
read the report, see [CONTRIBUTING.md](CONTRIBUTING.md#testing).

#### Tiers — graded by *what carries the signal when color fails*

Under dichromacy not every pair can be hue-unique, so we gate hardest where color
is the *only* cue and lean on the editor's built-in non-color cues elsewhere:

| Tier | Bar | What | Why this bar |
|---|---|---|---|
| **1** | ΔE ≥ 11 | diff add/delete backgrounds, diff inserted/deleted **text**, merge-conflict backgrounds, terminal red/green | color is the **sole** disambiguator — no glyph fallback |
| **2** | ΔE ≥ 8 | diagnostics (error/warn/info), core syntax (keyword/string/variable), comment-vs-code | color **plus** a non-color cue (icon shapes; position) |
| **3** | advisory | git-tree clique, extended syntax | every git entry has an **M/A/D/U/C letter badge**; syntax has bold/italic + position. Reported, not gated. |

#### Contrast policy

We hold the themes to WCAG bars, but only the bar that fits how each color renders:

- **Body text** (editor foreground) → **4.5:1** (SC 1.4.3 normal text).
- **Syntax tokens & meaningful signal colors** → **3:1** (SC 1.4.11 UI / large
text), checked normal **and** after simulation.
- **Report-only** (printed, never fails the build): colors whose canonical/brand
hue is intrinsically high-luminance and that base Pierre itself keeps bright —
`accent.primary`/`link` (brand blue), `warn` (caution yellow/amber), and the
decorative ansi colors. Their *distinguishability* is gated; their raw contrast
is not.
23 changes: 17 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,30 @@ To override this (or any other) theme in your personal config file, please follo
2. Run `npm install` to install the dependencies.
3. Press `F5` to open a new window with your extension loaded
4. Open `Code > Preferences > Color Theme` [`⌘k ⌘t`] and pick the "Pierre…" theme you want to test.
5. Make changes to the [`/src/theme.ts`](https://github.com/pierrecomputer/theme/blob/main/src/theme.ts) file.
6. Run `npm run build` to update the theme. You can also run `npm run start` instead to automatically rebuild the theme while making changes and no reloading should be necessary.
7. Run `npm test` to validate your changes (this runs automatically on PRs).
5. Make changes under [`/src`](https://github.com/pierrecomputer/theme/blob/main/src). Theme construction lives in `src/createTheme.ts`; role values live in `src/roles`.
6. Run `npm run build` to update the theme. You can also run `npm run dev` instead to automatically rebuild the themes and previews while making changes and no reloading should be necessary.
7. Run `npm test` to validate your changes (this runs automatically on PRs); see
[Testing](#testing) below.
8. Once you're happy, commit your changes and open a PR.

## Testing

`npm test` builds the themes, runs structural validation, and runs the CVD
accessibility gate (the design it enforces is documented in
[`ACCESSIBILITY.md`](ACCESSIBILITY.md)). The gate, in `test/`:g

For visual proofing, `npm run preview` writes `preview/*.html`: the palette
scales, the Display-P3 conversions, and a normal-vs-simulated CVD proof sheet.

## Scripts

| Script | Description |
| --- | --- |
| `npm run build` | Builds the theme `.json` files in `./themes` directory |
| `npm test` | Runs validation tests on the theme (includes build) |
| `npm run package` | Compiles the theme `.vsix` file at the project root |
| `npm start` | Automatically runs build on file change |
| `npm test` | Runs validation tests + the CVD accessibility gate (includes build) |
| `npm run preview` | Writes preview HTML files from `src/previews` into `preview/` |
| `npm run package` | Temporarily applies the VSIX package name/README shim, then writes the `.vsix` file at the project root |
| `npm run dev` | Rebuilds themes and previews on file change |

## Credit

Expand Down
19 changes: 3 additions & 16 deletions DISPLAY-P3.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,9 @@ const html = highlighter.codeToHtml(code, {

## Relevant files

- **`src/color-p3.ts`** - Color conversion and enhancement
- **`src/demo-p3.ts`** - Demo showing conversions (`npx ts-node src/demo-p3.ts`)
- **`color-comparison.html`** - Visual comparison tool (open in Safari on P3 display)

## Testing

```bash
# View color conversions
npx ts-node src/demo-p3.ts

# Rebuild themes
npm run build

# Run tests
npm test
```
- **`src/color/p3.ts`** - Display-P3 conversion and gamut enhancement
- **`src/color/`** - Shared color science (sRGB, contrast, CIEDE2000, CVD)
- **`src/previews/p3.ts`** - Preview showing conversions (`npm run preview`)

## Why this matters

Expand Down
16 changes: 13 additions & 3 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,20 @@ Usage:
import pierreLight from '@pierre/theme/pierre-light'

Available themes:
- @pierre/theme/pierre-dark
- @pierre/theme/pierre-light
- @pierre/theme/pierre-dark-vibrant (Display P3)
- @pierre/theme/pierre-light-vibrant (Display P3)
- @pierre/theme/pierre-light-protanopia-deuteranopia (CVD, red-green)
- @pierre/theme/pierre-light-tritanopia (CVD, blue-yellow)
- @pierre/theme/pierre-light-vibrant (Display P3)
- @pierre/theme/pierre-dark
- @pierre/theme/pierre-dark-protanopia-deuteranopia (CVD, red-green)
- @pierre/theme/pierre-dark-tritanopia (CVD, blue-yellow)
- @pierre/theme/pierre-dark-vibrant (Display P3)

Zed:
Install "Pierre" from the Zed extension registry

~~~

Contributing:
- Development, build, and testing: [CONTRIBUTING.md](CONTRIBUTING.md)
- Accessibility (CVD themes): [ACCESSIBILITY.md](ACCESSIBILITY.md)
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading