Document the vendored tools, the theme helpers and the auth leftovers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-03 12:43:07 +02:00
1 parent 483ed271e0
commit ca060f25fb
1 file changed
+37 -11
+37 -11
View File
@@ -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. `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 `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. 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 **Nothing in the build or the CI may download from github.com.** Tools live in the repo, and the CSS build runs
against a pinned sha256. To upgrade, change the versions and hashes there. 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 - 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. 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 - `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`. 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`. 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 - `bash tools/tailwind.sh watch` rebuilds the CSS live next to `dotnet watch`.
daisyUI's `llms.txt` (its component reference) into `.tools/tailwind/`. - Tailwind scans every file `.gitignore` doesn't exclude (minus `tools/`, `wwwroot/vendor/`, `Docs/`), and only
- Tailwind scans every file `.gitignore` doesn't exclude, and only generates the classes it finds written out generates the classes it finds written out **whole**. Never build a class name by concatenation or interpolation (`"btn-" + size`, `$"btn-{size}"`). Write
**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. every variant as a literal, e.g. in a switch arm.
Ionicons 4.5 (`wwwroot/vendor/ionicons.min.css`, `<i class="ion-md-...">`) is loaded as its own stylesheet. Ionicons 4.5 (`wwwroot/vendor/ionicons.min.css`, `<i class="ion-md-...">`) is loaded as its own stylesheet.
@@ -68,6 +76,12 @@ Ionicons 4.5 (`wwwroot/vendor/ionicons.min.css`, `<i class="ion-md-...">`) is lo
from localStorage, so there is no flash before Blazor boots. After that, CascadingState calls it through 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, `AppStatusService.ApplyTheme` whenever the settings change. With `PreferSystemTheming` it leaves `data-theme` off,
and the OS decides. 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: - `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 - `neo-raised` / `neo-inset` / `neo-flat`, with sizes `neo-2xs` / `neo-xs` / `neo-sm` / `neo-md` (these replace the
old `neomorph is-nxsmall` classes); 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<TValue>`, Blazor's `InputBase`, so `@bind-Value` and EditForm - Value-holding fields derive from `DInputBase<TValue>`, Blazor's `InputBase`, so `@bind-Value` and EditForm
validation work. Everything else derives from `DComponentBase` (`Class`, pass-through attributes, `Cx(...)`). 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 - 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 - 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. 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. - 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)`. `nameof(Type)`.
- **IndexedDB** (DnetIndexedDb, database `data`) is reached through `IStorage`. Its schema is declared in - **IndexedDB** (DnetIndexedDb, database `data`) is reached through `IStorage`. Its schema is declared in
`GenericExtensions.AddIndexedDb()`, and you must bump `WithVersion` when you change it. `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 - The schema is at version 2, which added the `ClientLogs` store that `IStorage.AddLog` writes. Before it, every log
fail silently (they are wrapped in try/catch). write failed silently.
**Auth.** **Auth.**
- `TokenAuthStateProvider` builds the auth state from the `AuthData` token in localStorage. - `TokenAuthStateProvider` builds the auth state from the `AuthData` token in localStorage.
- `SetToken` is mostly commented out, so there is no real login flow yet. - `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.** **JS interop.**
- `wwwroot/main.js` defines `window.*` helpers. `Services/AppStatusService.cs` calls them, mostly through the - `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 ## 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 `.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: host. The steps:
1. Publish the site. 1. Publish the site.