rustio-core 1.4.0

RustIO runtime library: HTTP, router, Postgres ORM, admin, RBAC, search, migrations, AI planner.
Documentation
/* RustIO admin — Tailwind input.
 *
 * This file is the SOURCE. The compiled output lives at
 * `rustio-core/assets/static/css/admin.css` and is baked into the
 * binary via `include_str!`. Run `make css` to rebuild after editing.
 *
 * Architecture (post-finalization)
 * --------------------------------
 * The component layer that used to live here has been pruned. The
 * single source of truth for component visuals is now the inline
 * `<style>` block in `rustio-core/assets/templates/admin/base.html`
 * — every card / button / field / table / alert / etc. is defined
 * there, keyed off `--rio-*` tokens.
 *
 * What still lives here
 * ---------------------
 *   1. `@tailwind base/components/utilities`  ← the Tailwind reset +
 *      the utility-class catalogue (`.flex`, `.gap-md`, `.w-4`, …)
 *      that every template uses for one-off layout.
 *   2. `@layer base` token blocks (`--ds-color-*` for light / dark /
 *      brand themes), `@font-face` Inter, `<html>` / `<body>` /
 *      `<h1-h3>` baseline, focus visibility.
 *   3. `@layer components` survivors — the small set of legacy
 *      classes still consumed by templates that haven't gained a
 *      v14 equivalent OR are pinned by tests:
 *        .results / .row-clickable    audit views + users_list
 *        .user-row                    users_list public-API hook
 *        .actions / .action-*         bulk-action UI (gated)
 *        .errornote                   _field_errors include
 *        .messagelist / .message-*    base.html flash banner
 *        .empty-list                  log_entries / object_history
 *        .checkbox-list / -item       user_edit Groups panel
 *        .cascade-list                confirm-delete pages
 *        .code-pill                   inline `<code>` chips
 *        .required                    test-pinned required marker
 *        .breadcrumbs                 audit views
 *        .badge-success / -warning /
 *          -danger / -neutral         user_view, users_list,
 *                                     user_confirm_delete role chips
 *        .btn-*:disabled              disabled-state for buttons
 *                                     (test-pinned `<button class=
 *                                     "btn-danger" disabled>`)
 *
 * Removed in this commit
 * ----------------------
 *   .admin-shell, .topbar*, .admin-body, .sidebar*, .content-shell,
 *   .content-area, .demo-banner*, .btn-primary, .btn-secondary,
 *   .btn-ghost, .btn-back, .btn-edit, .btn-danger (rule body),
 *   .deletelink-inline, .card, .card-compact, .module*,
 *   .object-tools*, #toolbar*, .form-row*, .form-input,
 *   .field-input, fieldset.module*, .submit-row*, .warningnote,
 *   .paginator*, #changelist*, body.login, #login-form*,
 *   .forbidden-page, .coming-soon-body, .danger-zone (legacy),
 *   .user-view*, .confirm-form .submit-row, .input, .table,
 *   .table-compact, .page-header, .page-actions, .subhead-note,
 *   .hero-icon*. Every removed rule had zero template consumers
 *   after the finalization migration.
 */

@tailwind base;
@tailwind components;
@tailwind utilities;

/* --- Phase 2 design tokens — themed CSS variables -------------------- *
 * Channel format `R G B` (space-separated, no `rgb()` wrapper) is what
 * `rgb(var(--ds-color-*) / <alpha-value>)` in tailwind.config.js
 * expects. Layer values for light/rust themes are intentionally empty
 * — only the dark theme stacks surfaces by depth.
 * --------------------------------------------------------------------- */
