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
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
matrix:
node: [22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
cache: npm
Expand All @@ -38,12 +38,15 @@ jobs:
- name: Unit + pipeline tests
run: npm test

- name: Palette PNGs must be current
run: node scripts/render-palette.mjs --check

- name: Package VSIX
run: npx vsce package --out alone.vsix

- name: Upload VSIX
if: matrix.node == 24
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: alone-vsix
path: alone.vsix
Expand Down
136 changes: 136 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: Release

# Tag-driven release: `git tag v2.0.0 && git push origin v2.0.0`
# build → verify → test → package the VSIX → GitHub Release (tags only)
# marketplace → publish that VSIX to the VS Code Marketplace (VSCE_PAT)
# open-vsx → publish the same VSIX to Open VSX (OVSX_PAT)
# The two registry jobs are independent, so if one fails (outage, expired
# token) "Re-run failed jobs" retries only that one — the Marketplace rejects
# a version that is already published, so re-running a combined job could
# never repair a half-finished release. A publish job is skipped when its
# secret is absent, so a workflow_dispatch run on a fork or before the
# accounts exist still produces a Release + VSIX. See docs/PUBLISHING.md.

on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
publish:
description: 'Publish to Marketplace / Open VSX (needs secrets)'
type: boolean
default: false

permissions:
contents: write

jobs:
build:
name: verify · package · release
runs-on: ubuntu-latest
outputs:
vsix: ${{ steps.pkg.outputs.vsix }}
publish: ${{ steps.flags.outputs.publish }}
has_vsce_pat: ${{ steps.flags.outputs.has_vsce_pat }}
has_ovsx_pat: ${{ steps.flags.outputs.has_ovsx_pat }}
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm

- run: npm ci

- name: Tag must match package.json version
if: startsWith(github.ref, 'refs/tags/v')
run: |
tag="${GITHUB_REF_NAME#v}"
pkg="$(node -p "require('./package.json').version")"
if [ "$tag" != "$pkg" ]; then
echo "::error::tag v$tag does not match package.json version $pkg"; exit 1
fi

- name: Build · verify · palette PNGs · test
run: npm run check && npm test

- name: Package VSIX
id: pkg
run: |
npx vsce package --out "alone-$(node -p "require('./package.json').version").vsix"
echo "vsix=$(ls alone-*.vsix)" >> "$GITHUB_OUTPUT"

- name: Upload VSIX artifact
uses: actions/upload-artifact@v7
with:
name: vsix
path: ${{ steps.pkg.outputs.vsix }}
if-no-files-found: error

- name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ github.token }}
run: |
# Idempotent so a re-run of this job after a downstream failure succeeds.
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" "${{ steps.pkg.outputs.vsix }}" --clobber
else
gh release create "$GITHUB_REF_NAME" "${{ steps.pkg.outputs.vsix }}" \
--title "Alone $GITHUB_REF_NAME" \
--notes "See [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/main/CHANGELOG.md) for details." \
--verify-tag
fi

- name: Publish flags
id: flags
# `secrets` is not available in a job-level `if`, so surface presence as
# outputs (booleans only — no secret material crosses the job boundary).
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
DO_PUBLISH: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.publish == true }}
run: |
{
echo "publish=$DO_PUBLISH"
echo "has_vsce_pat=$([ -n "$VSCE_PAT" ] && echo true || echo false)"
echo "has_ovsx_pat=$([ -n "$OVSX_PAT" ] && echo true || echo false)"
} >> "$GITHUB_OUTPUT"

marketplace:
name: publish · VS Code Marketplace
needs: build
if: needs.build.outputs.publish == 'true' && needs.build.outputs.has_vsce_pat == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: vsix
- name: vsce publish
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
run: npx vsce publish --packagePath "${{ needs.build.outputs.vsix }}"

