Skip to content

feat(dashboard): per-engine tabs with credentials, settings, and default indicator#1057

Merged
aaight merged 4 commits intodevfrom
feature/engine-config-tabs
Mar 25, 2026
Merged

feat(dashboard): per-engine tabs with credentials, settings, and default indicator#1057
aaight merged 4 commits intodevfrom
feature/engine-config-tabs

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 25, 2026

Summary

Refactors the Engine (Harness) tab in the dashboard to use a per-engine tabbed layout, replacing the previous single engine dropdown + separate credentials card with an integrated design:

  • Per-engine tabs — one tab per engine (Claude Code, LLMist, Codex, OpenCode), dynamically populated from the engine catalog API (trpc.agentConfigs.engines). New engines auto-get tabs as the catalog grows.
  • Default engine indicator — the active default engine tab shows a Default badge in the tab trigger and a "✓ Default engine for this project" message in the tab content.
  • Set as Default Engine button — non-default tabs show a "Set as Default Engine" button; clicking it updates the agentEngine state and is persisted on "Save Changes".
  • Per-engine credentialsENGINE_SECRETS are filtered per tab by secret.engines.includes(engineId). Shared secrets (e.g. OPENROUTER_API_KEY used by both opencode + llmist) show an "Also used by: ..." note.
  • Multi-engine settings persistencehandleSubmit now saves all engine settings across all tabs (not just the active engine): engineSettings: Object.keys(engineSettings).length > 0 ? engineSettings : null.
  • In-use indicators — non-default engine tabs that are used by agent config overrides get an In use badge using enginesInUseQuery, with a contextual note in the tab content.
  • Model & Runtime card — Model and Max Iterations fields are now in a separate engine-agnostic card above the engine tabs, as they apply globally.

Frontend-only change — no backend changes. The existing DB schema (JSONB agent_engine_settings), tRPC endpoints, and engine catalog already support multi-engine configuration.

Primary file changed: web/src/components/projects/project-harness-form.tsx

Trello: https://trello.com/c/cTpAdvc0/567-engine-configuration-tabs-in-dashboard-per-engine-credentials-settings-and-default-engine-indicator

Test plan

  • Navigate to Project > Engine tab; verify 4 engine tabs render (Claude Code, LLMist, Codex, OpenCode)
  • Verify the default engine tab shows a "Default" badge in the tab trigger
  • Verify the default engine tab content shows "✓ Default engine for this project"
  • Click "Set as Default Engine" on a non-default tab; verify badge moves to new tab
  • Save Changes and reload; verify new default engine persists
  • Verify each engine tab shows only its own credentials (e.g. Codex tab shows OPENAI_API_KEY + CODEX_AUTH_JSON)
  • Verify shared credentials (OPENROUTER_API_KEY on opencode/llmist tabs) show "Also used by: ..." note
  • Verify engine settings (effort, approval policy, etc.) render in the correct tabs
  • Verify changing settings in multiple tabs and saving sends all engine settings in the request
  • If any agent config overrides use a non-default engine, verify that tab shows "In use" badge
  • Verify Model and Max Iterations fields are above the engine tabs and function as before

🤖 Generated with Claude Code

🕵️ claude-code · claude-sonnet-4-6 · run details

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean refactor from dropdown-based engine selection to per-engine tabs. The architectural approach is sound — the tab-per-engine model is a good fit for the growing engine catalog, and saving all engine settings (not just the active engine) is correct for multi-engine workflows. CI is green. Three minor issues worth addressing.

Code Issues

Should Fix

  • project-harness-form.tsx:89defaultEngineLabel is dead code. The <Select> dropdown that referenced it was removed in this PR, but the variable was kept. Safe to delete.

  • project-harness-form.tsx:304-314sharedNote ?? secret.description replaces the original description rather than appending. When ANTHROPIC_API_KEY appears on the Claude Code tab, the user sees "Also used by: opencode" but loses the original description ("API key for Claude Code (non-subscription) or OpenCode backend") that explains what the credential is for. Consider combining: secret.description + (sharedNote ? ' · ' + sharedNote : '') so the user gets both the purpose and the sharing info.

  • project-harness-form.tsx:306 — The "Also used by" note displays raw engine IDs (opencode, llmist) while every other part of the UI uses the human-readable engine.label values (e.g., "OpenCode", "LLMist"). The engines array from the catalog is in scope inside the .map() — mapping IDs to labels would keep the UI consistent.