@layer base {
  :root {
    /* themes.light from docs/design-system.json */
    --ds-color-bg:             244 246 251; /* #F4F6FB */
    --ds-color-surface:        255 255 255; /* #FFFFFF */
    --ds-color-surface-muted:  249 250 251; /* #F9FAFB */
    --ds-color-text:            17  24  39; /* #111827 */
    --ds-color-text-muted:     107 114 128; /* #6B7280 */
    --ds-color-border:         229 231 235; /* #E5E7EB */
    --ds-color-primary:         17  24  39; /* #111827 */
    --ds-color-accent:          59 130 246; /* #3B82F6 */
    --ds-color-success:         22 163  74; /* #16A34A */
    --ds-color-warning:        217 119   6; /* #D97706 */
    --ds-color-danger:         220  38  38; /* #DC2626 */
    --ds-color-layer-1:        255 255 255;
    --ds-color-layer-2:        255 255 255;
    --ds-color-layer-3:        255 255 255;
    --ds-color-layer-4:        255 255 255;
  }

  .dark {
    /* themes.dark from docs/design-system.json */
    --ds-color-bg:              43  45  51; /* #2B2D33 */
    --ds-color-layer-1:         49  52  58; /* #31343A */
    --ds-color-layer-2:         56  59  66; /* #383B42 */
    --ds-color-layer-3:         64  68  76; /* #40444C */
    --ds-color-layer-4:         72  76  85; /* #484C55 */
    --ds-color-surface:         56  59  66; /* #383B42 */
    --ds-color-surface-muted:   49  52  58; /* #31343A */
    --ds-color-text:           245 245 245; /* #F5F5F5 */
    --ds-color-text-muted:     184 184 192; /* #B8B8C0 */
    --ds-color-border:          74  78  87; /* #4A4E57 */
    --ds-color-primary:        245 245 245; /* #F5F5F5 */
    --ds-color-accent:          91 140 255; /* #5B8CFF */
    --ds-color-success:         52 211 153; /* #34D399 */
    --ds-color-warning:        251 191  36; /* #FBBF24 */
    --ds-color-danger:         248 113 113; /* #F87171 */
  }

  /* `.theme-brand` is the official activation class for the optional
   * brand-accented theme. `.theme-rust` is kept as a backward-compatible
   * alias so any caller still toggling that class keeps working. */
  .theme-brand,
  .theme-rust {
    --ds-color-bg:             255 250 248; /* #FFFAF8 */
    --ds-color-surface:        255 255 255; /* #FFFFFF */
    --ds-color-surface-muted:  255 245 241; /* #FFF5F1 */
    --ds-color-text:            17  24  39; /* #111827 */
    --ds-color-text-muted:     107 114 128; /* #6B7280 */
    --ds-color-border:         239 230 225; /* #EFE6E1 */
    --ds-color-primary:         17  24  39; /* #111827 */
    --ds-color-accent:         194  65  12; /* #C2410C */
    --ds-color-success:         22 163  74; /* #16A34A */
    --ds-color-warning:        217 119   6; /* #D97706 */
    --ds-color-danger:         220  38  38; /* #DC2626 */
  }
}

/* --- @font-face: Inter ------------------------------------------------ */
@layer base {
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/static/fonts/Inter-Regular.woff2") format("woff2");
  }
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/static/fonts/Inter-Medium.woff2") format("woff2");
  }
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/static/fonts/Inter-SemiBold.woff2") format("woff2");
  }
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/static/fonts/Inter-Bold.woff2") format("woff2");
  }

  html { @apply text-body; }
  body {
    /* Light theme keeps the legacy bg-paper / text-metal brand pair;
     * <html class="dark"> swaps to the JSON `bg` / `text` tokens. */
    @apply bg-paper text-metal antialiased font-sans dark:bg-bg dark:text-text;
    font-feature-settings: "ss01", "tnum";
    line-height: 1.6;
  }

  h1 { @apply text-h1 font-heading font-bold tracking-tight text-metal leading-tight; }
  h2 { @apply text-h2 font-heading font-semibold tracking-tight text-metal; }
  h3 { @apply text-h3 font-heading font-semibold text-metal; }

  a { @apply text-rust hover:text-rust-hover transition-colors; }
  ::selection { @apply bg-rust text-white; }
}

/* --- Focus visibility -------------------------------------------------
 * Hide the default browser focus ring globally; restore via
 * `:focus-visible` so it only shows for keyboard navigation.
 */
@layer base {
  :focus { outline: none; }
  :focus-visible { @apply ring-2 ring-accent/30 outline-none; }
}

/* --- Component layer survivors --------------------------------------- *
 * Only rules with at least one template consumer (or a test pin) live
 * here. The full design system is in base.html's inline <style>.
 * Each rule below carries a one-line "Used by:" note so a future
 * audit can spot orphans.
 * --------------------------------------------------------------------- */
