Skip to content

refactor(dashboard): modular styles, TypeScript foundation, shared editor shells - #613

Merged
SantiagoDePolonia merged 10 commits into
mainfrom
chore/front-end-refactoring
Jul 30, 2026
Merged

refactor(dashboard): modular styles, TypeScript foundation, shared editor shells#613
SantiagoDePolonia merged 10 commits into
mainfrom
chore/front-end-refactoring

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Front-end refactoring pass over the Svelte 5 dashboard:

  • Stylesdashboard.css (2,453 lines) split into 14 ordered modules under src/styles/; dashboard.css is now an @import entry whose order preserves the cascade. Pure partition: the emitted CSS bundle is byte-identical. No preprocessor added on purpose — scoped styles + custom properties + native nesting cover the need with zero new dependencies.
  • Shared editor shell — new EditorDialog organism + FormField molecule + EnabledToggle atom replace the modal shell all 11 editors hand-rolled. Also makes the Escape-under-auth-dialog guard uniform (five editors previously discarded the form when Escape was pressed under the auth dialog).
  • Store request ladder — new $lib/api/adminCrud.js (loadAdminList / sendAdminMutation) replaces the fetch/submit/delete guard branches seven CRUD stores duplicated, applying the guards in the one correct order: stale first (a stale response never touches state — some stores previously checked 503 first and could clobber the availability flag from an old API key's response), then unavailable, then errors with silent-401 loads.
  • Component splits — ProviderStatusCard 428→239 (+ProviderStatusCardDetails), ConversationDrawer 377→147 (+ChatMessage), AuditEntrySummary 328→272 (+AuditAttemptTrack, hand-rolled chevron → Icon atom), Sidebar nav items → navigation.js, SummaryCards deduped with local snippets. All splits respect the scope-hash rules in CONVENTIONS.md; WorkflowChart deliberately not split (computed-class CSS the compiler cannot see).
  • Docs — CONVENTIONS.md records the style-module cascade rule and the "compose EditorDialog, never hand-roll the shell" rule; embedded dist rebuilt.

Note on history: a TypeScript migration of $lib was made and then reverted within this branch (ecc2e8ca) — deemed too much for one iteration; the tip is plain JS throughout. Squash-merge leaves no TS in the history of main.

User-visible impact

None intended. Two accepted normalizations: form error banners render uniformly just above the actions row, and AuthKeyEditor's "Done, I've stored it" action moved into the standard footer. Behavior fixes: uniform Escape guard under the auth dialog; stale responses can no longer flip a page's availability flag.

Verification

  • npm run check — 0 errors
  • npm test — 386/386
  • npm run build — clean; emitted CSS byte-identical after the style split; dist in sync (pre-commit hook verified on the tip commit)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added reusable UI components—an enabled/disabled toggle, a standardized editor dialog, and form-field wrappers—adopted across multiple dashboard create/edit flows.
    • Enhanced audit logs with function-call chat rendering, compact attempt tracking, and improved conversation display.
    • Added expandable provider status details and streamlined sidebar navigation.
  • Bug Fixes
    • Improved consistency of stale/unavailable/auth and error handling feedback across admin CRUD pages.
  • Style
    • Refreshed dashboard styling via modular CSS organization (themes, alerts, buttons, cards/charts, tables, forms, layout, responsive).

SantiagoDePolonia and others added 6 commits July 28, 2026 19:10
The 2453-line monolith becomes 14 files under src/styles/ imported in
cascade order from dashboard.css. Pure partition at section boundaries:
the concatenation — and the emitted Vite CSS bundle — is byte-identical
to the previous build. No preprocessor added on purpose: Svelte scoped
styles, custom properties, and native nesting already cover the need.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shared layer (api, utils, stores, datePickerLogic) moves from
.js/.svelte.js to strictly-typed .ts/.svelte.ts: ApiResult envelope,
store state shapes, Window globals in globals.d.ts. Import specifiers
updated across pages and tests; node --test runs the .ts imports via
native type stripping (Node 22.18+). jsconfig.json becomes a strict
tsconfig.json (allowJs keeps page code unchecked during migration).
No behavior changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gle shells

Every editor modal (11 files) repeated the same Modal + header +
error-banner + actions markup and an Enabled/Disabled toggle. They now
compose three shared components; the shell also adds the previously
inconsistent Escape-under-auth-dialog guard to the five editors that
lacked it. Store wiring, ids, autofocus and validation behavior are
unchanged. (dist rebuilt in the closing commit of this series.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… ladder

Seven page stores hand-rolled the same stale/503/error guards around
getJSON/sendJSON, with drift: some checked 503 before staleness (letting
an old API key's response clobber the availability flag) and 401 load
errors were inconsistently surfaced. loadAdminList/sendAdminMutation in
$lib/api/adminCrud.ts now encode the ladder once — stale first, then
unavailable, then errors with silent-401 loads. Public store APIs and
user-visible copy are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProviderStatusCard (428->239) hands its collapsible body to
ProviderStatusCardDetails; ConversationDrawer (377->147) extracts
ChatMessage; AuditEntrySummary extracts AuditAttemptTrack and swaps its
hand-rolled chevron SVG for the Icon atom; Sidebar moves its nav-item
table to a typed navigation.ts module and the logo into an atom;
SummaryCards dedupes its twin token/status cards with local snippets.
Computed-class CSS stays where the compiler can see its markup per
CONVENTIONS; rendered DOM is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ls in CONVENTIONS

Also syncs the embedded dist with the refactored sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 28, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 27cb6ef8-4067-474e-9942-5afcdae6866c

📥 Commits

Reviewing files that changed from the base of the PR and between e419cbc and 6ba88c3.

⛔ Files ignored due to path filters (3)
  • internal/admin/dashboard/static/dist/assets/index-D4Oy5e9B.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-DhDJqJDv.css is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (1)
  • web/dashboard/src/pages/audit-logs/AuditPane.svelte
💤 Files with no reviewable changes (1)
  • web/dashboard/src/pages/audit-logs/AuditPane.svelte

📝 Walkthrough

Walkthrough

The dashboard now centralizes admin CRUD request outcomes, standardizes editor dialogs and form fields, extracts audit and overview UI components, moves navigation definitions into a registry, and splits the monolithic stylesheet into ordered CSS modules.

Changes

Dashboard consolidation

Layer / File(s) Summary
Admin CRUD outcome handling
web/dashboard/src/lib/api/adminCrud.js, web/dashboard/src/pages/*/*.svelte.js
Admin stores now consume standardized stale, unavailable, error, and success outcomes.
Shared editor composition
web/dashboard/src/lib/components/{atoms,molecules,organisms}/*, web/dashboard/src/pages/*/*Editor.svelte
Editors use shared dialog, field, and toggle components; sidebar navigation uses a registry and reusable logo.
Audit and overview extraction
web/dashboard/src/pages/audit-logs/*, web/dashboard/src/pages/overview/*
Conversation messages, attempt tracks, provider details, and summary cards were extracted into reusable components or snippets.
Modular dashboard styling
web/dashboard/src/styles/*, web/dashboard/CONVENTIONS.md
Dashboard styles were split into ordered modules, with conventions documenting CSS ownership and editor composition.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: copilot

Poem

A rabbit hops through forms in a row,
Shared dialogs help them flow.
CRUD outcomes leap, styles divide,
Audit trails sparkle side by side.
Toggles glow and logos greet—
The dashboard burrow feels complete.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR’s main themes, though the TypeScript foundation part is inaccurate because that migration was reverted.
Description check ✅ Passed The description is detailed and on-topic, covering scope, impact, and verification, even though it uses non-template section headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/front-end-refactoring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Scope trim: the TS conversion of $lib was too much for this iteration.
The foundation returns to .js/.svelte.js with jsconfig.json, and the two
modules born as TS in this branch (adminCrud, navigation) are ported to
plain JS with their behavior unchanged. Everything else from the
refactoring — style modules, EditorDialog/FormField/EnabledToggle,
the adminCrud request ladder, component splits — stays.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 28, 2026 19:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/dashboard/src/lib/components/atoms/GoModelLogo.svelte`:
- Around line 4-8: Update the decorative SVG in GoModelLogo to include
aria-hidden="true", ensuring assistive technologies ignore the unlabeled logo
while preserving its visual rendering.

In `@web/dashboard/src/lib/components/organisms/EditorDialog.svelte`:
- Around line 107-114: Separate the EditorDialog submit button’s in-flight state
from its disabled state by adding and using a dedicated submitDisabled prop for
the disabled attribute, while keeping submitting responsible only for the
submitting label and icon state. Update callers that currently fake submitting
or override submittingLabel for failoverFormManaged, vmDeleting, or
vmFormManaged to pass their real saving/deleting state as submitting and their
permanent non-submittable conditions as submitDisabled, removing redundant
custom submittingLabel values.

In `@web/dashboard/src/pages/auth-keys/AuthKeyEditor.svelte`:
- Around line 48-135: Update AuthKeyEditor’s name and expires fields to use the
shared FormField component instead of hand-rolled .form-field wrappers,
preserving their existing labels, required/optional hints, input bindings, IDs,
and attributes. Keep the InlineHelpSection-based fields unchanged because their
custom label/help structure cannot use FormField directly.
- Around line 12-26: Update the store’s closeForm() method to clear the
issuedValue state when closing the dialog, matching dismissIssuedKey() behavior
so reopened dialogs cannot re-display the one-time secret. Keep the existing
form-closing behavior unchanged.

In `@web/dashboard/src/pages/guardrails/guardrails.svelte.js`:
- Around line 151-152: Update both fetcher paths around the assignments to
this.available and this.types so available is set to true only when
loadAdminList returns a non-null result, indicating the gateway responded.
Preserve the unavailable state for thrown requests with result: null, while
retaining the existing item assignment and error handling for valid responses.

In `@web/dashboard/src/pages/providers-config/ProviderCredentialEditor.svelte`:
- Around line 57-66: Update the error handling between providersConfig.svelte.js
and ProviderCredentialEditor.svelte so list/load failures remain in a scoped
list error and are not passed to EditorDialog. Ensure the dialog’s error prop
receives only form-local, non-field save errors, using the existing
providersConfig form state or a dedicated form-local error field.

In `@web/dashboard/src/pages/rate-limits/RateLimitEditor.svelte`:
- Around line 10-21: Update the comment above EditorDialog in
RateLimitEditor.svelte to reflect the current form behavior: period-seconds is
removed from the DOM unless period is "custom", so it is not a hidden invalid
control. If rateLimitFormPayload() no longer requires disabling native
validation, remove the novalidate attribute; otherwise retain it with an
accurate justification.

In `@web/dashboard/src/pages/workflows/WorkflowEditor.svelte`:
- Around line 184-193: Update the guardrail step number input in the workflow
editor to allow arbitrary integer ordering values by changing the step
constraint from 10 to 1, while preserving the existing nonnegative minimum and
binding.

In `@web/dashboard/src/pages/workflows/workflows.svelte.js`:
- Line 230: Guard availability updates on successful fetch results so network
failures do not restore availability after a 503. In the workflows load flow,
wrap the existing this.available = true in an outcome.result check; apply the
same guard to the availability assignments in guardrails fetchTypes and
fetchGuardrails, leaving their existing error handling unchanged.

In `@web/dashboard/src/styles/base.css`:
- Line 3: In the font-family declaration containing “Inter”, remove the
quotation marks around the Inter font name while preserving the existing
fallback fonts and declaration structure.

In `@web/dashboard/src/styles/themes.css`:
- Line 77: Add a blank line immediately before each of the three color-scheme
declarations in themes.css, including the declarations near the referenced
locations, to satisfy the configured Stylelint declaration-spacing rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 64525026-a558-49c5-8635-ec7d9e4459f9

📥 Commits

Reviewing files that changed from the base of the PR and between 20aa6c5 and ecc2e8c.

⛔ Files ignored due to path filters (4)
  • internal/admin/dashboard/static/dist/assets/index-BGJkl_-O.css is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-BOEWOpVo.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-Dd3LDA86.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (48)
  • web/dashboard/CONVENTIONS.md
  • web/dashboard/src/lib/api/adminCrud.js
  • web/dashboard/src/lib/components/atoms/EnabledToggle.svelte
  • web/dashboard/src/lib/components/atoms/GoModelLogo.svelte
  • web/dashboard/src/lib/components/molecules/FormField.svelte
  • web/dashboard/src/lib/components/organisms/EditorDialog.svelte
  • web/dashboard/src/lib/components/organisms/Sidebar.svelte
  • web/dashboard/src/lib/components/organisms/navigation.js
  • web/dashboard/src/pages/audit-logs/AuditAttemptTrack.svelte
  • web/dashboard/src/pages/audit-logs/AuditEntrySummary.svelte
  • web/dashboard/src/pages/audit-logs/ChatMessage.svelte
  • web/dashboard/src/pages/audit-logs/ConversationDrawer.svelte
  • web/dashboard/src/pages/auth-keys/AuthKeyEditor.svelte
  • web/dashboard/src/pages/auth-keys/AuthKeyLabelsEditor.svelte
  • web/dashboard/src/pages/auth-keys/authKeys.svelte.js
  • web/dashboard/src/pages/budgets/BudgetEditor.svelte
  • web/dashboard/src/pages/budgets/budgets.svelte.js
  • web/dashboard/src/pages/guardrails/GuardrailEditor.svelte
  • web/dashboard/src/pages/guardrails/guardrails.svelte.js
  • web/dashboard/src/pages/mcp-servers/McpServerEditor.svelte
  • web/dashboard/src/pages/mcp-servers/mcpServers.svelte.js
  • web/dashboard/src/pages/models/FailoverEditor.svelte
  • web/dashboard/src/pages/models/PricingOverrideEditor.svelte
  • web/dashboard/src/pages/models/VirtualModelEditor.svelte
  • web/dashboard/src/pages/overview/ProviderStatusCard.svelte
  • web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte
  • web/dashboard/src/pages/overview/SummaryCards.svelte
  • web/dashboard/src/pages/providers-config/ProviderCredentialEditor.svelte
  • web/dashboard/src/pages/providers-config/providersConfig.svelte.js
  • web/dashboard/src/pages/rate-limits/RateLimitEditor.svelte
  • web/dashboard/src/pages/rate-limits/rateLimits.svelte.js
  • web/dashboard/src/pages/workflows/WorkflowEditor.svelte
  • web/dashboard/src/pages/workflows/workflows.svelte.js
  • web/dashboard/src/styles/alerts.css
  • web/dashboard/src/styles/auth-dialog.css
  • web/dashboard/src/styles/base.css
  • web/dashboard/src/styles/budgets.css
  • web/dashboard/src/styles/buttons.css
  • web/dashboard/src/styles/cards-charts.css
  • web/dashboard/src/styles/dashboard.css
  • web/dashboard/src/styles/forms.css
  • web/dashboard/src/styles/layout.css
  • web/dashboard/src/styles/page-globals.css
  • web/dashboard/src/styles/responsive.css
  • web/dashboard/src/styles/settings.css
  • web/dashboard/src/styles/tables.css
  • web/dashboard/src/styles/themes.css
  • web/dashboard/src/styles/usage-audit.css

Comment thread web/dashboard/src/lib/components/atoms/GoModelLogo.svelte Outdated
Comment thread web/dashboard/src/lib/components/organisms/EditorDialog.svelte
Comment on lines +12 to +26
<EditorDialog
open={store.formOpen}
title="Create API Key"
ariaLabel="API key editor"
error={store.issuedValue ? "" : store.error}
submitting={store.formSubmitting}
submitLabel={store.issuedValue ? "Done, I’ve stored it" : "Create API Key"}
submittingLabel="Creating..."
submitIcon={store.issuedValue ? "check" : "plus"}
cancel={false}
dialogClass="auth-key-editor"
onclose={() => store.closeForm()}
onsubmit={() =>
store.issuedValue ? store.dismissIssuedKey() : store.submitForm()}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd authKeys.svelte.js --exec rg -n -A12 'closeForm|dismissIssuedKey'

Repository: ENTERPILOT/GoModel

Length of output: 799


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the auth-keys store and editor dialog files and inspect the relevant code.
fd 'AuthKeyEditor\.svelte|authKeys\.svelte\.js|EditorDialog' . -x sh -c '
  echo "===== $1 ====="
  wc -l "$1"
  sed -n "1,180p" "$1" | cat -n
' sh {}

Repository: ENTERPILOT/GoModel

Length of output: 18927


Clear issued secrets before closing the dialog.

closeForm() leaves issuedValue intact when submitted has already displayed it, so using the dialog close button or Escape can reopen the dialog and re-display the one-time API key secret. Clear the issued state in closeForm() as well as in dismissIssuedKey().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/pages/auth-keys/AuthKeyEditor.svelte` around lines 12 - 26,
Update the store’s closeForm() method to clear the issuedValue state when
closing the dialog, matching dismissIssuedKey() behavior so reopened dialogs
cannot re-display the one-time secret. Keep the existing form-closing behavior
unchanged.

Comment on lines +48 to +135
<div class="form-field">
<label class="form-field-label" for="auth-key-name">
Name <span class="form-hint">(required)</span>
</label>
<input
id="auth-key-name"
type="text"
placeholder="e.g. ci-deploy"
autocomplete="off"
data-modal-autofocus
bind:value={store.form.name}
/>
</div>
{:else}
<div class="auth-key-form-fields">
<div class="form-grid">
<div class="form-field">
<label class="form-field-label" for="auth-key-name">
Name <span class="form-hint">(required)</span>
</label>
<input
id="auth-key-name"
type="text"
placeholder="e.g. ci-deploy"
autocomplete="off"
data-modal-autofocus
bind:value={store.form.name}
/>
</div>
<div class="form-field">
<label class="form-field-label" for="auth-key-expires">
Expires <span class="form-hint">(optional, valid through the selected date)</span>
</label>
<input id="auth-key-expires" type="date" bind:value={store.form.expires_at} />
</div>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-user-path-help-copy" label="API key user path help">
{#snippet title()}
<label class="form-field-label" for="auth-key-user-path">User Path (optional)</label>
{/snippet}
{#snippet help()}
When set, this key overrides the configured user path request
header for audit logging and downstream request context.
{/snippet}
</InlineHelpSection>
<input
id="auth-key-user-path"
type="text"
placeholder="ex. /department1/team-a"
aria-describedby="auth-key-user-path-help-copy"
bind:value={store.form.user_path}
/>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-labels-help-copy" label="API key labels help">
{#snippet title()}
<label class="form-field-label" for="auth-key-labels">
Labels (optional, comma-separated)
</label>
{/snippet}
{#snippet help()}
Every request authenticated with this key gets these labels, in
addition to any labels from tagging headers. Labels show up in
usage analytics, the request log, and audit logs.
{/snippet}
</InlineHelpSection>
<input
id="auth-key-labels"
type="text"
placeholder="ex. team-a, batch-jobs"
aria-describedby="auth-key-labels-help-copy"
bind:value={store.form.labels}
/>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-dashboard-access-help-copy" label="API key dashboard access help">
{#snippet title()}
<label class="form-field-label" for="auth-key-dashboard-access">Dashboard access</label>
{/snippet}
{#snippet help()}
When off, this key is denied the dashboard and every /admin API
endpoint. Model endpoints and GET /v1/usage stay available to
the key. The master key always has dashboard access.
{/snippet}
</InlineHelpSection>
<label class="auth-key-dashboard-toggle">
<input
id="auth-key-dashboard-access"
type="checkbox"
aria-describedby="auth-key-dashboard-access-help-copy"
bind:checked={store.form.dashboard_access}
/>
<span>Allow this key to use the dashboard and /admin API</span>
</label>
</div>
<div class="form-field">
<label class="form-field-label" for="auth-key-description">Description (optional)</label>
<textarea
id="auth-key-description"
rows="2"
placeholder="What is this key used for?"
bind:value={store.form.description}
></textarea>
</div>
{#if store.error}
<p class="form-error" role="alert" aria-live="assertive">{store.error}</p>
{/if}
<div class="form-actions">
<button
type="submit"
class="btn btn-primary btn-with-icon"
disabled={store.formSubmitting}
>
{#if !store.formSubmitting}
<span aria-hidden="true"><Icon name="plus" class="table-icon-svg" /></span>
{/if}
<span>{store.formSubmitting ? "Creating..." : "Create API Key"}</span>
</button>
</div>
<div class="form-field">
<label class="form-field-label" for="auth-key-expires">
Expires <span class="form-hint">(optional, valid through the selected date)</span>
</label>
<input id="auth-key-expires" type="date" bind:value={store.form.expires_at} />
</div>
{/if}
</form>
</div>
</Modal>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-user-path-help-copy" label="API key user path help">
{#snippet title()}
<label class="form-field-label" for="auth-key-user-path">User Path (optional)</label>
{/snippet}
{#snippet help()}
When set, this key overrides the configured user path request
header for audit logging and downstream request context.
{/snippet}
</InlineHelpSection>
<input
id="auth-key-user-path"
type="text"
placeholder="ex. /department1/team-a"
aria-describedby="auth-key-user-path-help-copy"
bind:value={store.form.user_path}
/>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-labels-help-copy" label="API key labels help">
{#snippet title()}
<label class="form-field-label" for="auth-key-labels">
Labels (optional, comma-separated)
</label>
{/snippet}
{#snippet help()}
Every request authenticated with this key gets these labels, in
addition to any labels from tagging headers. Labels show up in
usage analytics, the request log, and audit logs.
{/snippet}
</InlineHelpSection>
<input
id="auth-key-labels"
type="text"
placeholder="ex. team-a, batch-jobs"
aria-describedby="auth-key-labels-help-copy"
bind:value={store.form.labels}
/>
</div>
<div class="form-field">
<InlineHelpSection copyId="auth-key-dashboard-access-help-copy" label="API key dashboard access help">
{#snippet title()}
<label class="form-field-label" for="auth-key-dashboard-access">Dashboard access</label>
{/snippet}
{#snippet help()}
When off, this key is denied the dashboard and every /admin API
endpoint. Model endpoints and GET /v1/usage stay available to
the key. The master key always has dashboard access.
{/snippet}
</InlineHelpSection>
<label class="auth-key-dashboard-toggle">
<input
id="auth-key-dashboard-access"
type="checkbox"
aria-describedby="auth-key-dashboard-access-help-copy"
bind:checked={store.form.dashboard_access}
/>
<span>Allow this key to use the dashboard and /admin API</span>
</label>
</div>
<FormField id="auth-key-description" label="Description (optional)">
<textarea
id="auth-key-description"
rows="2"
placeholder="What is this key used for?"
bind:value={store.form.description}
></textarea>
</FormField>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mixed field markup in one file.

Lines 48-127 hand-roll .form-field wrappers while line 128 uses FormField. Where the label is a plain string (name, expires), FormField applies; the InlineHelpSection cases genuinely can't. Consider converting the convertible ones so the shared molecule is the default here too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/pages/auth-keys/AuthKeyEditor.svelte` around lines 48 -
135, Update AuthKeyEditor’s name and expires fields to use the shared FormField
component instead of hand-rolled .form-field wrappers, preserving their existing
labels, required/optional hints, input bindings, IDs, and attributes. Keep the
InlineHelpSection-based fields unchanged because their custom label/help
structure cannot use FormField directly.

Comment thread web/dashboard/src/pages/guardrails/guardrails.svelte.js Outdated
Comment thread web/dashboard/src/pages/rate-limits/RateLimitEditor.svelte Outdated
Comment thread web/dashboard/src/pages/workflows/WorkflowEditor.svelte
Comment thread web/dashboard/src/pages/workflows/workflows.svelte.js Outdated
@@ -0,0 +1,32 @@
body {
font-family:
"Inter",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove quotes around Inter to satisfy Stylelint.

🧰 Tools
🪛 Stylelint (17.14.1)

[error] 3-3: Expected no quotes around "Inter" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/styles/base.css` at line 3, In the font-family declaration
containing “Inter”, remove the quotation marks around the Inter font name while
preserving the existing fallback fonts and declaration structure.

Source: Linters/SAST tools

var(--bg-surface-hover) 72%,
#fff 28%
);
color-scheme: dark;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the three Stylelint declaration-spacing errors.

Add an empty line before each color-scheme declaration so this new stylesheet passes the configured lint rule.

Also applies to: 125-125, 173-173

🧰 Tools
🪛 Stylelint (17.14.1)

[error] 77-77: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/styles/themes.css` at line 77, Add a blank line immediately
before each of the three color-scheme declarations in themes.css, including the
declarations near the referenced locations, to satisfy the configured Stylelint
declaration-spacing rule.

Source: Linters/SAST tools

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

@greptile-apps can you review it?

- EditorDialog gains submitDisabled so read-only/managed editors no
  longer overload submitting (and their labels stay honest)
- guardrails/workflows availability flags only trust answered requests:
  a thrown fetch (offline, watchdog abort) no longer undoes a 503
- guardrail step input allows any integer (step=1, was multiples of 10)
- GoModelLogo SVG marked decorative (aria-hidden)
- rate-limit editor's novalidate comment matches current markup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 16:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit review in 2ad779e — 5 findings fixed, 6 skipped with reasons:

Fixed

  • EditorDialog now has a dedicated submitDisabled prop; VirtualModelEditor/FailoverEditor pass their managed/deleting/generating states there instead of overloading submitting and re-deriving submittingLabel.
  • guardrails.svelte.js (both fetchers) and workflows.svelte.js guard available = true on outcome.result, so a thrown request (offline/DNS/watchdog abort) no longer undoes an earlier 503 "feature unavailable" state.
  • WorkflowEditor guardrail step input: step="10"step="1" (native validation was rejecting legitimate ordering values like 15; the constraint predates this PR but the fix is a one-liner).
  • GoModelLogo SVG marked aria-hidden="true" focusable="false".
  • RateLimitEditor's novalidate comment rewritten — the old rationale described a hidden input that the {#if} actually removes from the DOM; novalidate itself stays (store-side validation must run so its error copy is shown instead of a silent native block).

Skipped

  • AuthKeyEditor: clear issuedValue in closeForm() — deliberate pre-existing design, unchanged by this PR: openForm() explicitly preserves an issued secret across close/reopen so an accidental Escape can't permanently destroy the only chance to copy a just-created key. Destroying the secret requires the explicit "Done, I've stored it" action.
  • AuthKeyEditor name/expires → FormField — their labels embed <span class="form-hint"> inside the label element, which FormField's plain-string label prop can't express.
  • providersConfig: scope error away from the modal — pre-existing single-error-slot design: the page hides store.error while the form is open (!store.formOpen), i.e. the slot is intentionally shared and routed to whichever surface is active. Worth revisiting, but it's a store-API change beyond this refactor's no-behavior-change rule.
  • Stylelint findings in base.css/themes.css (font-family quotes, declaration spacing) — this repo doesn't run Stylelint (no config in the tree; the findings come from the reviewer's own toolchain), and those lines were moved verbatim from the old dashboard.css. The CSS-split commit intentionally guarantees a byte-identical stylesheet, so reformatting is out of scope here.

🤖 Generated with Claude Code

if (outcome.result) {
this.available = true;
} else {
this.keys = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Network errors erase cached keys

When an auth-key refresh fails before receiving an HTTP response, this branch clears the last successfully loaded keys even though their validity is unknown, leaving the table empty until a later refresh succeeds. Preserve the existing rows while displaying the load error, as the gateway-error branch already does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e419cbc — the network-failure branch now keeps the last-known rows next to the inline error, matching the gateway-error branch.

One correction for the record: this wasn't a regression introduced by the PR. The pre-refactor fetchKeys had the identical behavior (catchthis.keys = [] + error), and the store migration preserved it deliberately. Agreed it was worth improving regardless.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The PR is safe to merge, with a non-blocking auth-key table regression on transient network failures.

The shared request ladder preserves the core stale and unavailable-response behavior, but the auth-key store now clears previously loaded rows when a request fails before receiving an HTTP response.

Files Needing Attention: web/dashboard/src/pages/auth-keys/authKeys.svelte.js

T-Rex T-Rex Logs

What T-Rex did

  • The PR version of the API Key editor was verified to preserve entered values and use the shared EditorDialog/FormField shell, matching the baseline editor flow.
  • The Budget editor UI was verified to retain confirm-styled fields and keep Cancel/Save actions visible after CSS modularization.
  • Browser and gateway logs were reviewed and showed no errors: browser logs contained only Vite connection messages and successful assertions, while gateway logs showed authenticated startup with no relevant errors.

View all artifacts

T-Rex Ran code and verified through T-Rex

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
Dashboard[Dashboard pages] --> Editors[Shared EditorDialog and FormField]
Dashboard --> Stores[Feature CRUD stores]
Stores --> Crud[adminCrud request ladder]
Crud --> API[Admin REST API]
Dashboard --> Views[Extracted audit and provider components]
Dashboard --> Styles[Ordered CSS module entry point]
Build[Dashboard build] --> Dist[Embedded static distribution]
Loading

Reviews (1): Last reviewed commit: "revert(dashboard): back out the TypeScri..." | Re-trigger Greptile

…ailures

A fetch that dies before an HTTP response (offline, DNS) now leaves the
last successfully loaded rows on screen next to the inline error, like
the gateway-error branch always did, instead of blanking the table.
(Pre-existing behavior, not introduced by this branch.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 16:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Every pane builder sets layout: "split", so the stacked-layout sibling
margin (.audit-pane-block + .audit-pane-block) never renders anywhere
its split-layout margin-top: 0 override doesn't immediately cancel it.
Grid gap owns the spacing; both rules go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 30, 2026 12:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia
SantiagoDePolonia merged commit 76560c2 into main Jul 30, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants