Skip to main content

README.md

# Skua

Form-first UI components for Phoenix LiveView (1.1+) on Phoenix 1.8+:
rich select, date picker, OTP input, dialogs, menus, tables, toasts and
top-layer panels — server-authoritative, viewport-aware, themeable from 12 CSS
tokens, with **zero third-party JavaScript** (one JS import, ~9 KB min+gzip).

A daisyUI replacement you install and manage through `mix` tasks built on
Igniter — including `--strip-daisy` to remove Phoenix 1.8's bundled daisyUI.

## Install

**One command** — Igniter adds the dep and wires everything (CSS, JS hooks,
component imports, flash → Skua toasts, strips the default Phoenix navbar, and
scaffolds an editable starter home at `/`):

```bash
# existing app
mix igniter.install skua

# brand-new app — one-time: install the project-generator archive first
mix archive.install hex igniter_new
mix igniter.new my_app --with phx.new --install skua
```

(`igniter.new` and `phx.new` are Mix *archives*, not built-in tasks — if either
reports "could not be found", install it once with `mix archive.install hex
igniter_new` / `mix archive.install hex phx_new`. Or skip archives entirely and
use the plain-Mix path below.)

Skua replaces daisyUI, so the installer **removes Phoenix 1.8's bundled daisyUI
by default** — it deletes the vendored files and bridges daisy's color utilities
(`bg-base-100`, `text-error`, …) to Skua tokens so existing markup keeps
resolving. Keep daisyUI instead with `--no-strip-daisy`:

```bash
mix igniter.install skua --no-strip-daisy
```

(If your daisyUI config was customized with nested rules, the installer leaves
it alone and prints a manual step rather than risk mangling it.)

**Without Igniter** — add the dep and run the plain Mix task (Igniter is
optional; the task still works):

```elixir
# mix.exs
{:skua, "~> 0.7.0"}
```

```bash
mix deps.get
mix skua.install            # --strip-daisy optional
mix phx.server              # open /
```

Every step is idempotent — re-run any time after `mix deps.update skua`. The
installer degrades to printed manual steps if your app diverges from the default
layout. Local-testing notes in [guides/local-testing.md](guides/local-testing.md).

## Authentication

`mix skua.gen.auth` runs `mix phx.gen.auth` and then applies a flow on top. The
generated code is yours to edit.

```bash
mix skua.gen.auth --auth magic_link    # default — Phoenix's passwordless flow
mix skua.gen.auth --auth otp           # one-time-code login (no passwords)
mix skua.gen.auth --auth password_otp  # password + OTP on one login screen
mix skua.gen.auth --auth custom        # stock phx.gen.auth, nothing added
```