🕵️ claude-code · claude-opus-4-6 · run details

Cascade Bot and others added 2 commits March 25, 2026 23:26
…ions

- Remove dead code: `defaultEngineLabel` variable left over after Select dropdown was removed
- Combine original secret description with sharing note instead of replacing it
- Map shared engine IDs to human-readable labels (engine.label) in "Also used by" note

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…vulnerabilities

Fixes two high-severity CVEs in picomatch 4.0.3:
- GHSA-c2c7-rcm5-vvqj: ReDoS vulnerability via extglob quantifiers
- GHSA-3v7f-55p6-f55p: Method Injection in POSIX Character Classes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean refactor from a dropdown-based engine selector to a per-engine tabbed UI. The implementation is sound overall — pattern consistency with the existing codebase is good, the EngineSettingsFields and ProjectSecretField components are reused correctly, and the state management via useState + handleSubmit works despite the form/card split. All CI checks pass. Two issues worth addressing.

Code Issues

Should Fix

  • project-harness-form.tsx:268No way to reset to inherit system default. The old dropdown had a _none option that mapped to agentEngine: null, allowing users to opt out of an explicit engine override and inherit the system default. In the new tabbed UI, once the user clicks Set as Default Engine on any tab, there is no mechanism to revert. Even clicking the system default engine (e.g. claude-code) explicitly sets agentEngine: 'claude-code' instead of null, so the project loses the ability to dynamically follow future system default changes. Consider adding a Reset to System Default affordance (e.g. a small link or button on the currently-selected tab) that calls setAgentEngine('').

  • project-harness-form.tsx:173-190 and 333-350Duplicate Save Changes button. The save button + status indicators are copy-pasted verbatim into both the Model and Runtime card footer and the Engine Settings and Credentials card footer. Both submit the same form=engine-runtime-form. This creates two issues: (1) UX confusion — the user sees two identical save buttons and may not realize they are the same action, and (2) maintenance burden — any change to the button markup must be updated in two places. Consider extracting a shared SaveFooter component, or keeping only one save button (either in the last card, or as a sticky footer outside both cards).

🕵️ claude-code · claude-opus-4-6 · run details

…save button

- Add "Reset to system default" link on the default engine tab when an engine
  is explicitly set, calling setAgentEngine('') to restore inherit behaviour
- Remove the duplicate Save Changes footer from the Model & Runtime card;
  a single save button in the Engine Settings & Credentials card footer
  now covers both cards via form="engine-runtime-form"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

LGTM — Clean refactor from a single-engine dropdown to per-engine tabbed layout. The implementation is correct, well-structured, and all CI checks pass.

Notes

A few observations (none blocking):

  1. Tabs defaultValue vs controlled value: The Tabs component uses defaultValue={defaultTab} (uncontrolled). This means when the user clicks "Reset to system default" and the default engine changes to a different engine, the badge will move to the new default's tab but the user's active tab won't switch automatically. This is a minor UX quirk — the user sees the badge leave their current tab and has to click the new default tab to see it. Consider using controlled value + onValueChange if you want the tab to follow the default engine, but this is a polish item, not a bug.

  2. Engine settings now saved for all tabs: The change from saving only the active engine's settings to saving all engine settings (Object.keys(engineSettings).length > 0 ? engineSettings : null) is a nice improvement. Previously, switching the default engine could lose settings configured for the old engine. The backend JSONB column already supports this, so no migration needed.

  3. Form-across-cards pattern: The <form id="engine-runtime-form"> is in the Model & Runtime card, and the submit <button form="engine-runtime-form"> is in the Engine Settings & Credentials card. This is valid HTML5 and works correctly, but it's worth noting this cross-card form association for future maintainers.

🕵️ claude-code · claude-opus-4-6 · run details

@aaight aaight merged commit 04e71e4 into dev Mar 25, 2026
9 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.

2 participants