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 +[![CI](https://github.com/thorstenalpers/OpenThemeGenerator/actions/workflows/ci.yml/badge.svg)](https://github.com/thorstenalpers/OpenThemeGenerator/actions/workflows/ci.yml) +[![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](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", "{}");