diff --git a/CLAUDE.md b/CLAUDE.md index a1720cc..2e9cc12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,22 +40,30 @@ dotnet publish decePubClient.csproj -c Release -o publish # static site lands `global.json` pins SDK 10.0.100 with `rollForward: latestFeature`. There are no tests and no linter. -## CSS: Tailwind 4 + daisyUI 5, no Node +## CSS: Tailwind 4 + daisyUI 5, vendored, offline `Styles/app.css` is compiled into `wwwroot/css/app.css` by the Tailwind CSS standalone binary with the daisyUI plugin. `app.css` is gitignored and regenerated on every build. -- `tools/tailwind.sh` downloads Tailwind 4.3.3 and daisyUI 5.7.47 into the gitignored `.tools/tailwind/`, checking each - against a pinned sha256. To upgrade, change the versions and hashes there. +**Nothing in the build or the CI may download from github.com.** Tools live in the repo, and the CSS build runs +without network access. NuGet restore still uses nuget.org. +- `tools/tailwind/` holds the pinned tools: + - Tailwind 4.3.3 as `tailwindcss-linux-x64.xz`; + - daisyUI 5.7.47 (`daisyui.mjs`, `daisyui-theme.mjs`); + - daisyUI's component reference, `daisyui-llms.txt`. +- `.gitattributes` keeps these files byte-exact. +- `tools/tailwind.sh prepare` checks them against the sha256 values pinned in the script, and unpacks the binary into + the gitignored `.tools/tailwind/`. It needs `xz`. +- Only linux-x64 is vendored. +- The script's header explains how to upgrade or add a platform. - The `TailwindCompile` target in the csproj runs it, so `dotnet build` / `publish` are enough, in CI as well. Debug builds use `--optimize`, Release builds `--minify`. Pass `-p:SkipTailwind=true` to skip it. - `IncludeTailwindOutput` adds the generated file to `Content` before static web assets are resolved. Without that, a clean-clone publish would ship `app.css` without its `.gz`/`.br` copies and outside `service-worker-assets.js`. The deploy workflow checks for `css/app.css.gz`. -- `bash tools/tailwind.sh watch` rebuilds the CSS live next to `dotnet watch`. `bash tools/tailwind.sh docs` fetches - daisyUI's `llms.txt` (its component reference) into `.tools/tailwind/`. -- Tailwind scans every file `.gitignore` doesn't exclude, and only generates the classes it finds written out - **whole**. Never build a class name by concatenation or interpolation (`"btn-" + size`, `$"btn-{size}"`). Write +- `bash tools/tailwind.sh watch` rebuilds the CSS live next to `dotnet watch`. +- Tailwind scans every file `.gitignore` doesn't exclude (minus `tools/`, `wwwroot/vendor/`, `Docs/`), and only + generates the classes it finds written out **whole**. Never build a class name by concatenation or interpolation (`"btn-" + size`, `$"btn-{size}"`). Write every variant as a literal, e.g. in a switch arm. Ionicons 4.5 (`wwwroot/vendor/ionicons.min.css`, ``) is loaded as its own stylesheet. @@ -68,6 +76,12 @@ Ionicons 4.5 (`wwwroot/vendor/ionicons.min.css`, ``) is lo from localStorage, so there is no flash before Blazor boots. After that, CascadingState calls it through `AppStatusService.ApplyTheme` whenever the settings change. With `PreferSystemTheming` it leaves `data-theme` off, and the OS decides. +- Change the theme flags through `PageSettings.SetDarkMode` / `SetGray` / `SetHue` and + `CascadingState.UpdatePageSettings`, which keep the flags consistent: + - choosing light or dark leaves system theming; + - picking a hue leaves grey. +- `AppStatusService.IsDarkTheme()` tells which variant is showing. +- Settings → General edits every theme setting. NavMenu's toggle and slider are shortcuts to the dark and hue ones. - `Styles/neo.css` holds the neumorphic layer: - `neo-raised` / `neo-inset` / `neo-flat`, with sizes `neo-2xs` / `neo-xs` / `neo-sm` / `neo-md` (these replace the old `neomorph is-nxsmall` classes); @@ -88,7 +102,11 @@ all of them in every theme. When you add or change one: - Value-holding fields derive from `DInputBase`, Blazor's `InputBase`, so `@bind-Value` and EditForm validation work. Everything else derives from `DComponentBase` (`Class`, pass-through attributes, `Cx(...)`). - Open/active state lives in C# (`@bind-IsOpen`, `@bind-Active`) and is rendered with daisyUI's `*-open` modifier - classes. No JS interop. `DCalendar` is the one exception: it wraps the vendored Cally web component. + classes. No JS interop. + - What daisyUI does with scripts has a script-free equivalent here: `DCheckbox.Indeterminate` renders + `aria-checked="mixed"`, and `DCarousel`'s navigation links to the slides' ids, which Blazor scrolls to. + - `DCalendar` is the one exception: it wraps the vendored Cally web component (`wwwroot/vendor/cally.js`). +- Images and links in `/styleguide` stay local (`wwwroot/imgs/styleguide/`). - Don't name a parameter after an HTML attribute (`Title`, `Style`, `Id`, `Label`, ...). Blazor matches attributes to parameters without regard to case, so the parameter would swallow the attribute. - Components don't use `Localizer` or `CascadingState`; their user-visible strings are parameters. @@ -123,13 +141,18 @@ take it as `[CascadingParameter] CascadingState`. It handles: `nameof(Type)`. - **IndexedDB** (DnetIndexedDb, database `data`) is reached through `IStorage`. Its schema is declared in `GenericExtensions.AddIndexedDb()`, and you must bump `WithVersion` when you change it. -- Known gap: `IStorage` reads and writes a `ClientLogs` store that the schema never declares, so the error log writes - fail silently (they are wrapped in try/catch). +- The schema is at version 2, which added the `ClientLogs` store that `IStorage.AddLog` writes. Before it, every log + write failed silently. **Auth.** - `TokenAuthStateProvider` builds the auth state from the `AuthData` token in localStorage. - `SetToken` is mostly commented out, so there is no real login flow yet. -- `AddApiAuthorization` and `Pages/Authentication.razor` are leftovers from the Blazor template. +- `Pages/Logout.razor` signs out through `TokenAuthStateProvider.LogoutAsync`, which clears the token and the local + database. +- `AddApiAuthorization`, `Pages/Authentication.razor` (`/authentication/{action}`) and `RedirectToLogin` are leftovers + from the Blazor OIDC template. That route's `RemoteAuthenticatorView` throws `InvalidCastException`, because the + registered `AuthenticationStateProvider` is `TokenAuthStateProvider`. The Login links that point there need a real + login page first. **JS interop.** - `wwwroot/main.js` defines `window.*` helpers. `Services/AppStatusService.cs` calls them, mostly through the @@ -141,6 +164,9 @@ take it as `[CascadingParameter] CascadingState`. It handles: ## Deployment +Both workflows check the repo out with a plain `git fetch` from this Gitea, not `actions/checkout`, because the runner +would download that action from github.com. Never add a `uses:` step. + `.gitea/workflows/deploy.yml` runs on a `v*` tag or a manual dispatch, on a self-hosted runner that is also the web host. The steps: 1. Publish the site.