Commit Graph

24 Commits

Author SHA1 Message Date
jules
a770faf6eb fix(ai): clear conversation actually clears
useChat exposes reset() which calls setMessages(opts.initialMessages),
and the AI page passes initialLive.current as initialMessages — a ref
captured once on mount from localStorage.

resetAndClear was calling reset() then clearLive(). Sequence problem:

  reset()       → setMessages(initialLive.current)  // populated old array
  clearLive()   → localStorage.removeItem(LIVE_KEY) // does nothing to memory

The ref still held the original messages, so reset re-seeded them and
the conversation appeared to "come back" the moment you typed anything
(or sometimes immediately, depending on render timing).

Fix: blank the ref, clear localStorage, and call setMessages([])
directly. reset() is no longer needed at this call site so it's been
dropped from the useChat destructure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:59:32 +10:00
jules
2a68389121 button: support Radix-style asChild via base-ui render prop bridge
The project uses @base-ui/react, whose Button has no asChild prop —
just a `render` prop that takes a React element to merge into. About
14 call sites across the routes still use the Radix-shaped
`<Button asChild><Link to="…">…</Link></Button>` pattern, which until
now was producing nested-button DOM violations and asChild leaking as
a DOM attribute.

Bridges asChild → render inside the Button wrapper:

  <Button asChild><Link to="/login">Sign in</Link></Button>

…now renders as a single <a class="…btn classes…">Sign in</a> instead
of <button asChild><a>…</a></button>.

No call-site changes required; consumers keep the Radix ergonomic and
get correct DOM under the hood.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:57:53 +10:00
jules
7eb5093071 fix(ai): unbreak model dropdown — base-ui Trigger doesn't take asChild
The composer's model picker had <DropdownMenuTrigger asChild><button>...
which is the Radix Slot pattern. Project uses @base-ui/react where
Menu.Trigger has no asChild prop and renders its own <button>, so the
result was nested-button-inside-button (DOM-nesting violation) plus
asChild leaking as an unknown DOM attribute (React warning).

Dropped the inner <button> and put className/data-action straight on
the Trigger. Visual output identical, no more console errors.

This pattern is used by ~14 other routes (Button asChild + Link),
mostly behind sign-in-required states. They're broken too but rarely
fire — separate followup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:43:00 +10:00
jules
066a16bb8b ai: scope console theme to content wrapper, fix font loading
Two bugs in the previous /ai redesign:

1. theme="console" on AppShell put the entire shell (sidebar, appbar,
   appbar dropdowns, the lot) inside [data-theme="console"], so the
   console palette + JetBrains Mono override leaked into the sidebar
   and made light mode look broken on /ai. Scoped now: the AppShell
   stays in skyrise (so light/dark toggle keeps working everywhere),
   and only the route content area gets data-theme="console" via an
   inner wrapper.

2. The Google Fonts @import inside console.css was being silently
   dropped because @import rules must precede all other rules in the
   final bundle, and skyrise's content lands first. Moved JetBrains
   Mono + Newsreader into app.css's top-level @import url() alongside
   the existing Inter/Instrument Sans/Geist Mono families.

Atmosphere ::before was also position: fixed, which painted the grain
overlay across the whole viewport (including the sidebar) regardless
of where data-theme lived. Now position: absolute on the wrapper, with
isolation: isolate to keep z-index local.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:39:51 +10:00
jules
4f699bb90e ai: redesign /ai surface as Mission Console
Replaces the conventional chat aesthetic on /ai with a brutalist-mono
operator deck. The page now reads as a flight recorder — turn numbers
in the gutter, hairline rules, sodium-amber phosphor primary on
deep-ink ground, vim-style modeline at the foot.

Type system is the design's load-bearing element:
- JetBrains Mono for everything system-y (operator lines, signatures,
  modeline, session ids, tool calls)
- Newsreader serif for the agent's prose only — the synthesis voice
  literally lifts off the page in a different family from the machine
  voice. Operator and agent are typographically inseparable from their
  speaker.

Layout changes:
- Sticky session header with a giant base36 session id ("3K9P · A4C2")
  and a metadata strip showing agent, model, turn count, status. The
  status pill flips colour: AMBER on stream, ROSE on awaiting confirm,
  MINT on ready, MUTED on mock.