@layer components {

  /* Used by: log_entries.html, object_history.html breadcrumbs.
   * Other admin templates use the v14 `.crumbs` component instead. */
  .breadcrumbs {
    @apply text-small text-gray-600 mb-4;
  }
  .breadcrumbs a { @apply text-gray-600 hover:text-rust no-underline transition-colors; }

  /* Used by: users_list.html (test-pinned `class="results row-clickable"`),
   * log_entries.html, object_history.html.
   * `table-layout: fixed` was previously layered in base.html's inline
   * <style> (post-truncation-fix); folded in here so `.results` has
   * exactly one definition site. */
  .results {
    @apply w-full border-collapse bg-surface border border-border rounded-md text-body overflow-hidden;
    table-layout: fixed;
  }
  .results th, .results td {
    @apply px-4 py-3.5 text-left border-b border-gray-100;
  }
  .results th {
    @apply bg-surface-muted text-metal font-semibold text-caption uppercase tracking-wider;
  }
  .results tbody tr:nth-child(even) td { @apply bg-surface-muted/50; }
  .results tr:hover td { @apply bg-rust/5; }
  .results.row-clickable td { @apply p-0; }
  .results.row-clickable .row-link {
    @apply block px-4 py-3.5 text-metal no-underline transition-colors;
  }
  .results.row-clickable .row-link:hover { @apply no-underline; }
  .results.row-clickable td:first-child .row-link { @apply font-semibold; }
  .results.row-clickable .row-link.help { @apply text-gray-600 text-small; }

  /* Used by: users_list.html (`<tr class="user-row">`). Public-API
   * hook so projects can target users_list rows from CSS. */
  .user-row { @apply transition-colors; }

  /* Bulk-action UI — gated until the handler is wired (Phase 8). */
  .actions {
    @apply px-3.5 py-2.5 mb-3 bg-surface-muted border border-border rounded-md flex items-center gap-3 text-small text-gray-700;
  }
  .actions select {
    @apply px-2.5 py-1.5 rounded-md border border-border bg-surface text-small;
  }
  .actions button {
    @apply px-3 py-1.5 rounded-md bg-metal text-white text-small font-semibold border-0 cursor-pointer hover:bg-metal-dark transition-colors;
  }
  .action-counter { @apply text-text-muted text-small ml-auto; }
  .action-checkbox { @apply w-10 text-center pr-0; }
  .action-checkbox input[type=checkbox] { @apply w-4 h-4 rounded border-gray-300 text-rust focus:ring-accent; }

  /* Used by: includes/_form_field.html — required-field marker.
   * The literal classname is asserted by
   * `form_renders_required_marker_humanised_label_and_cancel`. */
  .required { @apply text-rust font-semibold ml-0.5; }

  /* Used by: includes/_field_errors.html — global error banner above
   * forms (the per-field error block lives in `_form_field.html` and
   * uses the `.field .err` rule from base.html). */
  .errornote {
    @apply px-4 py-3.5 mb-4 bg-red-50 text-red-800 border border-red-200 rounded-md text-body;
  }

  /* Used by: base.html flash banner (`<div class="messagelist message-{kind}">`). */
  .messagelist { @apply px-4 py-3.5 mb-4 rounded-md text-body border; }
  .message-success { @apply bg-emerald-50 text-emerald-800 border-emerald-200; }
  .message-warning { @apply bg-amber-50 text-amber-900 border-amber-200; }
  .message-error   { @apply bg-red-50 text-red-800 border-red-200; }

  /* Used by: log_entries.html, object_history.html empty-state copy. */
  .empty-list {
    @apply px-7 py-12 text-center text-gray-600 bg-surface-muted border border-dashed border-gray-300 rounded-md text-body;
  }

  /* Used by: user_edit.html Groups panel. */
  .checkbox-list { @apply flex flex-col gap-2.5; }
  .checkbox-list label.checkbox-item { @apply flex items-center gap-2.5 text-body font-normal m-0; }

  /* Used by: confirm_delete.html, user_confirm_delete.html, group_confirm_delete.html. */
  .cascade-list { @apply list-disc pl-5 my-3 space-y-1.5 text-body; }

  /* Used widely: inline `<code>` chip in confirm-delete pages, hints,
   * dashboard, user_view, group_edit. */
  .code-pill {
    @apply bg-paper px-1.5 py-0.5 rounded text-small font-mono;
  }

  /* Used by: user_view.html, users_list.html, user_confirm_delete.html
   * for role / status chips. The v14 `.badge` (boolean cells in
   * list.html) is a different component. */
  .badge-success {
    @apply inline-flex items-center px-2.5 py-1 rounded-md
           bg-success/10 text-success
           text-caption font-semibold uppercase tracking-wider;
  }
  .badge-warning {
    @apply inline-flex items-center px-2.5 py-1 rounded-md
           bg-warning/10 text-warning
           text-caption font-semibold uppercase tracking-wider;
  }
  .badge-danger {
    @apply inline-flex items-center px-2.5 py-1 rounded-md
           bg-danger/10 text-danger
           text-caption font-semibold uppercase tracking-wider;
  }
  .badge-neutral {
    @apply inline-flex items-center px-2.5 py-1 rounded-md
           bg-surface-muted text-text-muted
           text-caption font-semibold uppercase tracking-wider;
  }

  /* Disabled-state sibling for the v14 button rules in base.html.
   * Lives here because it pairs with the test-pinned
   * `<button class="btn-danger" disabled>` markup in
   * user_confirm_delete.html — the button's enabled colour comes
   * from base.html's `.btn-danger`, the disabled visual comes from
   * this rule. Same key works for any v14 button that ever needs
   * to render disabled. */
  .btn-primary:disabled, .btn-primary[disabled],
  .btn-secondary:disabled, .btn-secondary[disabled],
  .btn-text:disabled, .btn-text[disabled],
  .btn-danger:disabled, .btn-danger[disabled] {
    @apply opacity-50 cursor-not-allowed pointer-events-none;
  }
}