diff --git a/.gitattributes b/.gitattributes
index 0190ccd..ef9b2ad 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -9,3 +9,25 @@
*.bat text eol=crlf
*.sh text eol=lf
+
+###############################################################################
+# Agent tooling. It is configuration for the machines that help write this, not
+# part of the program: kept out of the language statistics, and out of the
+# archives GitHub builds for a release.
+###############################################################################
+.claude/** linguist-vendored export-ignore
+.agents/** linguist-vendored export-ignore
+.github/** export-ignore
+
+###############################################################################
+# Generated, and large enough to bury a diff. `registry-themes.ts` is written by
+# `scripts/fetch-registry-themes.mjs` and `reference/theme.css` by
+# `scripts/write-reference-theme.mjs`; neither is edited by hand.
+###############################################################################
+src/lib/theme/registry-themes.ts linguist-generated
+reference/theme.css linguist-generated
+package-lock.json linguist-generated
+
+# Binary, so git offers no line diff and no merge it could get wrong.
+*.png binary
+*.ico binary
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..892f815
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,46 @@
+name: CI
+
+# Every pull request, and every push to main so the badge reflects what is actually on the branch.
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ web:
+ name: Web
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run lint
+ - run: npm run check
+ - run: npm test
+ - run: npm run build
+
+ host:
+ name: Host
+ # The Tauri host is built for Windows, so its tests run where it ships.
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy, rustfmt
+ - uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: src-tauri
+ - run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check
+ - run: cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
+ - run: cargo test --manifest-path src-tauri/Cargo.toml
diff --git a/README.md b/README.md
index 4668d7a..c4095e3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
+
+
# OpenThemeGenerator
+[](https://github.com/thorstenalpers/OpenThemeGenerator/actions/workflows/ci.yml)
+[](LICENSE)
+
A desktop theme generator for shadcn — and for anything else that reads CSS custom properties.
Pick a seed colour, and the generator derives a complete light and dark palette: the shadcn token
@@ -140,6 +145,11 @@ program is the hosted assistant, and the local binary is the default.
## Development
+You need **Node 22**, a **Rust toolchain**, and on Windows the **WebView2 runtime** — which ships
+with Windows 11 and every recent 10. Nothing else: the icons, the reference stylesheet and the
+imported theme list are all produced by scripts in this repository rather than fetched at build
+time.
+
```bash
npm install
npm run dev
@@ -155,16 +165,34 @@ starts drifting from the first. For the real thing:
npm run start
```
-| Command | What it does |
-| ------------------------------------------------- | -------------------------------------------------------- |
-| `npm run check` | `svelte-check` against the app tsconfig |
-| `npm run lint` | prettier and eslint |
-| `npm test` | vitest — colour maths, the generator, and every exporter |
-| `npm run app:build` | the NSIS installer |
-| `npm run icons` | regenerates the icon set from `scripts/make-icons.mjs` |
-| `npm run reference:theme` | rewrites `reference/theme.css` from a built-in preset |
-| `cargo test --manifest-path src-tauri/Cargo.toml` | the host's own tests |
+| Command | What it does |
+| --------------------------------------------------- | -------------------------------------------------------- |
+| `npm run check` | `svelte-check` against the app tsconfig |
+| `npm run lint` | prettier and eslint |
+| `npm run format` | writes the formatting rather than checking it |
+| `npm test` | vitest — colour maths, the generator, and every exporter |
+| `npm run build` | the static front end |
+| `npm run app:build` | the NSIS installer |
+| `npm run icons` | regenerates the icon set from `scripts/make-icons.mjs` |
+| `npm run reference:theme` | rewrites `reference/theme.css` from a built-in preset |
+| `cargo test --manifest-path src-tauri/Cargo.toml` | the host's own tests |
+| `cargo clippy --manifest-path src-tauri/Cargo.toml` | the lints CI fails on |
+
+## Contributing
+
+`main` is protected: changes arrive as a pull request, and CI has to be green before it can merge.
+The workflow runs the table above — lint, types, tests and build on Linux, and `cargo fmt`,
+`clippy` and the host's tests on Windows, where the app actually ships.
+
+Two things the tests care about more than style:
+
+- **Every theme this app derives clears 4.5:1** on every surface/label pair, in both modes. So does
+ `app.css`, the window's own chrome, which has its own test because the generator cannot keep it
+ honest. Imported themes are exempt — they are other people's designs, carried as published, and
+ the gallery reports what they score instead of retinting them.
+- **Nothing reaches the network at runtime.** The imported theme list is generated into the
+ repository by `scripts/fetch-registry-themes.mjs` and read from disk.
## Licence
-MIT.
+[MIT](LICENSE).
diff --git a/src-tauri/src/project.rs b/src-tauri/src/project.rs
index 870285c..de3188c 100644
--- a/src-tauri/src/project.rs
+++ b/src-tauri/src/project.rs
@@ -106,7 +106,7 @@ fn rank(name: &str) -> u8 {
}
}
-fn collect(root: &Path, current: &Path, depth: usize, found: &mut Vec) {
+fn collect(current: &Path, depth: usize, found: &mut Vec) {
if depth > MAX_DEPTH {
return;
}
@@ -125,7 +125,7 @@ fn collect(root: &Path, current: &Path, depth: usize, found: &mut Vec)
// hold a git worktree of the whole project, and scanning that reads the same tokens a
// second time under a different path — half the prompt budget spent on a copy.
if !name.starts_with('.') && !SKIP.contains(&name) {
- collect(root, &path, depth + 1, found);
+ collect(&path, depth + 1, found);
}
} else if is_wanted(&path, name) {
found.push(path);
@@ -207,7 +207,7 @@ pub fn scan(root: &str) -> AppResult {
}
let mut found = Vec::new();
- collect(&base, &base, 0, &mut found);
+ collect(&base, 0, &mut found);
found.sort_by_key(|path| {
let name = path
.file_name()
@@ -356,7 +356,10 @@ mod tests {
write("src/app.css", "@import 'tailwindcss';");
write("components.json", "{}");
- write(".claude/worktrees/copy/src/app.css", "@import 'tailwindcss';");
+ write(
+ ".claude/worktrees/copy/src/app.css",
+ "@import 'tailwindcss';",
+ );
write(".claude/skills/vendored/package.json", "{}");
write("node_modules/some-package/package.json", "{}");