- Empty state is no longer the apologetic "How can I help you today?".
  It's "ATLAS. standing by." in oversize mono with the agent name in
  italic serif amber, a hairline divider, and a single one-liner
  instruction prefixed with ›. Lines stagger in via animation-delay.
- Operator turns: monospace, 14px, sodium-amber › prompt, no bubble.
  Hangs from a left gutter with T01/T02… turn number + UTC timestamp.
- Agent turns: serif, 17px/1.55, with a tiny mono signature underneath
  ("atlas» 03:14:08Z · recv"). Cyan accent column instead of amber.
- Composer: terminal frame (square, 1px border, focus ring is amber
  glow). Internal ›_ prompt mark in front of the textarea, mono input.
- Bottom modeline: utc clock + turn count + estimated tokens on the
  left, keyboard hints on the right. Streaming flips the right side
  to a pulsing phosphor bar + STREAM label.

Atmosphere details:
- 2px scanline overlay (very faint, 1.2% opacity)
- Corner phosphor blooms (amber top-right, cyan bottom-left)
- Inline SVG turbulence grain (3.5% opacity) over the whole theme
- Cursor blink animation on the prompt mark
- Consolas-tier ligatures on the mono via JetBrains Mono ss01/calt

All theming scoped via [data-theme="console"] — picks up automatically
because /ai's AppShell now passes theme="console". Other routes are
untouched. Tool-call cards from @crema/agent-ui inherit the palette
via overridden CSS variables (--card, --border, --primary, etc) plus
a [data-slot="tool-call-card"] override for the frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:32:22 +10:00
jules
c0eb85d2fe LLM configs: edit/delete buttons available for all rows
Previously gated to tenant-owned rows (tenant_id != null), which made
seed-from-catalog rows uneditable since they default to platform scope
on the backend. The backend doesn't enforce extra ownership rules on
update/delete either, so the gate was a UI overreach.

Buttons now appear on every row. Tooltips clarify when a row is a
platform default so the operator knows the change applies broadly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:59:56 +10:00
jules
b397bbcb9e LLM configs: vault secret picker (Select from api_key secrets)
Replaces the free-text secret name input with a Select populated from
/api/v1/admin/secrets, filtered to category=api_key + enabled. Each
option shows the secret name plus its description for context.

Includes "(none — keyless / local)" for lmstudio-style configs and a
"Type a name…" escape hatch for secrets that don't exist in the vault
yet (the proxy will fail loudly at request time if the name is wrong,
which is the right behaviour — better than silently saving a config
that can't authenticate).

Secrets are fetched once on panel load alongside configs/catalog/usage,
not per modal-open, so the dialog opens instantly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:49:47 +10:00
jules
5dfceeff94 Settings/LLM: unified panel with per-row active toggle, edit, spend
Reworks the LLM settings surface based on UX feedback. Drops the
separate "Active LLM (this session)" card — its functionality is now
inline on each saved config as a star toggle (writes the same
localStorage key the Assistant reads via @crema/llm-providers-ui's
saveSettings, so the existing assistant code picks the change up
without any plumbing).

Per-row controls now include:
- Star: make this config active for the current browser
- Switch: enable/disable server-side
- Pencil: edit (modal, not inline-expand)
- Trash: delete (with confirm)
- Spend (30d): cost + request count, sourced from
  /api/v1/ai/llm/usage/by-model and matched on (provider, model)

Other improvements:
- Add wizard moved to a Dialog modal instead of pushing the list
  around. Same form handles edit.
- Empty state: "Seed from catalog" button creates a curated starter
  set (GPT-4o mini/4o, Sonnet 4.6, Haiku 4.5, DeepSeek V4 Flash, LM
  Studio) so first-time operators don't face a blank panel.
- Catalog dropdown picks now auto-fill input/output costs as you
  switch models, so the rates always reflect the chosen model unless
  manually overridden.
- The lib's full settings card (system prompt, transport, context
  budget) is still reachable for advanced cases — collapsed into a
  <details> below the panel.

Adds llm-configs.ts: getUsageByModel + findSpend helper for the
per-row spend lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:47:45 +10:00
jules
bfe61c220a Default to dark mode and small text on first load
The bootstrap script previously fell back to the OS color-scheme
preference and left font-scale unset until the user picked one. For
admin work — dense tables, lots of small text, monitoring dashboards —
dark + sm is the better starting point and matches what most operators
end up choosing anyway.