The **`otp`** and **`password_otp`** flows ship a production-grade one-time-code
login: CSPRNG codes (unique every time, never `:rand`), SHA-256-hashed storage,
single-use under concurrency (`FOR UPDATE` + transaction), constant-time compare,
a session-fixation guard, enumeration-safe responses, and built-in
[Hammer](https://hexdocs.pm/hammer) rate limiting on both the request and verify
steps — all tunable in the generated `config/config.exs`. Code length and expiry
are flags:

```bash
mix skua.gen.auth --auth otp --otp-length 8 --otp-expiry 5
```

All three login flows (`magic_link`, `otp`, `password_otp`) wire a Resend
production mailer so the link/code actually delivers off-box (dev/test keep
Swoosh's local mailbox; prod reads `RESEND_API_KEY` at runtime — see the
generated `.env.example`).

Two dev conveniences are wired automatically: the OTP login/verify screens show
a dev-only "Open mailbox ↗" link to Swoosh's local mailbox so the sign-in code is
one click away, and the generated app **creates + migrates its database on first
boot in dev** — so `mix deps.get && mix phx.server` just works, no manual
`mix ecto.create && mix ecto.migrate`. Both are strictly dev-gated (no effect in
test or prod).

## Pages

`mix skua.gen.pages` scaffolds editable starter pages and wires their routes (run
it after `skua.install`, and after `skua.gen.auth` for the auth-aware nav and the
dashboard's auth gate):

```bash
mix skua.gen.pages
```

- a shared, auth-aware `SiteNav` — section links plus Register (ghost button) /
  Sign in (primary button), or a signed-in user's email + Settings + Log out. It
  replaces the stock `phx.gen.auth` nav, so there's no duplicate top bar, and it
  is **responsive**: inline from the `sm` breakpoint up, collapsing into a
  keyboard-accessible hamburger menu (a native `<details>`) on narrow screens.
- `HomeLive` at `/` — a full-height hero with the live Phoenix + Skua version
  badges above the app name, and a full-height interactive showcase: buttons that
  fire each toast kind and open a dialog/drawer/popover/menu/tooltip, plus cards,
  badges, an alert, tabs, a table, a list, an accordion, and a form.
- `DashboardLive` at `/dashboard` — an authenticated page with a sidebar
  (Dashboard / Settings + Log out) and Skua stat cards.

Everything generated is yours to edit; re-running overwrites the pages and leaves
the routes and the nav injection in place.

## SEO & discovery

`mix skua.gen.seo` scaffolds two editable, **public-facing** discovery files and
wires them to be served:

```bash
mix skua.gen.seo
```

- `priv/static/robots.txt` — public crawl rules. The conventional
  scoped/authenticated prefixes a Skua app generates (`/users`, `/dashboard`,
  `/dev`) are **Disallowed by default**, plus a commented `Sitemap:` line to fill
  in. A stock phx.new robots.txt is replaced; one this task already wrote is left
  alone so your edits survive re-runs.
- `priv/static/llms.txt` — an [llms.txt](https://llmstxt.org) template (name,
  summary, link sections) describing your **public** content for LLMs and agents.
  Never overwritten once it exists.

Both are served by `Plug.Static`, which sits *above* the router — so they bypass
your `:browser`/auth pipelines entirely and can't leak scoped or authenticated
routes. Public-only by default; point them at real content by editing the two
files. Serves at `/robots.txt` and `/llms.txt`.

### Per-page meta

`mix skua.gen.pages` also wires `Skua.Components.Meta.seo_meta/1` into the root
layout `<head>`, driven by per-page assigns. The public homepage gets a
`page_description` and full Open Graph + Twitter tags; the authenticated
dashboard sets `page_robots: "noindex, nofollow"`. Control any page's meta from
its `mount/3`:

```elixir
assign(socket,
  page_title: "Pricing",
  page_description: "Simple, transparent pricing.",
  page_image: "https://example.com/og/pricing.png",
  page_canonical: "https://example.com/pricing"
)
```

Each tag is omitted when its assign is unset, so **scoped/authenticated pages
emit nothing by default** (or set `page_robots` to keep them out of search).

## Themes

Skua ships **100 prepackaged themes** — each a coherent token set (dark + light,
contrast-checked, with its own radius/spacing/fonts/body-size). Bake one in:

```bash
mix skua.install --theme greenfield
mix skua.themes                       # list all 100
```

`--theme <name>` writes that theme's tokens into your `assets/css/app.css`
(`:root` for dark, `:root[data-theme="light"]` for light), so the whole component
kit re-skins from one place — and it stays yours to edit. Plain `mix skua.install`
(no flag) keeps Skua's built-in palette; themes are opt-in, and re-running with a
different `--theme` swaps the block.

The set spans refined originals (`greenfield`, `midnight`, `brutalist`,
`terminal`, `paper`, `nord`, `mono`, `sunset`, `solar`, `contrast`), community
palettes (`dracula`, `gruvbox`, `tokyo-night`, `catppuccin`, `rose-pine`,
`monokai`, `one-dark`, `ayu`, `everforest`, `cobalt`…), retro / print /
design-movement / nature / bold sets, and 50 app-style homages under fictional
names (`potion`, `discordia`, `gabgpt`, `notify`, `strapped`, `staycation`,
`dualingo`…). Run `mix skua.themes` for the full list.

## Repository layout

- `PLAN.md` — the full project plan (architecture decisions, installer spec,
  strip-daisy design, release pipeline, roadmap).
- `assets/css/skua.css` — the token + component CSS layer (the stable artifact).
- `lib/` — components, the install mix tasks (`Skua.Install.Patches` holds the
  shared install logic), and supporting modules.
- `_component_defaults/` — reference prototypes the library is being built
  from (styled-layer HTML demo, LiveView hook/component prototypes, and the
  skua.sh brand system). Not shipped in the hex package.

## License

MIT — see [LICENSE.md](LICENSE.md).