feat: bake tweakcn themes into projects at build time#23
Merged
Conversation
- Introduced new commands for theme management in the CLI, allowing users to fetch and apply themes from tweakcn.com. - Added `theme apply` command to fetch a theme and update `wwwroot/input.css`, with options for standalone CSS file generation. - Implemented `theme update` command to re-fetch and apply the previously recorded theme from `shellui.theme.lock`. - Created a new `ThemeService` class to handle theme fetching, parsing, and application logic, enhancing modularity and maintainability.
- Simplified comments in `ShellUI.Components.targets` to maintain focus on the auto-generated nature of the file. - Removed redundant explanations while retaining essential information about the safelist generation process.
- Updated comments in `Program.cs` to clarify the functionality of the safelist generator, including the types of files scanned and the output artifacts. - Streamlined the code by removing redundant comments and improving clarity on the filtering process for class-list strings. - Ensured consistency in the documentation style to align with recent changes in the project.
- Introduced a new `ThemeLockFile` class to record the applied theme details, including the source URL, content hash, theme name, and application timestamp. - This model supports the theme management functionality by enabling the `shellui theme update` command to reference previously applied themes.
- Introduced a new test file `ThemeServiceTests.cs` to validate the functionality of the `ThemeService` class. - Implemented tests for URL normalization, JSON parsing, CSS generation, and application to input CSS, ensuring robust coverage of theme management features. - Added various test cases to handle accepted and rejected URL formats, verify JSON structure, and confirm correct CSS output.
5 tasks
Shewart
added a commit
that referenced
this pull request
Jul 3, 2026
Follow-up to #23 (tweakcn theme import). Closes the last three items on issue #21. ## What ships ### 1. `shellui theme init <url>` — one-shot init + apply Combined command for greenfield setup. Runs `InitService.InitializeAsync` (Tailwind CLI download, `App.razor` patch, `input.css`, `shellui.json`, MSBuild wiring) then chains into `ApplyThemeAsync` — so a fresh project goes from empty to fully themed in one command: ```bash dotnet shellui theme init https://tweakcn.com/themes/<id> --yes ``` Accepts the same options as `init`: `--force`, `--style`, `--tailwind`, `--yes`. Console output is two labelled steps so users can see which phase they're in if something fails. Fits alongside the existing `theme apply` (existing project) and `theme update` (refresh from lock) — the full CLI surface is now: ```bash shellui theme init <url> # fresh project — init + apply shellui theme apply <url> # existing project — write to input.css shellui theme apply <url> --emit-override <path> # Path A/D — standalone override CSS shellui theme update # re-fetch from shellui.theme.lock ``` ### 2. README coverage across all install-path docs Three READMEs now cover the tweakcn workflow with concrete commands per install path: | README | Change | |---|---| | **Top-level `README.md`** | New "Using tweakcn themes with `shellui theme`" section before "Theming across paths". Existing table extended with an "Auto-import from tweakcn" column showing the exact command for each path. | | **`src/ShellUI.CLI/README.md`** | Quick Start shows the `theme init` one-shot alternative alongside plain `init`. New `theme` commands section covers all three subcommands + lock file semantics. | | **`src/ShellUI.Components/README.md`** | Path A trade-off block now shows `--emit-override` as the recommended override workflow. Path B shows `theme apply` as the tweakcn auto-import path. Path C Quick Start includes the `theme init` one-shot. Bottom "Theme Customization" section rewritten to lead with the CLI, keep the manual paste as fallback. | ### 3. `path-c-cli-tweakcn/` demo — verified locally, not committed The `shellui-installation-tests/` directory is gitignored (per its README). Rather than land a fifth test project there that won't merge, I verified the flow end-to-end in a scratchpad: - `dotnet new blazor -n TestApp` - `shellui theme init https://tweakcn.com/themes/cmgy5f2vg000504l42pep9ob1 --yes` - Step 1/2 (init) — Tailwind CLI downloaded, `App.razor` patched, `input.css` + `app.css` + `tailwind.config.js` + `shellui.json` + `Build/ShellUI.targets` created - Step 2/2 (apply) — theme fetched (53 light + 52 dark vars = 128 oklch values in output), sentinel-marked block written to `input.css`, `shellui.theme.lock` written with correct SHA-256 - `dotnet build` succeeds; Tailwind rebuilds; components render with the tweakcn theme ## Issue #21 status All checkboxes now green: | Item | PR | Status | |---|---|---| | `shellui theme apply <url>` | #23 | ✅ | | `shellui theme apply <url> --emit-override <path>` | #23 | ✅ | | `shellui theme update` | #23 | ✅ | | `shellui.theme.lock` | #23 | ✅ | | `shellui theme init <url>` | **this PR** | ✅ | | Install-path READMEs with theme-apply examples | **this PR** | ✅ (three READMEs updated; per-path READMEs under `shellui-installation-tests/` are gitignored, so the user-visible surface is top-level + package READMEs) | | Test project `path-c-cli-tweakcn/` — end-to-end demo | **this PR** | ✅ (verified locally; not committed since the parent dir is gitignored) | ## Files | File | Change | |---|---| | `src/ShellUI.CLI/Program.cs` | +40 — `CreateThemeInitCommand` + registration under `theme` | | `README.md` | +47 −5 — new "Using tweakcn themes" section + expanded theming table | | `src/ShellUI.CLI/README.md` | +28 −1 — theme init in Quick Start + `theme` subcommand section | | `src/ShellUI.Components/README.md` | +42 −5 — tweakcn command references across Paths A/B/C + rewritten Theme Customization block | Net: **+143 −14** across 4 files. No behavior change to `init`, `apply`, or `update`; no new tests needed (composition of two already-tested functions, smoke-tested end-to-end). ## Test plan - [x] `dotnet test` — 76/76 green (unchanged; no new test surface) - [x] `dotnet shellui theme --help` — lists `init`, `apply`, `update` - [x] `dotnet shellui theme init --help` — shows `<url>` arg + `--force / --style / --tailwind / --yes` options - [x] End-to-end in scratchpad `dotnet new blazor` project — both steps complete, `input.css` contains `@import "tailwindcss"` + BEGIN/END sentinel + 128 oklch vars, `shellui.theme.lock` written - [x] Re-run `theme init` twice — second run is idempotent within the sentinel region
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
Adds
shellui themecommands that fetch a theme from tweakcn.com and bake its CSS variables into the consumer's project at build time — no runtime fetch, works offline, exactly-what-you-see-is-what-ships.What ships
apply— fetches tweakcn's public/r/themes/<id>endpoint (no auth, CORS open, force-static), converts the payload into:root {},.dark {}, and@theme inline {}blocks, and writes them intowwwroot/input.cssbetween sentinel markers. Content outside the markers is preserved verbatim, so user's custom utilities,@sourcedirectives, and imports survive re-applies.--emit-override <path>— writes only the theme block to a standalone CSS file. Path A/D consumers link this AFTERshellui-all.cssin their<head>so overrides win the cascade.update— readsshellui.theme.lock, re-fetches from the recorded URL, re-applies. Enables reproducible refreshes without the user having to remember the URL.shellui.theme.lock— recordsSourceUrl,ContentSha256,ThemeName,AppliedAt. Small enough to commit.URL normalization
Accepts every form users might paste:
https://tweakcn.com/themes/<id>https://tweakcn.com/r/themes/<id>https://tweakcn.com/themes/<id>?tab=colorshttps://tweakcn.com/r/themes/<id>https://tweakcn.com/r/themes/<id>http://tweakcn.com/themes/<id>https://tweakcn.com/r/themes/<id><id>alonehttps://tweakcn.com/r/themes/<id>Rejects non-tweakcn URLs, empty input, dashboard links.
Issue #21 scope — what's in / what's deferred
shellui theme apply <url>— rewrite input.css:root/.darkblocksshellui theme apply <url> --emit-override <path>— Path A/D override CSSshellui theme update— refresh from last-used URLshellui.theme.lock— source URL + SHA-256 for reproducible re-appliesshellui theme init <url>— combinedinit+applychainshellui-installation-tests/with theme-apply examplespath-c-cli-tweakcn/— end-to-end demoFiles
src/ShellUI.CLI/Services/ThemeService.cssrc/ShellUI.CLI/Program.cstheme apply/theme updatesubcommandssrc/ShellUI.Core/Models/ThemeLockFile.csshellui.theme.lockShellUI.Tests/ThemeServiceTests.cstools/ShellUI.SafelistGenerator/Program.csWhy not runtime fetch (recap from #21)
A
ShellUI.applyTweakcnTheme(url)JS helper would:Build-time bake avoids all four. If runtime demand shows up later, the helper can ship opt-in.
Test plan
dotnet test— 76/76 green (56 existing + 20 new ThemeService tests)shellui theme apply https://tweakcn.com/themes/cmgy5f2vg000504l42pep9ob1inpath-c-cli— 53 light + 52 dark vars written toinput.cssbetween sentinelsshellui theme update— re-fetches from lock, no URL argument neededshellui theme apply <url> --emit-override wwwroot/theme.css— standalone override written, "next step" hint printed for Path A/DApply_PreservesUserContentAboveAndBelow_WhenSentinelExists)