# `mix admin_css`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mix/tasks/admin_css.ex#L5)

Builds the two BUILD-TIME stylesheets, into the host app's `priv/static`.

One command, no prerequisites for whoever installs this CMS:

  * compiles `admin.css` — every Tailwind utility the dashboard, the page builder and the login
    page use, scanned out of the host app and every extension;
  * compiles `public.css` — the same for the host's own marketing pages, from the host's web tree
    alone;
  * installs the vendored Tailwind browser build, which the page builder's canvas loads;
  * installs the admin's web fonts.

Both sheets exist because their classes live in `.ex` files and cannot change without a deploy.
The third audience — a SITE — has classes in database rows that change without one, which is why
`MishkaCmsCore.Runtime.Compilers.TailwindCompiler` builds those at runtime. Three audiences, three
sheets, no overlap; they used to be a single 2.4 MB file that every one of them downloaded.

All three ship inside this library's `priv/admin_assets/`, so a host gets them by depending on it.
Nothing is fetched at build time and nothing is expected to be there already.

## `source(none)` is load-bearing

Without it Tailwind v4 auto-detects sources from the CSS file's own tree in ADDITION to the
`@source` lines below, and walks into `priv/cms_ui_kits/chelekom.json` — an 11.7 MB kit bundle whose
every component template is a string inside JSON. Tailwind cannot tell that from markup, so it
compiled a rule for every class of all 244 components plus 1,288 heroicons: 2,511,005 bytes and an
11-second build, for classes the admin chrome never renders. Those components appear only inside
the editor's canvas, which loads Tailwind Live and generates them on demand.

With `source(none)`, only the explicit `@source` lines below are scanned. 589,694 bytes, 836 ms.

## Why a task and not a `config :tailwind` profile

Because the `@source` globs have to be GENERATED. Tailwind v4 skips gitignored entries while
WALKING a directory, and a CMS install routinely gitignores vendored path dependencies and always
gitignores `deps/` — so `@source "__extensions/**"` silently matches nothing for them. Absolute
paths do not help: the filter applies to the walk, not to the form of the path. Naming each
directory outright is what bypasses it, and that list can only be built by listing the disk.

That is not a hypothetical. Compiling this without the per-directory globs produced a dashboard
whose tables had lost their responsive layout, because one Gervaz line
(`max-[980px]:overflow-visible!` and friends) lived in a gitignored extension.

## Why the admin is compiled at all, when a site is not

A site's classes live in database rows that change without a deploy, which is why
`MishkaCmsCore.Runtime.Compilers.TailwindCompiler` builds site CSS at runtime. The admin's classes
live in templates and are fixed when the release is built, so compiling them per request bought
nothing — it cost a 275 KB browser JIT and an in-browser compile on every admin page.

The page builder still loads that JIT, and always will: a class typed into the inspector, or a kit
component whose classes no build ever scanned, has to exist a frame later.

## Why neither sheet may go into the host's `app.css`

`TailwindCompiler.build_css_content_static_base/0` inlines that file wholesale into the base every
site's CSS is built on. Compiling the dashboard into it would ship the entire admin inside every
public page's stylesheet — which is the bug this whole split exists to undo.

## Usage

    mix admin_css              # once, as part of assets.build / assets.deploy

In development the endpoint watches each audience in its own process — see `watch/1`. `--watch` is
refused here, because Tailwind's watch never returns and only the first of the two sheets would
ever be built.

# `watch`

```elixir
@spec watch(keyword()) :: non_neg_integer()
```

Rebuilds ONE audience on every change, for the endpoint's `:watchers` list.

The admin used to be styled by the browser JIT, which generated a class the moment it appeared in
the DOM — so an edit to an admin template needed no build step at all. Serving compiled CSS means
something has to notice that edit, and without this a developer adds a class, sees nothing happen,
and has no reason to suspect the stylesheet.

## One audience per watcher, and why it cannot be both

Tailwind's `--watch` does not return. A single call that built the admin sheet and then the public
one therefore never reached the second: `admin.css` rebuilt on every keystroke while `public.css`
sat untouched for days, so an edit to a marketing page produced no rule and the class silently did
nothing — the exact failure described above, fixed for the admin and left live for the public half.

Each audience gets its own entry, which Phoenix runs in its own process:

    watchers: [
      admin_css: {Mix.Tasks.AdminCss, :watch, [[only: :admin]]},
      public_css: {Mix.Tasks.AdminCss, :watch, [[only: :public]]}
    ]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