Users who've already picked a theme/font keep their stored preference;
this only affects fresh sessions where localStorage has no value yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:41:54 +10:00
jules
8e07f4b9c0 Settings: real model dropdown with cost hints + Custom escape hatch
The previous datalist-on-input approach was fragile — Safari hid the
suggestions, and there was no visual cue that a dropdown existed.
Replace with a proper Select populated from the catalog. Each option
shows the per-1M-token rates inline so operators see cost while
choosing. "Custom…" switches to free-text for models the catalog
doesn't know about, with a "Catalog" button to flip back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:25:49 +10:00
jules
baf42c4cec Settings: server-side LLM configurations + 30d spend roll-up
Replaces the localStorage-only LLM settings with a persisted catalogue
backed by /api/v1/admin/llm-configurations. The Settings → LLM screen
now has two cards:

- "Saved configurations" — full CRUD against the server. Each row shows
  provider/model/secret/published per-1M-token costs. Add wizard
  auto-fills costs from the curated catalog. One-click "Import local"
  button promotes any pre-existing localStorage settings into a server
  row, then clears the local store.
- "Active LLM (this session)" — the existing LLMProvidersSettingsCard,
  scoped down to "what does the Assistant use right now" (still
  localStorage; per-operator).

Spend (30d) tile in the configurations card header reads
/api/v1/ai/llm/usage/summary and surfaces total cost / requests /
tokens. First visible cost roll-up in the admin UI.

New module app/lib/arcadia/llm-configs.ts: typed CRUD client,
catalog lookup, computeCostCents helper (mirrors the server's
LlmConfiguration.compute_cost_cents/3), and getUsageSummary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:06:29 +10:00
jules
29030c9e72 Wire health probes, host stats, and LLM proxy round-trip
Three things from the latest arcadia-app pull:

- health.ts: client for /api/v1/health{,/:service,/detailed,/host}.
  monitoring.tsx now reads real per-subsystem probe state instead of
  synthesizing it from indirect signals (rate limits, sessions, jobs).
- New Host tab on Monitoring with KPI tiles + per-core CPU bars,
  load-avg cards, memory + swap usage, and per-mount disk bars,
  backed by /api/v1/health/host.
- llm-proxy.ts: typed errors (secret_disabled, ip_not_allowed, etc.)
  and a probeProxy() that round-trips a 1-token chat. settings.tsx's
  "Test connection" in proxy mode now exercises the real endpoint
  instead of just confirming the adapter built. Contract doc flipped
  from "not yet implemented" to "implemented".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:05:22 +10:00