open-vsx:
name: publish · Open VSX
needs: build
if: needs.build.outputs.publish == 'true' && needs.build.outputs.has_ovsx_pat == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v7
with:
node-version: 24
- uses: actions/download-artifact@v8
with:
name: vsix
- name: ovsx publish
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
run: npx --yes ovsx@1 publish "${{ needs.build.outputs.vsix }}" --pat "$OVSX_PAT"
3 changes: 3 additions & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ themes/_src/**
themes/_snapshot/**
samples/**
images/icon.svg
# README images are served from the GitHub repo (vsce rewrites relative URLs); only the icon ships
images/palette-*.png
images/screenshot-*.png
*.vsix
package-lock.json
*.md
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ New `tokenColors` rules and `semanticTokenColors` selectors in `themes/_src/base
- **FAQ** rewritten ("Why no blue or cyan?", new "Does it protect my night vision?" and "Is it colour-blind safe?").
- **`CONTRIBUTING.md`** added; the "Building the Themes (Contributors)" section moves there and grows into layout, verifier contract, palette-change and new-variant procedures. `package.json` description no longer claims dark-adaptation protection.

### Added — Release pipeline, palette images, publishing guide

- **`.github/workflows/release.yml`**: on a `v*` tag — tag/version match check → `npm run check && npm test` → `vsce package` → GitHub Release with the VSIX, then two independent publish jobs, `vsce publish` (`VSCE_PAT`) and `ovsx publish` (`OVSX_PAT`), so a failed registry can be re-run alone; a publish job skips when its secret is absent, and `workflow_dispatch` gives a dry run. CI actions bumped to `checkout@v7` / `setup-node@v7` / `upload-artifact@v7`.
- **`scripts/render-palette.mjs`** (`npm run render:palette`): dependency-free PNG palette strips per variant (`images/palette-*.png` — ladder with hex + L\*, bracket pairs, ANSI slots) shown in the README; `--check` runs in `npm run check`, CI and a new test. README images are excluded from the VSIX (`vsce` serves them from the repository).
- **`scripts/capture-screenshots.sh`**: reproducible macOS editor screenshots per variant (throwaway VS Code profile, recommended settings, `samples/demo.tsx`); README carries a labelled slot until they are captured.
- **`docs/PUBLISHING.md`**: Marketplace publisher + Azure DevOps PAT, Open VSX namespace/token, repository secrets, first-publish smoke test, screenshots, badges, troubleshooting.

### Changed — Internal: verifier v2, tests, CI

- **Verifier v2** (`scripts/verify-palette.mjs`). New checks alongside the L\* ladder, wavelength band, and key parity: an **APCA Lc** column and per-role floors (body 60 / syntax 40 / special 37 / punctuation 28 / comments 22, overridable per variant via `verify.apcaFloors` — Alone Soft declares a scaled set); **ΔE2000** on every tight ladder gap; a **colour-vision-deficiency** table (Viénot protan/deutan/tritan simulation) over the ten most confusable role pairs, passing on ΔE2000 ≥ 5 or a font-style difference; **ANSI** pairwise ΔE2000 among the eight normal terminal slots (≥ 10); and a **whole-theme wavelength scan** — every chromatic hex in every variant, not just the ten headline roles, must have a dominant wavelength ≥ 575 nm. The perceptual checks were introduced as warnings against the 1.3.1 palette (which missed several — comments Lc 16, ANSI blue/cyan ΔE 5.2, Variables/Parameter under CVD) and are hard failures from 2.0.0. Each check's severity is a one-line `POLICY` entry.
Expand Down
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ The shipped `themes/*.json` files are **generated**. Don't edit them directly
npm ci
npm run build:themes # regenerate themes/*.json from _src/
npm run verify # L* ladder, APCA floors, CVD + ANSI ΔE2000, wavelength scan, key parity, README tables
npm run check # both, in sequence
npm run check # build + verify + palette-PNG freshness
npm run render:palette # regenerate images/palette-*.png after a palette change
npm test # node --test tests/
node scripts/verify-palette.mjs --write-readme # re-render the verifier-owned README tables after a palette change
npx @vscode/vsce package # build the VSIX locally
Expand All @@ -45,6 +46,7 @@ npx @vscode/vsce package # build the VSIX locally
- **ANSI** — the eight normal terminal slots pairwise ΔE2000 ≥ 10.
- **Wavelength** — two parts. The ten ladder roles must pass the variant's declared band (`warm` = no blue/cyan hex). Separately, *every* chromatic hex in the theme — UI chrome, terminal, everything — must have a dominant wavelength ≥ 575 nm (`SCAN_MIN_NM`); neutrals below the chroma threshold are skipped. Note the whole-theme scan does not apply the declared band: a narrower band (a future `red-only` variant, say) constrains the ten roles, not the rest of the theme.
- **Parity** — every variant has exactly the same keys, rules and selectors as every other.
- **Palette PNGs** — `images/palette-*.png` must match what `scripts/render-palette.mjs` produces (`--check`).
- **README** — the tables between `<!-- verify:<name>:start/end -->` markers must match what the palette produces.

The header of the script explains the policy: since 2.0.0 every check is a hard failure. If you are deliberately moving the palette, demote the affected check to `warn` in the same PR that changes the palette and restore it before merge — do not add environment overrides.
Expand All @@ -53,7 +55,7 @@ The header of the script explains the policy: since 2.0.0 every check is a hard

1. Edit the hex in `themes/_src/variants/<name>.yaml` (or `base.yaml` if it is shared by every variant).
2. `npm run check` — read the verifier output; fix any failure rather than relaxing the threshold.
3. `node scripts/verify-palette.mjs --write-readme` and commit the README diff along with the regenerated `themes/*.json`.
3. `node scripts/verify-palette.mjs --write-readme` and `npm run render:palette`; commit the README diff and `images/palette-*.png` along with the regenerated `themes/*.json`.
4. If you touched an ANSI slot, mirror it in the four `terminal/` files (the README ANSI table is the reference).
5. Note the change under `## [Unreleased]` in `CHANGELOG.md`. Visible look changes are a minor/major bump; fixes that don't change rendered colours are patch.

Expand All @@ -74,3 +76,7 @@ Edit `base.yaml` only. Bind the new rule to an existing role token (`${tokenColo
- Conventional commit prefixes: `feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`.
- `npm run check && npm test` green locally; CI runs the same plus `git diff --exit-code themes/` (committed JSON must be what the build produces) and a `vsce package` dry run on Node 22 and 24.
- Keep generated JSON, README tables and CHANGELOG in the same PR as the palette change that caused them.

## Releasing

See [docs/PUBLISHING.md](docs/PUBLISHING.md): bump `version`, move CHANGELOG entries out of `[Unreleased]`, merge, tag `vX.Y.Z` — the release workflow packages, creates the GitHub Release and publishes to the Marketplace and Open VSX.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

**A theme for those who thrive coding alone in the dark.**

<!-- hero: images/hero.png — added with the release pipeline (see docs/PUBLISHING.md) -->
<!-- screenshot slot: replace with ![Alone — samples/demo.tsx](images/screenshot-alone.png) once captured (scripts/capture-screenshots.sh, see docs/PUBLISHING.md) -->
![Alone palette — syntax ladder, bracket pairs, ANSI slots](images/palette-alone.png)

Alone is a warm, low-luminance dark theme for people who code in dark rooms for hours. Every colour in it — syntax, UI chrome, terminal — has a dominant wavelength between 575 and 611 nm: gold, amber, olive, terracotta, dusty rose. There is no blue, no cyan, no purple, and no white text. Foregrounds sit well below the brightness of a typical dark theme, contrast is tuned with APCA rather than maxed out, and a verifier script fails the build if any of that drifts.

Expand Down Expand Up @@ -263,14 +264,20 @@ The full-featured theme with balanced contrast for extended coding sessions. Def

For pitch-black rooms and light-sensitive eyes. All syntax colours reduced ~20 % in brightness, backgrounds slightly lifted to reduce contrast. Verified against its own, lower APCA floors.

![Alone Soft palette](images/palette-alone-soft.png)

### Alone Focused

For concentration. UI chrome is muted — activity-bar badges dimmed, sidebar de-emphasised, borders hidden. Syntax highlighting unchanged. Your code takes centre stage.

![Alone Focused palette](images/palette-alone-focused.png)

### Alone Roman

The Standard palette with **no italics** — for astigmatic readers who find slanted monospace edges fringe, or anyone who simply dislikes italic code. Every rule that Standard sets in italic (comments, strings, docstrings, regex, decorators, interfaces, type parameters, namespaces, `*.defaultLibrary`, `*.async`, `this`/`self`) is upright here; the only italic left is Markdown `*emphasis*`, which is the document's own formatting. Bold keywords/escapes/errors are unchanged. Two hexes differ from Standard so the pairs that Standard tells apart with italics stay separable on colour alone under red-green colour-vision deficiency: strings `#9A8B60 → #9C8B4A` and `*.defaultLibrary` `#B08C50 → #AA884C` (both verified ΔE2000 ≥ 5 after protan/deutan simulation). What you give up is the italic-only distinctions — interfaces look like classes, library calls like local ones, async like sync.

![Alone Roman palette](images/palette-alone-roman.png)

---

## Terminal Themes
Expand Down
Loading