jules
0fcb9e40f1 Add Buckets, Monitoring, Memberships, Networking, SSO, Announcements, Status page
Full set of admin surfaces on top of /platform/* and /admin/* endpoints,
plus a migration of /assistant onto @crema/llm-providers-ui.

Buckets (/buckets):
  S3-level CRUD over /platform/buckets — list, create, delete (with the
  6-digit confirmation flow the backend enforces), per-bucket configure
  for versioning / CORS rules / policy JSON, plus an object browser
  with FileGrid/FileList from @crema/file-ui and presigned-URL reveal.
  Storage-config picker scopes the view to one credential at a time.

Monitoring (/monitoring):
  Live dashboard. Service health board derived from indirect signals
  (status-ui OverallStatus + ComponentRow). KPI tiles for sessions,
  jobs, audit. Tabs: background jobs (Donut + BarChart + retry recent),
  sessions (Sparkline of last 24h sign-ins), audit activity (BarChart
  of severity / top resource types), infrastructure (DO summary +
  WorldMapSvg coloured by droplet region + droplet list + Spaces),
  rate limits. 30s auto-refresh.

Memberships (/memberships):
  M:N glue between users and tenants over /admin/memberships. Add /
  edit / suspend / activate / remove with role multi-select.

Networking (/networking):
  Tabs over /platform/{firewalls,vpcs,domains,floating_ips}.
  Read/delete on firewalls, read on VPCs, full DNS-record CRUD, and
  inline assign/unassign for floating IPs.

SSO (/sso):
  /sso/identity-providers CRUD with PEM cert as write-only field, plus
  /sso/sessions list with destroy.

Announcements (/announcements):
  /admin/announcements CRUD. Platform-wide vs per-tenant audience,
  schedule windows, dismissible + active toggles.

Status page (/status-page):
  /admin/status-page/{components,incidents,subscribers}. Components
  CRUD, incidents with timeline + post-update + resolve flow,
  subscriber list. Public preview at the top using StatusBoard +
  IncidentTimeline from @crema/status-ui.

Assistant migration:
  /assistant now uses @crema/llm-providers-ui (provider catalog +
  vault key resolution) instead of ~/lib/llm-settings. Same async
  buildAdapter() flow used by /ai. The legacy lib file is now
  unreferenced and can be removed when ready.

New sibling libs wired (cloned from CremaUIStudio):
  lib-file-ui, lib-card-ui, lib-dashboard-ui, lib-chart-ui,
  lib-map-ui, lib-status-ui.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 07:55:46 +10:00
jules
7ba415d78e Wire @crema/llm-providers-ui: multi-provider picker + AI persistence
Replaces the single-base-URL LLM settings with the new providers lib
(OpenAI, Anthropic, DeepSeek, Qwen, LM Studio). Settings/LLM hosts the
catalog-aware card; the /ai route builds adapters via buildAdapter()
and resolves API keys from the arcadia vault per-call (direct mode).
Anthropic skips the /v1/models probe (no such endpoint) and uses
catalog defaults; failed probes for keyed providers fall back to the
catalog instead of dropping to mock.

AI conversation now persists across navigation and refresh via a new
crema.ai.live localStorage key (separate from the compact-snapshot
key). useChat hydrates from initialMessages on mount, saves on every
change, and "Clear conversation" wipes both state and storage.

Vite needs explicit resolve.alias for @crema/llm-ui and
@crema/llm-providers-ui — when a sibling lib imports another @crema/*,
tsconfigPaths can't resolve it (the importing file isn't in this
project's tsconfig scope).

Adds docs/LLM_PROXY_CONTRACT.md describing the
POST /api/v1/ai/llm/chat endpoint the backend needs for proxy mode
(keys never leave the server). Direct mode works against today's
arcadia; proxy mode unblocks once that endpoint ships.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:50:23 +10:00
jules
a907e25a7c Add Storage, Users, Secrets, Webhooks, Scheduled tasks, Audit log screens
Full management surfaces for the platform-admin tenant, mirroring the
existing Tenants pattern (DataTable + row actions + create/edit dialogs +
ConfirmDialog for destructive ops, all data-action tagged for the
command bus, useRegisterAdminContext publishing for the assistant).

- Storage (/storage): backends + credentials. Write-only secret fields,
  Validate/Activate/Deactivate/Set-default/Mark-degraded/Maintenance.
- Users (/users): tabs for Users, Invitations, Roles. Per-user View
  drawer with profile, role add/remove, API keys (one-time reveal on
  create), usage + quota.
- Secrets (/secrets): /api/v1/admin/secrets — create/rotate/rollback,
  versions dialog, enable/disable, generate-value helper.
- Webhooks (/webhooks): CRUD, pause/resume, regenerate-secret with
  one-time reveal, send test event, deliveries dialog.
- Scheduled tasks (/scheduled-tasks): cron CRUD, run-now trigger,
  enable/disable, expandable run history.
- Audit log (/activity): replaces the empty stub. Filter by severity,
  resource type, date range; click for full JSON detail.

All endpoints are hand-rolled HTTP because most aren't covered by the
generated OpenAPI typed paths yet — switch to arcadia.typed.* when the
backend wires them into OpenApiSpex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:50:09 +10:00
jules
45fa130951 Rich output rendering: GFM markdown, tool-result blocks, card blocks
Three layers:

1. GFM markdown — add remark-gfm so tables, task lists, strikethrough,
   autolinks render properly. Style table elements (overflow-aware
   container, muted header, divider rows). Render `[ ]` task list items
   as visible checkboxes.

2. Structured tool-result rendering — new `tool-result-renderers.tsx`
   dispatches by tool name to render a small UI block beneath each
   ToolCallCard:
   - list_tenants → table with status pills + plan column
   - get_tenant → tenant detail card
   - get_platform_stats → KPI tiles (total + per-status)
   - list_audit_log → timeline rows with actor_type + action
   - list_users → user list with role chips
   - suspend_tenant / activate_tenant → tenant card with action confirm
   ToolCallCard collapses by default — operators expand for raw JSON.

3. Custom ```card``` blocks the LLM can emit inline:
   - {"kind":"pill","status":"…"} — status pill
   - {"kind":"stat","label":"…","value":…} — stat tile
   - {"kind":"callout","tone":"info|warning|danger|success",…} — callout
   Malformed blocks fall through to the prose unchanged. Client strips
   well-formed blocks from prose and renders them as components.

Domain primer updated to teach the model the card schemas and remind it
NOT to re-render tool-result data as markdown tables (that's done
automatically — it should add commentary only).

Layers are independent: 1 + 2 always work; 3 is purely additive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:39:06 +10:00
jules
b9a163c7cc Shrink AgentAvatar to sm + dim on idle instead of removing
Persistent presence is less jumpy than pop-in/out. When the model isn't
actively doing anything, the avatar collapses to the `sm` size variant,
loses its activity label, and dims to 50% opacity. Springs back to
full-size + label when activity resumes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:28:54 +10:00
jules
1b2e85cdad Wire @crema/agent-ui: ToolCallCard + AgentAvatar with activity
The /ai surface now renders agent-ui primitives instead of homegrown
tool/typing widgets:

- AgentAvatar with activity (thinking / working / waiting / speaking /
  idle) replaces TypingIndicator. Pulses while the model is generating,
  shows "waiting" while a write is held for confirmation, "working" while
  a confirmed write is executing, "speaking" once tokens are streaming.
- ToolCallCard renders each native tool_call with typed status (pending
  / running / success / error). Built from the assistant message's
  toolCalls plus the matching tool result message. Tool messages no
  longer render standalone — absorbed into their parent assistant turn.
- Empty assistant bubbles (no prose, only tool_calls) collapse so the
  ToolCallCards carry the visual weight.

Wiring: add @crema/agent-ui path entry to tsconfig and @source line to
app.css. Sibling lib-agent-ui must be cloned next to arcadia-admin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:25:36 +10:00
jules
e5cd85fff3 Add 5 more admin tools + inline write confirmation flow
New tools in admin-tools.ts:
- list_audit_log({limit?}) — recent audit entries (terse: actor, action,
  target, timestamp). Hits /api/v1/admin/audit-log.
- get_platform_stats() — aggregate counts (tenants by status + by plan),
  composed locally from list_tenants until arcadia exposes a real stats
  endpoint.
- list_users({limit?}) — users in the currently-selected tenant via
  /api/v1/users.
- suspend_tenant({slug}) — write tool, suspends a tenant by slug.
- activate_tenant({slug}) — write tool, restores a suspended/deactivated
  tenant.

Inline write confirmation:
- New ConfirmCard component renders below the assistant message that
  proposed a write. Shows tool(args) and Confirm/Deny buttons.
- classifyCalls() splits LLM tool calls into reads/writes. Auto-loop
  runs reads immediately; for any writes, holds them in pendingConfirm
  state instead of dispatching.
- On Confirm: runs writes with allowWrites:true, prepends prior read
  results, continueChat to produce the final answer.
- On Deny: synthesises tool-result messages telling the model the user
  declined; continueChat so it can acknowledge.
- Arcadia-knowledge primer updated to tell the model the user sees an
  inline confirm card automatically — it shouldn't ask in prose first.

Wired into both /ai and /assistant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:16:41 +10:00
jules
fe93f2766c Wire AI assistant to arcadia: domain primer, tool calling, admin context
Make /ai and /assistant operate as the platform admin's assistant
against arcadia-app's API:

- Add `arcadia-knowledge.ts` — domain primer (multi-tenant Phoenix
  backend, tenant lifecycle, platform_admins identity, etc.) baked into
  every system prompt.
- Add `admin-tools.ts` — curated tool registry exposing `list_tenants`
  and `get_tenant`, callable via OpenAI-native function calling. Tools
  hit arcadia through `useArcadiaClient()` and inherit the operator's
  JWT + tenant header. `runLLMToolCalls()` returns `tool` role messages
  ready to push back into history.
- Add `admin-context.ts` — runtime registry pages publish to so the
  assistant can answer factual questions about live UI state without
  scraping the DOM. Tenants page registers its summary on mount.
- Replace generic Vibespace personas (Atlas/Forge/Inkwell/Pilot/Cursor)
  with arcadia-flavoured ones: Operator, Auditor, Triage, Analyst,
  UI Operator. Auto-migrate stored agents from the legacy set.
- /assistant: build admin preface (role + primer + persona + ctx) and
  pass it as the `useChat` system at construction. Pass `tools` on every
  `send()`. Auto-loop reads `toolCalls` off the streaming assistant
  message and uses `continueChat()` to push tool results.
- /ai: same wiring (this is the canonical admin chat surface; the user
  prefers its look).
- MessageBody renders tool-result cards (role: "tool") and a "Called X"
  pill on assistant messages with toolCalls. Strips Qwen-style
  `<tool_call>` XML from prose when the tags were converted to
  structured calls.
- Extend ThreadMessage with the `tool` role + tool-call metadata so
  conversations round-trip through localStorage.
- Tenants page: row actions get `data-action="tenant-<slug>-{suspend,
  activate,deactivate}"` (via lib-table-ui's new dataAction prop);
  registers tenant summary into admin-context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:08:47 +10:00
jules
e7cb8c942b feat: add /ai nav entry; pre-bundle arcadia/phoenix deps to fix 504s
Adds an "AI" item to the sidebar nav (mirrors Vibespace's /ai route,
already wired in routes.ts). vite.config.ts now lists openapi-fetch,
phoenix, lucide-react, clsx, tailwind-merge, class-variance-authority
in optimizeDeps.include and adds explicit resolve.alias entries for
the arcadia and crema sibling libs so Vite resolves them on first
load instead of triggering a re-optimize mid-session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:26:45 +10:00
jules
b513fbe405 refactor: tenants surface uses @crema/table-ui + search-ui + feedback-ui
Replaces the hand-rolled shadcn primitives in app/routes/tenants.tsx
with manifest libs: DataTable + ActionsCell + BadgeCell + DateCell +
Pagination + useTable from table-ui, SearchInput from search-ui, and
AlertBanner + ConfirmDialog + EmptyState + LoadingOverlay from feedback-ui.

Behaviour preserved: same columns, same row actions, same suspend/
deactivate/activate handlers. Gains: built-in sortable columns,
pagination controls, density toggle support, proper confirmation dialogs
for destructive actions, accessible empty/loading/error states.

Wires three new sibling libs into tsconfig.json paths and app.css
@source lines: lib-table-ui, lib-search-ui, lib-feedback-ui, plus
lib-auth-ui (used by lib-arcadia-auth-ui after its refactor).

Corrects the earlier miss of not checking docs/LIBS.md before rolling
custom UI — the manifest already had what we needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:00:33 +10:00
jules
080597d046 feat: tenants admin surface
Adds the first admin screen — /tenants — listing tenants from
GET /api/v1/admin/tenants with search, status badges, plan, created
date, and a per-row menu with suspend / activate / deactivate actions.

Hand-typed shapes in app/lib/arcadia/tenants.ts because arcadia's admin
endpoints aren't yet covered by /api/openapi (same 'ok'-placeholder
issue documented in lib-arcadia-client/scripts/sync-spec.mjs). When
the spec gains coverage, switch to arcadia.typed.GET(...) and drop the
manual types.

Trims the inherited consumer-app sidenav (Resources / Assistant / AI /
Library) down to admin-shaped items: Overview, Tenants, Audit log,
Settings. The unused route files stay in place; they just aren't linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:36:42 +10:00
jules
f8cbf142b5 init: arcadia-admin — admin webapp for arcadia-core, cloned from vibespace
Initial commit. Spun up via the docs/STARTER.md recipe: cp from vibespace,
reset git, rename package, set brand to "Arcadia Admin" with Shield icon
in app/lib/identity.ts.

Inherits the full Crema sibling-lib wiring including @crema/arcadia-client
(typed HTTP + Phoenix Channels realtime against arcadia-core) and
@crema/arcadia-auth-ui (login/signup/password-reset/2FA forms). The /login
route already renders <LoginForm>; <ArcadiaProvider> in app/root.tsx reads
VITE_ARCADIA_URL (default localhost:4000) and VITE_ARCADIA_TENANT (default
"default").

CLAUDE.md and README rewritten to frame this as the admin app for
arcadia-core. docs/STARTER.md removed — arcadia-admin is a leaf consumer,
not a downstream starter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:28:39 +10:00