Skip to content

feat: 276 expand dashboard and web operational clients to match runtime admin capabilities#352

Merged
yacosta738 merged 27 commits into
mainfrom
feat/276-expand-dashboard-and-web-operational-clients-to-match-runtime-admin-capabilities
Mar 28, 2026
Merged

feat: 276 expand dashboard and web operational clients to match runtime admin capabilities#352
yacosta738 merged 27 commits into
mainfrom
feat/276-expand-dashboard-and-web-operational-clients-to-match-runtime-admin-capabilities

Conversation

@yacosta738
Copy link
Copy Markdown
Contributor

This pull request introduces several new admin API endpoints and significantly refactors the admin configuration view to provide more detailed and structured information about system status, configuration, and runtime health. It also adds a new Server-Sent Events (SSE) streaming chat endpoint for real-time frontend consumption. The changes improve observability, configurability, and the ability to monitor and interact with the system.

Key changes include:

New API Endpoints and Features

  • Added /web/admin/channels endpoint to report the configuration status of all communication channels, with a new AdminChannelStatusView struct for detailed channel info. [1] [2] [3] [4]
  • Added /web/admin/scheduler endpoint to report scheduler configuration and status, including a new AdminSchedulerStatusView struct. [1] [2] [3] [4]
  • Added /web/admin/health endpoint to expose a runtime health snapshot for observability. [1] [2] [3] [4]
  • Added /web/chat/stream endpoint for SSE-based streaming chat responses, including error and completion events, for improved frontend integration. [1] [2] [3] [4]

Refactored and Expanded Admin Config View

  • Replaced the old AdminConfigView and related structs with a new, more detailed version that includes new sections for cost controls, MCP (multi-command processor) servers, tunnel configuration, reliability settings, and heartbeat monitoring. [1] [2] [3] [4] [5]
  • Updated the admin config view construction logic to populate these new sections from the runtime config.

Code Cleanup and Organization

  • Removed legacy admin config and channel view structs and their construction logic, consolidating all admin-facing config serialization into the new, more comprehensive structures. [1] [2]
  • Updated the startup banner to include the new endpoints for visibility at runtime.

These changes collectively provide a more robust, observable, and user-friendly admin interface, and lay the groundwork for future enhancements to system monitoring and control.

Closes: #276

…ational parity (#276)

Introduce comprehensive documentation for expanding dashboard and chat web clients to match runtime admin capabilities. Includes technical design, gap analysis, phased implementation proposal, detailed specification, and actionable task breakdown for surfacing operational features via new dashboard sections, gateway endpoints, and chat enhancements.
…es config sections (#276)

Wire existing gateway admin capabilities into dashboard UI:
- Extend TypeScript types to match Rust AdminConfigView structs
- Add WebSearchSettings, BrowserSettings, ComposioSettings,
  MemorySettings, and UpdateSettings components with tests
- Update useConfig composable with new field defaults, mappings,
  and secret reset logic for 6 new config sections
- Add payload builders for web-search, browser, composio, memory
- Integrate new sections into App.vue
- Add i18n keys for en and es locales

Phase 1 of DALLAY-181 web operational parity.
…view component (#276)

Add GET /web/admin/channels gateway endpoint that returns configuration
status for all 15 channel types (cli, webhook, telegram, discord, slack,
mattermost, imessage, matrix, signal, whatsapp, email, irc, lark,
dingtalk, qq) with redacted config summaries.

Dashboard side:
- Add AdminChannelStatusView TypeScript type
- Create ChannelsOverview component with fetch, loading/error states,
  and color-coded configuration indicators
- Wire into App.vue with gateway URL and bearer token props
- Add i18n keys for en and es locales

Channel health is best-effort per ADR-5: config-derived status only,
no real-time probing (channel instances not in AppState).

Phase 2 of DALLAY-181 web operational parity.
…SS imports

- Update all CSS files to use rgb() slash syntax for alpha transparency instead of rgba()
- Switch @import statements to use url() syntax for external CSS files
- Refine font-family fallbacks for consistency
- Adjust media queries to use modern syntax (width <= ...)
- Minor reordering and deduplication of CSS rules for clarity
- Update style check script to run stylelint before recursive checks
…olicy details (#276)

Phase 3 of DALLAY-181 web operational parity:

Rust gateway:
- Add GET /web/admin/health endpoint exposing runtime health snapshot
  (pid, uptime, per-component status, restart counts)
- Add GET /web/admin/scheduler endpoint exposing scheduler config and
  task count

Dashboard:
- Extend SecuritySettings with autonomy policy details: require
  approval for medium risk, block high risk commands, auto-approve
  and always-ask command lists (comma-separated textareas)
- Add HealthDashboard component with overall status indicator,
  formatted uptime, and per-component health details
- Add SchedulerStatus component with enabled/disabled state,
  task limits, and active task count
- Add TypeScript types for health snapshot and scheduler status
- Update configPayload with autonomy list diff logic
- Add i18n keys for en and es locales
…tbeat overview sections (#276)

Phase 4 of DALLAY-181 web operational parity:

Rust gateway:
- Extend AdminConfigView with cost, mcp, tunnel, reliability, and
  heartbeat sections including secret redaction for tunnel tokens
- Remove stale duplicate admin_config_view and dead view structs
  from gateway/mod.rs

Dashboard:
- Add CostOverview, McpOverview, TunnelOverview, ReliabilityOverview,
  and HeartbeatOverview read-only components with fetch + tests
- Add TypeScript types matching all new Rust view structs
- Wire all 5 components into App.vue
- Add i18n keys for en and es locales
…ator, and session persistence (#276)

Phase 5 of DALLAY-181 web operational parity:

Rust gateway:
- Add POST /web/chat/stream SSE endpoint that processes messages via
  existing dispatch and streams response as chunk/done/error events

Chat app:
- Add streamMessage() with fetch + ReadableStream SSE parsing, with
  automatic fallback to non-streaming sendMessage on failure
- Update ChatMessage with streaming cursor and error state styling
- Extract Message types to types/chat.ts with status field
- Add ToolApprovalCard component with approve/reject buttons
- Handle 403 approval_required responses in useChat
- Add HealthIndicator component with 30s polling to /health
- Add session persistence via sessionStorage watch/restore
- Add i18n keys for streaming, approval, and health states
Complete SDD cycle for DALLAY-181:
- Sync delta specs to openspec/specs/web-operational-parity/
  with 12 requirements reflecting implementation reality
- Archive change to openspec/changes/archive/ with all artifacts
  (exploration, proposal, spec, design, tasks, verify-report,
  archive-report)
- Verification status: PASS WITH WARNINGS (no CRITICAL issues)
- Known gap: ProviderPoolsSettings component deferred
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 28, 2026

✅ Contributor Report

User: @yacosta738
Status: Passed (12/13 metrics passed)

Metric Description Value Threshold Status
PR Merge Rate PRs merged vs closed 89% >= 30%
Repo Quality Repos with ≥100 stars 0 >= 0
Positive Reactions Positive reactions received 9 >= 1
Negative Reactions Negative reactions received 0 <= 5
Account Age GitHub account age 3074 days >= 30 days
Activity Consistency Regular activity over time 108% >= 0%
Issue Engagement Issues with community engagement 0 >= 0
Code Reviews Code reviews given to others 462 >= 0
Merger Diversity Unique maintainers who merged PRs 2 >= 0
Repo History Merge Rate Merge rate in this repo 91% >= 0%
Repo History Min PRs Previous PRs in this repo 184 >= 0
Profile Completeness Profile richness (bio, followers) 90 >= 0
Suspicious Patterns Spam-like activity detection 1 N/A

Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-03-28 to 2026-03-28

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Real-time message streaming in chat with live text generation
    • Tool approval interface for users to approve/reject assistant actions
    • Live service health indicator in chat app
    • Operator dashboard views for system health, channels, scheduler status, and infrastructure metrics
    • New dashboard configuration sections for web search, browser automation, Composio, and memory services
    • Chat message persistence within sessions
  • Documentation

    • CSS architecture guidelines for web applications
    • Web operational parity specification and design documentation
  • Chores

    • Updated Android build tools and dependencies
    • Refactored CSS layer organization

Walkthrough

Adds gateway admin endpoints and SSE chat streaming; extends Rust admin views/types and handlers; implements client SSE streaming with idempotency and tool-approval UI; expands dashboard admin sections, types, payload builders, and many Vue components/tests; introduces shared CSS layers, Stylelint and workspace updates; updates build lockfiles.

Changes

Cohort / File(s) Summary
Gateway: admin & streaming
clients/agent-runtime/src/gateway/admin.rs, clients/agent-runtime/src/gateway/mod.rs
Extended AdminConfigView with cost/mcp/tunnel/reliability/heartbeat, added many Admin*View structs, new handlers (/web/admin/channels, /web/admin/scheduler, /web/admin/health), admin_channels_view helper, and wired POST /web/chat/stream SSE endpoint and stream handler.
Runtime: image gating & router
clients/agent-runtime/src/channels/media.rs, clients/agent-runtime/src/providers/router.rs
Converted ImageRejectionReason to thiserror::Error; router now returns typed ImageRejectionReason::RouteNotImageCapable for non-image routes and tests/mocks updated to assert typed error and provider call counts.
Chat client: streaming, approvals & API changes
clients/web/apps/chat/src/composables/useChat.ts, .../useChat.spec.ts, .../types/chat.ts, .../App.vue, .../App.spec.ts, .../ChatMessage.vue, .../ToolApprovalCard.vue, .../ToolApprovalCard.spec.ts
Added streamMessage SSE flow with streaming state, idempotency/request-id headers, SSE chunk/done/error parsing and fallback to non-stream path; changed sendMessage signature/result shape; added ToolApprovalCard component and tests; updated app integration and tests.
Chat health, persistence & UI
clients/web/apps/chat/src/components/HealthIndicator.vue, .../HealthIndicator.spec.ts, clients/web/apps/chat/src/App.vue
New HealthIndicator component (auth header, 5s timeout, 30s polling) and tests; App-level session-scoped message persistence and message status lifecycle (streaming/complete/error).
Dashboard: new config sections & payload builders
clients/web/apps/dashboard/src/components/config/*, clients/web/apps/dashboard/src/composables/{configPayload,useConfig}.ts, clients/web/apps/dashboard/src/types/admin-config.ts
Added WebSearch/Browser/Composio/Memory settings components and tests; extended admin-config types/forms/snapshots/patches; added buildSecretUpdate and section-specific payload builders; updated form mapping and save handling.
Dashboard: operational overviews
clients/web/apps/dashboard/src/components/config/{ChannelsOverview,SchedulerStatus,HealthDashboard,CostOverview,McpOverview,TunnelOverview,ReliabilityOverview,HeartbeatOverview}.vue, *.spec.ts
Eight read-only overview components that fetch /web/admin/* or /web/admin/config with abortable fetches/polling, error handling and unit tests; each accepts gatewayUrl and bearerToken props.
Dashboard app wiring & fixtures
clients/web/apps/dashboard/src/App.vue, .../test/*, .../adminConfigFormFactory.ts
Wired operator views into App.vue, added many component imports and conditional operator UI, updated test fixtures and sectionSaving state to include new sections.
Shared web utilities & CSS packaging
clients/web/packages/shared/index.ts, clients/web/packages/shared/{theme.css,base.css,app-shell.css}, clients/web/packages/shared/package.json
Added trimTrailingSlashes and validateGatewayUrl, introduced shared CSS layers (theme/base/app-shell), exported new CSS entrypoints and updated resolvePublicUrl usage.
Style tooling & workspace
clients/web/stylelint.config.mjs, clients/web/package.json, pnpm-workspace.yaml
Added Stylelint config and scripts (check:styles, format:styles) and devDeps; added clients/web to pnpm workspace.
App styling changes
various clients/web/apps/*/src/style.css, clients/web/packages/*/*.css
Replaced local resets/imports with shared app-shell/base imports, modernized color syntax to rgb(... / alpha), removed some redundant global baseline rules.
I18n, docs & specs
clients/web/packages/locales/src/{en,es}.json, openspec/.../web-operational-parity/*, clients/web/CSS_ARCHITECTURE.md
Added translations for many new UI sections/statuses; added design/spec/archive/verification docs and CSS architecture doc.
Tests & fixtures (many)
many clients/web/apps/*/src/*.spec.ts, clients/web/apps/dashboard/src/composables/configPayload.spec.ts
Large suite of new/updated unit tests covering SSE parsing, component behaviors, payload builders, admin form mapping, and extended configuration sections.
Build lockfiles
clients/*/buildscript-gradle.lockfile, clients/*/gradle.lockfile, gradle/libs.versions.toml
Bumped Android Gradle/Kotlin/ASM/tooling lockfile versions and updated AGP version in catalog.
Other runtime edits
clients/agent-runtime/src/...
Minor control-flow/test changes supporting gateway admin behavior and typed errors.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatApp as Chat App
    participant Gateway
    participant Runtime

    User->>ChatApp: Send message (UI)
    ChatApp->>Gateway: POST /web/chat/stream (SSE, X-Request-Id)
    Gateway->>Runtime: Deliver message
    Runtime-->>Gateway: SSE event: chunk
    Gateway-->>ChatApp: SSE event: chunk
    ChatApp->>ChatApp: Append chunk (status=streaming)

    alt Approval required
        Runtime-->>Gateway: SSE event: tool_approval
        Gateway-->>ChatApp: SSE event: tool_approval
        ChatApp->>User: Render ToolApprovalCard
        User->>ChatApp: Approve/reject
        ChatApp->>Gateway: POST /web/chat/tool-approval
        Gateway->>Runtime: Forward decision
        Runtime-->>Gateway: SSE event: chunk/resume
        Gateway-->>ChatApp: SSE event: chunk
    end

    Runtime-->>Gateway: SSE event: done (message_id, session_id)
    Gateway-->>ChatApp: SSE event: done
    ChatApp->>ChatApp: Set status=complete
Loading
sequenceDiagram
    participant Dashboard
    participant Timer as Poll Timer
    participant Gateway
    participant Runtime

    Dashboard->>Gateway: GET /web/admin/health (Auth)
    activate Gateway
    Gateway->>Runtime: Query health snapshot
    Runtime-->>Gateway: Return health snapshot
    Gateway-->>Dashboard: JSON health response
    Dashboard->>Dashboard: Render component statuses
    loop every 30s
        Timer->>Gateway: GET /web/admin/health
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/276-expand-dashboard-and-web-operational-clients-to-match-runtime-admin-capabilities

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 51

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
clients/web/apps/dashboard/src/composables/configPayload.spec.ts (1)

101-125: 🧹 Nitpick | 🔵 Trivial

Add test coverage for new autonomy policy fields.

The existing security section test doesn't exercise the new fields (autonomy_require_approval_for_medium_risk, autonomy_block_high_risk_commands, autonomy_auto_approve, autonomy_always_ask). If buildPayloadForSection fails to include these in the autonomy patch, this test would still pass.

💡 Suggested test addition
it("includes new autonomy policy fields in security payload", () => {
  const snapshot = createSnapshot();
  const payload = buildPayloadForSection(
    "security",
    createForm({
      autonomy_require_approval_for_medium_risk: false,
      autonomy_auto_approve: "ls, cat, echo",
    }),
    snapshot
  );

  expect(payload).toEqual({
    autonomy: {
      require_approval_for_medium_risk: false,
      auto_approve: ["ls", "cat", "echo"], // or however the list is serialized
    },
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/composables/configPayload.spec.ts` around
lines 101 - 125, The test for security payload is missing coverage for the new
autonomy policy fields; update the spec to add assertions that
buildPayloadForSection (used with createForm and createSnapshot) includes
autonomy_require_approval_for_medium_risk, autonomy_block_high_risk_commands,
autonomy_auto_approve, and autonomy_always_ask in the resulting autonomy patch.
Add a new it(...) or extend the existing test to supply those autonomy_* inputs
via createForm and assert the payload.autonomy contains the corresponding keys
with the correct types/serializations (e.g., boolean flags and a parsed array
for auto_approve) so buildPayloadForSection correctly nests and serializes the
new fields.
clients/web/apps/chat/src/App.spec.ts (1)

82-101: ⚠️ Potential issue | 🟡 Minor

Add test coverage for successful SSE streaming with chunk/done events.

The test correctly verifies the fallback when /web/chat/stream returns 500. However, the SSE success path—processing chunk and done events via ReadableStream—is unimplemented in the test suite. The composable logic in useChat.ts (lines 370–420) handles these events but has no corresponding test cases in either App.spec.ts or useChat.spec.ts. Add a test that mocks successful streaming with sample chunk/done events to confirm the streaming code path works as intended.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/chat/src/App.spec.ts` around lines 82 - 101, Add a new test
in App.spec.ts (or useChat.spec.ts) that covers the successful SSE streaming
path: mock fetch for the stream endpoint to resolve with a Response whose body
is a ReadableStream that emits serialized SSE messages representing "chunk"
events followed by a "done" event, then assert that the composable logic in
useChat.ts (the stream handling between lines ~370–420) consumes those chunks
and updates state as expected; specifically, create a mocked ReadableStream that
enqueues JSON payloads for chunk events and a final done event, ensure the test
awaits the stream completion, and verifies the resulting message
content/session_id/state changes produced by useChat's handlers for "chunk" and
"done".
clients/web/apps/dashboard/src/composables/useConfig.ts (1)

772-815: ⚠️ Potential issue | 🟠 Major

Preserve unsaved secret edits across unrelated saves.

Every successful save calls connectGateway(), which rebuilds form from server state. Only webhook_secret_* is copied out and restored, so pending edits in the new Brave, computer-use, Composio, and Cerebro secret inputs disappear if the user saves a different section first.

As per coding guidelines, "Security first, performance second" and "Look for behavioral regressions, missing tests, and contract breaks across modules."

Also applies to: 850-856

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/composables/useConfig.ts` around lines 772 -
815, processSaveResponse currently preserves only pendingWebhookSecret across
the connectGateway() call, causing unsaved secret edits for Brave, computer-use,
Composio, and Cerebro to be lost; capture the pending mode/value pairs for
web_search_brave_api_key, browser_computer_use_api_key, composio_api_key, and
memory_cerebro_auth_token before calling connectGateway() (similar to
pendingWebhookSecret), call connectGateway(), and then restore each pending pair
back into form (e.g., form.web_search_brave_api_key_mode/value,
form.browser_computer_use_api_key_mode/value, form.composio_api_key_mode/value,
form.memory_cerebro_auth_token_mode/value) if they were present so unrelated
saves don’t wipe in-progress secret edits in processSaveResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/gateway/admin.rs`:
- Around line 1785-1790: The response currently hardcodes task_count: 0 in
AdminSchedulerStatusView; replace this with the real runtime task count by
calling into the scheduler runtime (e.g., add or use a method like
Scheduler::task_count(&self) or .len() on the scheduler instance) and set
task_count: scheduler.task_count() (or convert to usize/u64 as needed). If a
scheduler reference is not available in this scope, obtain it from the
runtime/context where cfg is used (or thread-safe shared state such as
Arc<Mutex<Scheduler>>) and call the new accessor; alternatively, if you prefer a
quick change, make AdminSchedulerStatusView.task_count Option<u64> and return
None until the accessor is implemented. Ensure AdminSchedulerStatusView and any
serialization derive reflect the new type if you choose Option.

In `@clients/web/apps/chat/src/App.vue`:
- Around line 244-272: handleApprove and handleReject are currently UI-only
stubs; replace the local-only updates in handleApprove and handleReject with a
proper POST to the backend endpoint POST /web/chat/tool-approval, sending the
approvalId and action ("approve" or "reject"), then update messages.value based
on the successful response and handle errors (show toast/log and do not mutate
state on failure); if you cannot implement the backend round-trip now, open a
tracking issue linking these functions (handleApprove, handleReject) and the
endpoint POST /web/chat/tool-approval so the follow-up work to wire the full
approval round-trip is tracked.
- Around line 229-242: In restoreMessages, validate parsed sessionStorage data
before assigning to messages.value: after JSON.parse(raw)
(messagesStorageKey()/restoreMessages), run a type guard/filter that ensures
each item has the expected Message shape (e.g., numeric id, required string
fields like content/role or whatever Message defines) and only assign the
filtered array to messages.value; compute messageIdCounter using the filtered
array (fall back to current counter if empty) and keep the try/catch around
JSON.parse to ignore parse errors but avoid assigning malformed objects to
messages.value or using invalid ids.
- Around line 150-193: The outer catch around chat.streamMessage currently
swallows all exceptions and falls back to chat.sendMessage (causing duplicate
auth handling); change it so the catch only handles streaming-specific failures:
detect auth/credential errors (e.g., error.name/message or a sentinel like
error.isAuthError set by markCredentialInvalid in streamMessage) and rethrow
those immediately, while only proceeding to the sendMessage fallback for
non-auth streaming errors; keep updateAssistantMessage(assistantMessageId, ...)
behavior for streaming failures but avoid calling the fallback when the error
indicates credential invalidation.

In `@clients/web/apps/chat/src/components/chat/ChatMessage.vue`:
- Around line 34-35: The streaming cursor span in ChatMessage.vue is decorative
but currently has an aria-label and the streaming assistant text isn't
announced; make the span with class "streaming-cursor" purely decorative (remove
aria-label and add aria-hidden="true") and add an accessible live region for
streaming content by setting an appropriate aria-live (e.g., aria-live="polite")
and/or role="status" on the assistant message container (the element rendering
class "bubble-text" or its parent) when status === 'streaming' so screen readers
announce incremental updates; ensure the change only applies for assistant
messages and preserve visual styling.

In `@clients/web/apps/chat/src/components/chat/ToolApprovalCard.vue`:
- Around line 22-27: In ToolApprovalCard.vue update the approve/reject buttons
to set type="button" and guard against duplicate emits by using a one-shot flag
or the existing pending prop: only emit 'approve'/'reject' when not pending,
immediately set a local isSubmitting flag (or rely on pending prop) to true and
disable both buttons while true to prevent rapid double-clicks, and ensure the
click handler checks the flag before emitting (refer to the current
`@click`="$emit('approve', approvalId)" and `@click`="$emit('reject', approvalId)"
handlers and the approvalId prop).

In `@clients/web/apps/chat/src/components/HealthIndicator.spec.ts`:
- Around line 45-52: Add a new test in HealthIndicator.spec.ts that covers
non-OK HTTP responses by resolving fetch (via fetchMock) with a Response-like
object having status: 500 (and optional ok: false) and then asserting the same
disconnected behavior; specifically, replicate the existing "shows disconnected
on health check failure" flow but replace mockRejectedValueOnce with
mockResolvedValueOnce({ status: 500, ok: false }) (or the project's Response
shim) when calling mountIndicator(), await flushPromises(), and assert that
wrapper.get('[data-testid="health-status"]').classes() contains "disconnected".

In `@clients/web/apps/chat/src/components/HealthIndicator.vue`:
- Around line 21-24: Normalize props.gatewayUrl before composing the health
endpoint: ensure you strip any trailing slashes from props.gatewayUrl (and guard
against null/undefined) then append a single "/health" when calling fetch (the
string interpolation around `${props.gatewayUrl}/health`), so the request never
becomes "//health" or contains duplicate slashes; update the fetch call in
HealthIndicator.vue to use the normalized base URL (e.g., assign a local
variable like gatewayBase from props.gatewayUrl with trailing slashes removed)
and use that when building the request URL.

In `@clients/web/apps/chat/src/composables/useChat.ts`:
- Around line 399-409: The JSON.parse calls for the "done" and "error" SSE
branches can throw on malformed data; wrap the parsing in try-catch inside the
currentEvent === "done" and currentEvent === "error" branches (where doneEvent
is parsed as StreamDoneEvent and errorEvt as StreamErrorEvent) so parse failures
are handled gracefully—on parse error, log or process a fallback (e.g., log the
raw currentData and continue the stream or throw a controlled Error with a safe
message using t("chat.requestError", { text: normalizedMessage })) instead of
letting an unhandled exception bubble up; ensure setSessionReady still runs only
when a valid session_id is present after successful parse.

In `@clients/web/apps/chat/src/style.css`:
- Around line 1-2: The CSS `@import` lines currently use url(...) notation which
fails Stylelint; update the two imports (the `@import
url("@corvus/shared/app-shell.css")` and `@import url("tailwindcss")` lines) to
use quoted import syntax (e.g., `@import` "..." ) so they become plain quoted
`@import` statements without url(...) to satisfy the linter.

In `@clients/web/apps/dashboard/src/App.spec.ts`:
- Around line 240-241: Add a short inline comment above the two assertions
(expect(wrapper.findAll("input")).toHaveLength(12) and
expect(wrapper.findAll("[data-section]")).toHaveLength(7)) that documents why
those exact counts are expected (e.g., list the form fields, hidden inputs,
dynamic sections or components counted) so future maintainers can update the
numbers with context; modify the test near those assertions to include that
explanatory comment.

In `@clients/web/apps/dashboard/src/App.vue`:
- Around line 236-259: The five overview components (CostOverview, McpOverview,
TunnelOverview, ReliabilityOverview, HeartbeatOverview) each receive identical
auth props (config.baseUrl.value and config.bearerToken.value) and likely
perform redundant admin-config fetches; refactor to fetch the admin config once
in the parent (App.vue) or a shared composable (e.g., useAdminConfig) and pass
down the needed slices or derived props instead of raw auth values so children
consume the same snapshot and avoid fan-out/skew—implement a single async fetch
in App.vue (or a composable) that exposes the config object and pass specific
pieces (e.g., costConfig, mcpConfig, tunnelConfig, reliabilityConfig,
heartbeatConfig) to the respective components.
- Around line 266-268: UpdateSettings is not mounted because useConfig exposes
AdminConfigForm instead of the AdminConfigView that UpdateSettings requires; fix
by updating the useConfig hook (or adding a small adapter function) to return or
convert to AdminConfigView (or expose a getter like getAdminConfigView) so
UpdateSettings can be safely mounted in App.vue where the TODO is. Locate the
useConfig implementation and either change its return typing to include
AdminConfigView or add a conversion helper that maps AdminConfigForm ->
AdminConfigView, update UpdateSettings props/usage to consume that
AdminConfigView, and then mount UpdateSettings in App.vue using the provided
AdminConfigView from useConfig.
- Around line 226-264: The overview components (ChannelsOverview,
SchedulerStatus, CostOverview, McpOverview, TunnelOverview, ReliabilityOverview,
HeartbeatOverview, HealthDashboard) are mounting immediately and firing admin
fetches before pairing/auth is established; add a single ready/auth reactive
flag (e.g., isAuthenticated or isReady) in App.vue that becomes true only after
pairing/connection and valid config.bearerToken.value and config.baseUrl.value
are confirmed, then wrap each component tag with a v-if="isReady" (or a computed
like showOverviews() that checks config.bearerToken.value &&
config.baseUrl.value && pairing state) so they only mount/render once
authentication/connection is valid, preserving the existing prop bindings
(:gateway-url="config.baseUrl.value" and
:bearer-token="config.bearerToken.value").

In `@clients/web/apps/dashboard/src/components/config/BrowserSettings.vue`:
- Around line 35-49: The select change handler currently updates
modelValue.browser_computer_use_api_key_mode via updateField but does not clear
the stored secret; modify the change handler in BrowserSettings.vue so that
after calling updateField('browser_computer_use_api_key_mode', ...), if the new
value !== 'replace' you also call
updateField('browser_computer_use_api_key_value', '') (or null per existing
conventions) to wipe the plaintext API key from reactive state; apply the same
fix to the other analogous select that controls the related mode (the other
browser_computer_*_use_api_key_mode field) so both modes clear their
corresponding browser_computer_use_api_key_value when not 'replace'.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts`:
- Around line 27-33: Update the test in ChannelsOverview.spec.ts that stubs
global fetch (vi.stubGlobal(...)) to also assert the fetch call used the
expected admin endpoint and bearer auth: after the component renders, verify the
mocked fetch was called with the URL "/web/admin/channels" and that the Request
(or fetch call args) includes an Authorization header with the expected "Bearer
..." token/format (use the same auth source used by the app under test) so the
test locks the auth contract while still returning mockChannels.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts`:
- Around line 21-51: The test for "renders cost data on successful fetch" must
also assert the authenticated request contract: after mounting via
mountComponent() and flushPromises(), verify that the global fetch
(vi.stubGlobal) was called with the admin config endpoint and that the request
init includes an Authorization header with a Bearer token (e.g.,
expect(fetch).toHaveBeenCalledWith(expect.stringMatching(/\/admin\/config/),
expect.objectContaining({ headers: expect.objectContaining({ Authorization:
expect.stringMatching(/^Bearer\s+/) }) }))). If your component reads the token
from a helper (e.g., getAuthToken) or localStorage, set/stub that value in the
test before mounting so the Authorization header is predictable, and continue to
call vi.unstubAllGlobals() at the end.

In `@clients/web/apps/dashboard/src/components/config/HealthDashboard.spec.ts`:
- Around line 21-68: The test "renders health data on successful fetch"
currently stubs fetch but doesn't assert the request contract; update the test
(which uses vi.stubGlobal("fetch", ...) and mountComponent()) to also assert
fetch was called with the admin health endpoint and an Authorization Bearer
header: after await flushPromises(), add an assertion that the fetch mock was
called (e.g., expect(fetch).toHaveBeenCalled()) and verify its call args include
the expected URL (e.g., contains "/admin/health" or the exact endpoint your
component uses) and that the options object includes headers.Authorization set
to "Bearer <expectedToken>" (if the component reads a token from storage,
set/stub that token in the test before mountComponent so you can assert the
exact value). Ensure you reference the existing fetch stub and mountComponent
when adding these assertions.

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.spec.ts`:
- Around line 21-58: The tests currently stub global fetch but don’t assert
Authorization header propagation; update both tests ("renders heartbeat data on
successful fetch" and "shows error on fetch failure") to verify that fetch was
called with an Authorization: Bearer <token> header by referencing the test
setup's bearerToken and the mountComponent() call; after mounting and awaiting
flushPromises(), add an expectation that vi.mocked(global.fetch) (or the fetch
spy) was called and inspect the call args (Request or init.headers) to assert
the Authorization header equals `Bearer ${bearerToken}` so the admin config
fetch contract is exercised for auth forwarding.

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue`:
- Around line 46-61: The template currently renders nothing when fetch returns
but data.config?.heartbeat is null; update HeartbeatOverview.vue to handle a
null/absent heartbeat by rendering an explicit empty-state within the same
status-grid (or an early conditional block) that displays a clear "not
configured" / "unavailable" message and consistent status visuals. Specifically,
keep the existing bindings (heartbeat.enabled and heartbeat.interval_minutes)
for the normal path but add a fallback path when heartbeat is falsy that shows a
status-item with label t("heartbeat.enabled") and a status-indicator +
status-value indicating not configured/unavailable, and optionally a second
status-item for interval showing “—” or “N/A”; ensure the conditional around the
block checks for heartbeat !== null/undefined (or add a v-else) so the
empty-state is rendered when data.config?.heartbeat is null.
- Around line 58-60: The UI currently hardcodes the unit "min" next to
heartbeat.interval_minutes in HeartbeatOverview.vue; change this to use the i18n
translation instead (e.g., call t with a key like "heartbeat.unit_min" or use a
plural/seconds formatter) so the unit is localized—update the span that renders
{{ heartbeat.interval_minutes }} min to concatenate or format via t(...) (or a
number/relative formatter) and ensure the new translation key is added to locale
files; keep the displayed number source as heartbeat.interval_minutes and only
replace the literal "min".

In `@clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts`:
- Around line 21-70: The tests currently stub global fetch but never assert that
the Authorization header with the bearer token is sent; update both specs in
McpOverview.spec.ts (the "renders MCP servers on successful fetch" and "shows
error on fetch failure" tests that call mountComponent()) to assert the fetch
stub was called with an init object whose headers.Authorization contains the
expected "Bearer <token>" string (inspect
vi.getMockedFunction(fetch).mock.calls[0][1].headers or use vi.fn() call
assertions) so any dropped/misformatted Authorization header is detected.

In `@clients/web/apps/dashboard/src/components/config/McpOverview.vue`:
- Around line 59-79: The template assumes mcp.servers and server.capabilities
are always arrays; add defensive guards to avoid render-time exceptions by
checking for arrays before using .length or .join. Update the v-if and v-for
usages around mcp.servers (e.g., replace v-if="mcp.servers.length > 0" and
v-for="server in mcp.servers" with checks like Array.isArray(mcp?.servers) and
iterating over mcp.servers || []) and guard the capabilities block and join
(e.g., v-if="Array.isArray(server?.capabilities) && server.capabilities.length >
0" and use server.capabilities.join(', ') only when it's an array). Ensure the
status and command bindings still render safely when fields are missing (use
optional chaining or fallback values where referenced).

In `@clients/web/apps/dashboard/src/components/config/MemorySettings.vue`:
- Around line 67-76: The select change handler for
memory_cerebro_auth_token_mode currently only updates the mode and leaves
modelValue.memory_cerebro_auth_token_value populated; update the handler used in
the <select> (and the similar handler at the other occurrence) to, after calling
updateField('memory_cerebro_auth_token_mode', ...), check the new mode value and
if it is not 'replace' call updateField('memory_cerebro_auth_token_value', '')
to clear the plaintext token from reactive state (use the same AdminConfigForm
type casting as shown), ensuring the token is wiped whenever mode !== 'replace'.

In
`@clients/web/apps/dashboard/src/components/config/ReliabilityOverview.spec.ts`:
- Around line 37-52: Add an explicit assertion that the stubbed global fetch was
called with the expected admin URL and Authorization header to enforce the admin
auth contract: after mounting via mountComponent() and flushPromises(), assert
that the fetch mock (vi.fn() used in vi.stubGlobal("fetch")) was called at least
once and that one of its calls includes the correct admin endpoint (matching
mockConfig or the component's admin URL) and an Authorization header containing
the admin token; update the test to inspect fetch.mock.calls (or
vi.mocked(fetch).mock.calls) and assert the request init.headers.Authorization
(or headers["Authorization"]) equals the expected token to catch regressions
that drop headers or hit the wrong URL.
- Around line 53-66: The test currently calls vi.unstubAllGlobals() inside the
test body which can leave global stubs in place if the test errors; move the
global unstub cleanup into an afterEach hook so fetch is always restored. Add an
afterEach(() => vi.unstubAllGlobals()) near the top of
ReliabilityOverview.spec.ts (alongside existing setup/teardown), and remove the
per-test vi.unstubAllGlobals() calls from the "shows error on fetch failure"
test (which uses vi.stubGlobal("fetch", ...)), ensuring mountComponent and
flushPromises remain unchanged.

In `@clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue`:
- Around line 64-69: The UI currently displays only the keys of
reliability.model_fallbacks which hides the fallback targets; update the
rendering inside the <span class="status-value"> to iterate
Object.entries(reliability.model_fallbacks) and render each entry as "primary →
fallback" (e.g., map entries to `${primary} → ${fallback}` and join with ", "),
falling back to t("reliability.none") when there are no entries; keep this
change localized to the reliability.model_fallbacks usage in the
ReliabilityOverview component so the status-label remains the same.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts`:
- Around line 21-47: The test "renders scheduler status on successful fetch"
stubs global fetch but doesn't assert the authenticated request; update the test
(around mountComponent and vi.stubGlobal usage) to set a known token (e.g.,
const token = "test-token") the component will use (via localStorage or the same
token-fetch helper your code uses), then assert the fetch mock was called with
the scheduler endpoint URL and an Authorization header containing "Bearer
test-token" by adding an expectation like
expect(fetch).toHaveBeenCalledWith(expect.stringContaining("/scheduler"),
expect.objectContaining({ headers: expect.objectContaining({ Authorization:
`Bearer ${token}` }) })); ensure you still call vi.unstubAllGlobals() at the
end.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue`:
- Around line 18-38: Replace the one-time onMounted(fetchSchedulerStatus) call
with a watcher so fetchSchedulerStatus runs whenever the gateway or auth
changes: import watch from Vue (add to the existing import line), remove or
keep-onMounted but add watch([() => props.gatewayUrl, () => props.bearerToken],
fetchSchedulerStatus, { immediate: true }) so fetchSchedulerStatus (the async
function) re-runs on changes to props.gatewayUrl and props.bearerToken; ensure
the watcher uses the same fetchSchedulerStatus, and remove duplicate initial
fetch if you keep onMounted.

In `@clients/web/apps/dashboard/src/components/config/SecuritySettings.spec.ts`:
- Around line 32-42: Add interaction assertions for the new autonomy controls:
locate the test in SecuritySettings.spec.ts that already mounts the component
and currently checks existence of data-testid selectors
'autonomy-require-approval-medium-risk', 'autonomy-block-high-risk-commands',
'autonomy-auto-approve', and 'autonomy-always-ask'; for each control simulate
user interaction (checkboxes: trigger('click') or setChecked; textareas/inputs:
setValue or trigger('input')) and assert the component emitted the expected
'update:modelValue' events with the updated payload (use
wrapper.find('[data-testid="..."]').trigger(...) / setValue(...) and then
expect(wrapper.emitted('update:modelValue')) to include the new values for the
corresponding fields).

In `@clients/web/apps/dashboard/src/components/config/SecuritySettings.vue`:
- Around line 103-116: The two textarea placeholders in SecuritySettings.vue are
hardcoded ("command1, command2"); replace them with the i18n translator (e.g.,
use $t("security.placeholderCommands")) for both the autonomy_auto_approve and
autonomy_always_ask textareas (refer to the textarea elements with data-testid
"autonomy-auto-approve" and "autonomy-always-ask" and the updateField handler).
Add the new "security.placeholderCommands" key to the English and Spanish
translation files (or the appropriate locale files) to maintain EN/ES parity, or
mark the key for translation if not yet available.

In `@clients/web/apps/dashboard/src/components/config/TunnelOverview.spec.ts`:
- Around line 21-63: The tests "renders tunnel data on successful fetch" and
"shows error on fetch failure" currently stub fetch but never assert the
Authorization header; update both specs to pass a known bearerToken into
mountComponent (or the component props used by mountComponent) and after
flushPromises assert that the global fetch was called with request init
containing headers.Authorization === `Bearer ${bearerToken}` (use
vi.stubGlobal("fetch", ...) and expect(fetch).toHaveBeenCalledWith(...,
expect.objectContaining({ headers: expect.objectContaining({ Authorization:
`Bearer ${bearerToken}` }) })) so the tunnel fetch auth propagation is
validated; keep assertions in the same tests and still call
vi.unstubAllGlobals() after.

In `@clients/web/apps/dashboard/src/components/config/UpdateSettings.vue`:
- Around line 25-35: The template currently treats missing booleans as "No";
update the two bindings (config.updates?.status?.update_available and
config.updates?.auto_install_enabled) to explicitly distinguish
true/false/undefined by rendering the "yes" text only when === true, the "no"
text only when === false, and an "unknown" (or placeholder like '-') when the
value is null/undefined; apply this change for both data-testid elements
(updates_update_available and updates_auto_install_enabled) so unknown state is
shown instead of defaulting to "No".

In `@clients/web/apps/dashboard/src/composables/configPayload.ts`:
- Around line 367-368: The current early-return for "updates" (if (section ===
"updates") return {}) silently produces an empty payload; either make this
section truly read-only by removing its save path in the consumer (i.e., stop
calling save for "updates") or implement a proper payload builder instead of
returning {}. Update the branch handling section === "updates" to construct and
return the real payload object (mirror the pattern used for other sections in
this module), or if intended read-only, remove any save/submit handlers that
rely on this function for "updates" so nothing is sent to the gateway.

In `@clients/web/apps/dashboard/src/style.css`:
- Around line 1-2: Replace the CSS `@import` url(...) syntax with string import
notation to satisfy the Stylelint import-notation rule; update the two imports
in style.css (the lines importing "@corvus/shared/app-shell.css" and
"tailwindcss") to use plain string form (e.g., `@import` "..." ) so Stylelint no
longer flags import-notation violations.

In `@clients/web/apps/docs/src/content/docs/es/guides/css-architecture.md`:
- Around line 1-9: The new Spanish guide "Arquitectura CSS"
(docs/es/guides/css-architecture.md) is orphaned in the docs graph causing CI to
fail; register it by adding a sidebar entry under the Spanish docs sidebar
configuration or add a persistent link to an indexed Spanish page (for example
include a link from an existing ES guide or the Spanish homepage) so the file is
reachable; ensure the title "Arquitectura CSS" and its frontmatter
(appliesTo/main, owner/team-platform) are used in the sidebar/link text so the
Docs Quality checker recognizes the page.

In `@clients/web/apps/docs/src/content/docs/guides/css-architecture.md`:
- Around line 1-9: The "CSS Architecture" guide (css-architecture.md) is
currently orphaned and missing a Spanish translation; fix by adding this guide
to the site's sidebar configuration (or add an inbound link to it from an
existing guide) so it is discoverable by the pipeline, and either add a Spanish
translation file for css-architecture.md under the ES docs directory or insert a
clear "Translation pending" notice at the top of the English guide indicating
the missing Spanish version.
- Around line 63-67: The three list items all begin with the word "Put", which
is repetitive; reword the bullets to vary sentence structure while preserving
meaning by using alternative verbs and phrasing for the entries referencing
packages/shared/theme.css, packages/shared/base.css, and
packages/shared/app-shell.css, e.g. change one or more "Put" lines to "Store",
"Place", "Keep", or "Define", and adjust the Astro/Starlight, landing-page, and
component-local style lines similarly so each bullet reads distinctly but still
directs tokens/aliases/theme variables to theme.css, resets/baseline rules to
base.css, app-frame concerns to app-shell.css, presentation rules to the owning
app, and component-specific styles inside the component.

In `@clients/web/apps/docs/src/styles/custom.css`:
- Around line 7-8: Stylelint flags the two imports using the url(...) form;
replace the url(...) imports with the linted string form so they pass
import-notation checks — change lines importing "@corvus/shared/base.css" and
"@corvus/shared/theme.css" from `@import` url("...") to the string form `@import`
"..." (update the two import statements shown).

In `@clients/web/apps/marketing/src/styles/global.css`:
- Around line 1-2: The `@import` lines in global.css use url(...) without quotes
which violates Stylelint's import-notation rule; update both imports (the lines
importing "@corvus/shared/theme.css" and "@corvus/shared/base.css") to use
quoted notation (e.g., replace url(...) usage with the quoted string form) so
the linter accepts the imports.

In `@clients/web/packages/shared/app-shell.css`:
- Around line 1-2: Stylelint rejects the url() wrapper in the `@import` rules;
update the two `@import` statements that reference "./theme.css" and "./base.css"
to use string notation (e.g., `@import` "<path>";) instead of `@import` url(...); to
satisfy Stylelint's expected syntax.

In `@gradle/libs.versions.toml`:
- Line 13: Update the AGP version referenced by the agp key in
libs.versions.toml from 8.13.2 to 9.1.0; after changing the agp = value, run a
quick compatibility check with the current Gradle (e.g., 9.5.0-milestone-1) and
Kotlin (2.3.20) toolchain, fix any breaking API changes or Gradle plugin
compatibility issues reported by the build, and update any related plugin
configuration or migration steps (AGP migrationGuide changes) to ensure the
project builds cleanly with agp set to 9.1.0.

In
`@openspec/changes/archive/2026-03-28-web-operational-parity/archive-report.md`:
- Around line 59-62: Update the final section titled "## SDD Cycle Complete" to
avoid claiming full completion: change the wording to indicate the change is
archived with known follow-ups and outstanding items (partial spec compliance,
an untested scenario, and skipped integration tests) so the archive remains
accurate — reference the "## SDD Cycle Complete" header and the footer paragraph
to rephrase it as "Archived with known follow-ups" and list the specific
outstanding items mentioned earlier in the report.

In `@openspec/changes/archive/2026-03-28-web-operational-parity/design.md`:
- Around line 77-84: Design currently lists GET /web/admin/tasks returning
AdminTaskListView but the implementation (see admin.rs) exposes GET
/web/admin/scheduler returning AdminSchedulerStatusView; update the design to
match the code (replace /web/admin/tasks and AdminTaskListView with
/web/admin/scheduler and AdminSchedulerStatusView) or explicitly document both
endpoints and their intended differences, ensuring the endpoint path and
response type in the design match the handler name and view type used in
admin.rs.
- Line 17: Update the fenced code blocks in the document to include language
identifiers: mark ASCII diagrams and sequence diagrams with ```text or
```plaintext, and mark HTTP examples (the blocks showing HTTP
requests/responses) with ```http or ```text; locate the relevant fenced blocks
by searching for the ASCII/sequence diagram blocks and the HTTP example blocks
in the document and add the appropriate language token to each opening ```
fence.

In `@openspec/changes/archive/2026-03-28-web-operational-parity/exploration.md`:
- Around line 29-39: The heading "Dashboard Coverage (~10 config sections)" is
incorrect relative to the listed components; update the heading to match the
enumerated components or expand the list accordingly — either change the heading
to "Dashboard Coverage (~7 config sections)" or add the missing config-section
entries to the list; verify you adjust the text that references the component
names (GeneralSettings.vue, SecuritySettings.vue, ObservabilitySettings.vue,
RuntimeSettings.vue, SchedulerSettings.vue, GatewaySettings.vue,
WebhookSettings.vue) so the count and list remain consistent.

In `@openspec/changes/archive/2026-03-28-web-operational-parity/proposal.md`:
- Around line 91-93: Update the proposal to use the implemented route name
`/web/admin/scheduler` instead of the stale `/web/admin/tasks`: locate the
bullet that currently reads "Add `GET /web/admin/tasks` endpoint for scheduled
task list" and change it to "Add `GET /web/admin/scheduler` endpoint for
scheduled task list"; keep the other bullets (e.g., `GET /web/admin/health` and
the skills inventory endpoint) unchanged unless their routes differ from the
implementation—verify and align any mention of the skills inventory
endpoint/component with the actual runtime enumeration route name as well.

In `@openspec/changes/archive/2026-03-28-web-operational-parity/tasks.md`:
- Around line 250-258: Update the task descriptions and acceptance criteria to
match the implemented endpoint and handler: replace references to GET
/web/admin/tasks and handle_admin_tasks with GET /web/admin/scheduler and
handle_admin_scheduler_status, and update the task bullets (3.10 and 3.11) to
enumerate the scheduler route, returned fields, and tests accordingly so
documentation aligns with the deployed implementation.

In `@openspec/changes/archive/2026-03-28-web-operational-parity/verify-report.md`:
- Around line 14-22: The summary table in verify-report.md incorrectly shows
59/59 complete while the body notes missing artifacts for tasks referencing
ProviderPoolsSettings.vue and IdentitySettings.vue; update the report so metrics
match reality by either decrementing "Tasks complete" and incrementing "Tasks
incomplete" to reflect those two missing files or mark those specific tasks as
"deviated/incomplete" in tasks.md and the verify-report text, and add a short
note that IdentitySettings functionality was folded into SecuritySettings.vue
(referencing ProviderPoolsSettings.vue, IdentitySettings.vue,
SecuritySettings.vue, and tasks.md) so auditors can trace the deviation.

In `@openspec/specs/web-operational-parity/spec.md`:
- Around line 151-163: REQ-5 in spec.md is out of sync with the implementation:
the code exposes GET /web/admin/scheduler and SchedulerStatus.vue shows
aggregate scheduler status, not a task list from GET /web/admin/tasks. Update
the spec (REQ-5) to reference the actual endpoint GET /web/admin/scheduler,
describe the returned aggregate scheduler fields (e.g., overall status, last run
timestamp/outcome, next run, enabled flag), and update the dashboard doc to
state that SchedulerStatus.vue renders aggregate scheduler status (leaving
SchedulerSettings.vue for config); alternatively, if you prefer to keep the
spec, implement GET /web/admin/tasks and add task-list rendering to
SchedulerStatus.vue—pick one consistency path and make the spec and code
references (GET /web/admin/scheduler, GET /web/admin/tasks, SchedulerStatus.vue,
SchedulerSettings.vue) match.

---

Outside diff comments:
In `@clients/web/apps/chat/src/App.spec.ts`:
- Around line 82-101: Add a new test in App.spec.ts (or useChat.spec.ts) that
covers the successful SSE streaming path: mock fetch for the stream endpoint to
resolve with a Response whose body is a ReadableStream that emits serialized SSE
messages representing "chunk" events followed by a "done" event, then assert
that the composable logic in useChat.ts (the stream handling between lines
~370–420) consumes those chunks and updates state as expected; specifically,
create a mocked ReadableStream that enqueues JSON payloads for chunk events and
a final done event, ensure the test awaits the stream completion, and verifies
the resulting message content/session_id/state changes produced by useChat's
handlers for "chunk" and "done".

In `@clients/web/apps/dashboard/src/composables/configPayload.spec.ts`:
- Around line 101-125: The test for security payload is missing coverage for the
new autonomy policy fields; update the spec to add assertions that
buildPayloadForSection (used with createForm and createSnapshot) includes
autonomy_require_approval_for_medium_risk, autonomy_block_high_risk_commands,
autonomy_auto_approve, and autonomy_always_ask in the resulting autonomy patch.
Add a new it(...) or extend the existing test to supply those autonomy_* inputs
via createForm and assert the payload.autonomy contains the corresponding keys
with the correct types/serializations (e.g., boolean flags and a parsed array
for auto_approve) so buildPayloadForSection correctly nests and serializes the
new fields.

In `@clients/web/apps/dashboard/src/composables/useConfig.ts`:
- Around line 772-815: processSaveResponse currently preserves only
pendingWebhookSecret across the connectGateway() call, causing unsaved secret
edits for Brave, computer-use, Composio, and Cerebro to be lost; capture the
pending mode/value pairs for web_search_brave_api_key,
browser_computer_use_api_key, composio_api_key, and memory_cerebro_auth_token
before calling connectGateway() (similar to pendingWebhookSecret), call
connectGateway(), and then restore each pending pair back into form (e.g.,
form.web_search_brave_api_key_mode/value,
form.browser_computer_use_api_key_mode/value, form.composio_api_key_mode/value,
form.memory_cerebro_auth_token_mode/value) if they were present so unrelated
saves don’t wipe in-progress secret edits in processSaveResponse.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d98cf928-7b77-4fe1-a801-3889b1bc0048

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8b7c8 and 2f9303b.

⛔ Files ignored due to path filters (1)
  • clients/web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (76)
  • clients/agent-runtime/src/gateway/admin.rs
  • clients/agent-runtime/src/gateway/mod.rs
  • clients/androidApp/buildscript-gradle.lockfile
  • clients/androidApp/gradle.lockfile
  • clients/composeApp/buildscript-gradle.lockfile
  • clients/web/CSS_ARCHITECTURE.md
  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/chat/src/components/HealthIndicator.spec.ts
  • clients/web/apps/chat/src/components/HealthIndicator.vue
  • clients/web/apps/chat/src/components/chat/ChatMessage.vue
  • clients/web/apps/chat/src/components/chat/ToolApprovalCard.spec.ts
  • clients/web/apps/chat/src/components/chat/ToolApprovalCard.vue
  • clients/web/apps/chat/src/composables/useChat.spec.ts
  • clients/web/apps/chat/src/composables/useChat.ts
  • clients/web/apps/chat/src/style.css
  • clients/web/apps/chat/src/types/chat.ts
  • clients/web/apps/dashboard/src/App.spec.ts
  • clients/web/apps/dashboard/src/App.vue
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.spec.ts
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/MemorySettings.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/SecuritySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/SecuritySettings.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.vue
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/apps/dashboard/src/composables/configPayload.ts
  • clients/web/apps/dashboard/src/composables/useConfig.ts
  • clients/web/apps/dashboard/src/style.css
  • clients/web/apps/dashboard/src/test/adminConfigFormFactory.ts
  • clients/web/apps/dashboard/src/types/admin-config.ts
  • clients/web/apps/docs/src/content/docs/es/guides/css-architecture.md
  • clients/web/apps/docs/src/content/docs/guides/css-architecture.md
  • clients/web/apps/docs/src/styles/custom.css
  • clients/web/apps/marketing/src/styles/global.css
  • clients/web/package.json
  • clients/web/packages/locales/src/en.json
  • clients/web/packages/locales/src/es.json
  • clients/web/packages/shared/README.md
  • clients/web/packages/shared/app-shell.css
  • clients/web/packages/shared/base.css
  • clients/web/packages/shared/package.json
  • clients/web/packages/shared/theme.css
  • clients/web/stylelint.config.mjs
  • gradle/libs.versions.toml
  • openspec/changes/archive/2026-03-28-web-operational-parity/archive-report.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/design.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/exploration.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/proposal.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/specs/web-operational-parity/spec.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/tasks.md
  • openspec/changes/archive/2026-03-28-web-operational-parity/verify-report.md
  • openspec/specs/web-operational-parity/spec.md
  • pnpm-workspace.yaml

Comment thread clients/agent-runtime/src/gateway/mod.rs
Comment thread clients/web/apps/chat/src/components/chat/ChatMessage.vue Outdated
Comment thread clients/web/apps/chat/src/components/chat/ToolApprovalCard.vue Outdated
Comment thread clients/web/apps/chat/src/components/HealthIndicator.spec.ts
Comment thread clients/web/apps/chat/src/components/HealthIndicator.vue Outdated
Comment thread openspec/changes/archive/2026-03-28-web-operational-parity/tasks.md
Comment thread openspec/specs/web-operational-parity/spec.md
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment thread clients/agent-runtime/src/gateway/admin.rs
Comment thread clients/web/apps/chat/src/App.vue
Comment thread clients/web/apps/chat/src/App.vue
Comment thread clients/web/apps/chat/src/composables/useChat.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue Outdated
Comment thread clients/web/apps/docs/src/content/docs/guides/css-architecture.md Outdated
Comment thread clients/web/apps/docs/src/content/docs/guides/css-architecture.md Outdated
Comment thread clients/web/apps/docs/src/styles/custom.css
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 22

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
clients/web/packages/shared/index.ts (1)

49-51: 🧹 Nitpick | 🔵 Trivial

Inconsistent trailing-slash removal: regex still used here.

resolvePublicUrl uses .replace(/\/$/, "") while trimTrailingSlashes was added to avoid regex-based removal. Consider using the new helper for consistency and to fully mitigate S5852 concerns across the module.

Proposed fix
   try {
-    return new URL(candidate).toString().replace(/\/$/, "");
+    return trimTrailingSlashes(new URL(candidate).toString());
   } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/packages/shared/index.ts` around lines 49 - 51, The try block in
resolvePublicUrl still uses a regex-based trailing-slash removal
(.replace(/\/$/, "")) which is inconsistent with and defeats the purpose of the
new trimTrailingSlashes helper; update resolvePublicUrl to call
trimTrailingSlashes on the URL string returned by new URL(candidate).toString()
(and ensure trimTrailingSlashes is imported/visible to that module) so all
trailing-slash trimming uses the new helper instead of a regex.
♻️ Duplicate comments (4)
clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts (1)

110-134: ⚠️ Potential issue | 🟡 Minor

Make the unchanged -> clear test proving as well.

At Line 113, the test does not seed a non-empty composio_api_key_value, so Line 131 can pass even if the clear behavior regresses for this transition.

Suggested test adjustment
-  it("clears api key value when mode changes to clear", async () => {
+  it("clears non-empty api key value when mode changes from unchanged to clear", async () => {
     const wrapper = mount(ComposioSettings, {
       props: {
         modelValue: createAdminConfigForm({
           composio_api_key_mode: "unchanged",
+          composio_api_key_value: "existing-key-789",
         }),
         disabled: false,
         saving: false,
       },

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`
around lines 110 - 134, The test in ComposioSettings.spec.ts doesn't seed a
non-empty composio_api_key_value so the "unchanged -> clear" behavior can pass
even if clearing regresses; update the test that mounts ComposioSettings (using
createAdminConfigForm) to initialize modelValue with composio_api_key_mode:
"unchanged" AND a non-empty composio_api_key_value (e.g., "secret"), then change
the select to "clear" and assert the emitted update.modelValue contains
composio_api_key_mode: "clear" and composio_api_key_value: "" to prove the clear
transition works.
clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts (1)

103-128: ⚠️ Potential issue | 🟡 Minor

Add the replace -> unchanged reset case.

updateSecretMode() clears web_search_brave_api_key_value for every mode other than "replace", but this spec only locks down the "clear" branch. Add the "unchanged" transition too so both reset paths stay covered.

Suggested test
   it("clears brave api key value when switching from replace to clear", async () => {
     const wrapper = mount(WebSearchSettings, {
       props: {
         modelValue: createAdminConfigForm({
           web_search_brave_api_key_mode: "replace",
           web_search_brave_api_key_value: "brave-key-789",
         }),
         disabled: false,
         saving: false,
       },
       global: {
         plugins: [createI18n({ ...i18nConfig, locale: "en" })],
       },
     });

     await wrapper.get('[data-testid="web_search_brave_api_key_mode"]').setValue("clear");

     const updates = wrapper.emitted("update:modelValue");
     expect(updates).toHaveLength(1);
     expect(updates?.[0]?.[0]).toEqual(
       expect.objectContaining({
         web_search_brave_api_key_mode: "clear",
         web_search_brave_api_key_value: "",
       })
     );
   });
+
+  it("clears brave api key value when switching from replace to unchanged", async () => {
+    const wrapper = mount(WebSearchSettings, {
+      props: {
+        modelValue: createAdminConfigForm({
+          web_search_brave_api_key_mode: "replace",
+          web_search_brave_api_key_value: "brave-key-789",
+        }),
+        disabled: false,
+        saving: false,
+      },
+      global: {
+        plugins: [createI18n({ ...i18nConfig, locale: "en" })],
+      },
+    });
+
+    await wrapper.get('[data-testid="web_search_brave_api_key_mode"]').setValue("unchanged");
+
+    expect(wrapper.emitted("update:modelValue")?.[0]?.[0]).toEqual(
+      expect.objectContaining({
+        web_search_brave_api_key_mode: "unchanged",
+        web_search_brave_api_key_value: "",
+      })
+    );
+  });
As per coding guidelines, “Look for behavioral regressions, missing tests, and contract breaks across modules.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts`
around lines 103 - 128, The test currently covers the "replace -> clear" branch
but misses the "replace -> unchanged" branch: update the test suite in
WebSearchSettings.spec.ts (the existing it block or by adding a new it) to also
simulate setting the select with data-testid "web_search_brave_api_key_mode" to
"unchanged" and assert that the emitted update:modelValue contains
web_search_brave_api_key_mode: "unchanged" and web_search_brave_api_key_value:
"" (since updateSecretMode() clears non-"replace" values); reference
updateSecretMode() and the select with data-testid
"web_search_brave_api_key_mode" when locating where to add the assertion.
clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue (1)

24-45: ⚠️ Potential issue | 🟠 Major

Close the malformed-number gap in updateField().

type="number" inputs can still surface exponent/partial strings. This helper only normalizes values that Number() accepts as finite, so malformed values can still be emitted raw here and then be silently dropped or coerced when buildWebSearchPayload() reparses them in clients/web/apps/dashboard/src/composables/configPayload.ts:265-289.

Suggested fix
 function updateField<Key extends keyof AdminConfigForm>(
   key: Key,
   value: AdminConfigForm[Key]
 ): void {
   let clamped = value;
-  if (key === "web_search_max_results") {
-    const n = Number(value);
-    if (Number.isFinite(n)) {
-      clamped = `${Math.max(1, Math.min(10, Math.round(n)))}` as AdminConfigForm[Key];
-    }
-  }
-  if (key === "web_search_timeout_secs") {
-    const n = Number(value);
-    if (Number.isFinite(n)) {
-      clamped = `${Math.max(1, Math.round(n))}` as AdminConfigForm[Key];
-    }
+  if (key === "web_search_max_results" || key === "web_search_timeout_secs") {
+    const raw = String(value).trim();
+    if (!/^\d+$/.test(raw)) return;
+    const parsed = Number.parseInt(raw, 10);
+    clamped = (
+      key === "web_search_max_results"
+        ? Math.max(1, Math.min(10, parsed))
+        : Math.max(1, parsed)
+    ).toString() as AdminConfigForm[Key];
   }
   emit("update:modelValue", {
     ...props.modelValue,
     [key]: clamped,
   });
As per coding guidelines, “Validate input boundaries, auth/authz implications, and secret management.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue`
around lines 24 - 45, The updateField function currently emits raw non-finite
numeric strings (e.g., partial/exponent input) for keys "web_search_max_results"
and "web_search_timeout_secs"; change the logic in updateField so that when
Number(value) is not finite you do NOT emit the raw malformed string—either
return early (no emit) or set clamped to a safe normalized value such as an
empty string ('' as AdminConfigForm[Key]) before emitting; update the branches
that handle "web_search_max_results" and "web_search_timeout_secs" accordingly
so malformed inputs are filtered here instead of relying on
buildWebSearchPayload to reparse them.
clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue (1)

35-36: 🧹 Nitpick | 🔵 Trivial

Dead code branch and missing payload validation.

Per the backend (handle_admin_channels), the response is always { "channels": [...] }. The Array.isArray(data) branch never executes. Remove it and add basic shape validation.

Proposed fix
     const data = await res.json();
-    channels.value = Array.isArray(data) ? data : (data.channels ?? []);
+    if (!data || typeof data !== "object" || !Array.isArray(data.channels)) {
+      throw new Error("Invalid response shape");
+    }
+    channels.value = data.channels;

As per coding guidelines, "Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue` around
lines 35 - 36, The current response handling in ChannelsOverview.vue assigns
channels.value from a branch that never occurs; remove the Array.isArray(data)
branch and instead validate the payload shape returned by the backend: after
const data = await res.json(), check that data is an object and that
Array.isArray(data.channels) is true, then set channels.value = data.channels;
otherwise set channels.value = [] (and optionally log or surface an error).
Update the assignment logic around channels.value to only accept the expected {
channels: [...] } shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/web/apps/chat/src/App.vue`:
- Around line 151-166: The stream handler currently overwrites the assistant
message on each SSE chunk; change the callback passed to chat.streamMessage so
it appends each chunk to the existing content instead of replacing it (use
messages.value.find(m => m.id === assistantMessageId)?.content or maintain a
local buffer to concatenate previous content + chunk before calling
updateAssistantMessage with status "streaming"), then keep the final
updateAssistantMessage call that sets status "complete" using the assembled
content; also add a new App.spec.ts test that simulates streamMessage emitting
at least two chunk events to assert that the final assistant message contains
the concatenation of both chunks and that status transitions from "streaming" to
"complete".

In `@clients/web/apps/chat/src/composables/useChat.ts`:
- Around line 321-334: The 403 webhook branch only checks for error.code ===
"approval_required" and mistakenly treats other approval-shaped errors as
invalid credentials; update the handler in useChat.ts (the response.status ===
403 block) to parse the error body with the same logic used by streamMessage() —
call parseApprovalErrorType(errorData) (or equivalent parser) and accept
returned approval types including "approval_contract" as valid approval
responses, returning the appropriate object (tool, reason, sessionId/default to
currentSessionId.value) instead of calling gateway.markCredentialInvalid() and
clearSession(); only mark credentials invalid when the parsed result is
definitively a non-approval auth error.

In `@clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts`:
- Line 46: Remove the unnecessary async modifiers from the four test
declarations that don't await anything: the it blocks with descriptions "shows
password input when computer use api key mode is replace", "shows password input
when browser use api key mode is replace" (line ~64), "shows api key input when
browser use api key mode is configure" (line ~81), and "shows api key input when
computer use api key mode is configure" (line ~108); edit those it(...)
declarations in BrowserSettings.spec.ts to drop the async keyword so each
becomes a plain synchronous test function.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue`:
- Around line 50-51: In ChannelsOverview.vue, the status messages for loading
and error lack ARIA live regions; update the <p v-if="loading" class="helper">{{
t("channels.loading") }}</p> to include aria-live="polite" and role="status"
(and aria-atomic="true" if desired), and update the <p v-else-if="error"
class="error">{{ error }}</p> to include aria-live="assertive" and role="alert"
(or role="status" with aria-live="assertive") so the loading and error states
(variables loading and error) are announced like in CostOverview.vue.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`:
- Around line 48-81: Add a regression test to ComposioSettings.spec.ts that
mounts ComposioSettings with modelValue from createAdminConfigForm where
composio_api_key_mode is "unchanged" and composio_has_api_key is true (and
composio_api_key_value is set to a non-empty string), then assert that the input
with data-testid="composio_api_key_value" does NOT render or display the secret
value; ensure the test references ComposioSettings, composio_api_key_mode,
composio_has_api_key, and data-testid="composio_api_key_value" so it fails if
the component accidentally exposes the secret in "unchanged" mode.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue`:
- Around line 63-70: The UI currently prefixes USD values with a hardcoded "$"
in CostOverview.vue (see cost.daily_limit_usd and cost.monthly_limit_usd);
replace this with a proper currency formatter using Intl.NumberFormat (e.g.,
create a formatCurrency helper or computed method used where these values are
rendered) and pass the currency code (USD) so values are localized for number
formatting and currency symbol placement instead of string-concatenating "$".

In `@clients/web/apps/dashboard/src/components/config/HealthDashboard.vue`:
- Around line 77-90: The decorative status indicator spans (the element with
class "health-indicator" bound to overallStatus and the span with class
"component-indicator" inside the v-for over health.components) should be marked
aria-hidden="true" so screen readers ignore them; update the two spans (the one
using :class="overallStatus" and the one using :class="comp.status === 'ok' ?
'ok' : 'error'") to include aria-hidden="true" while keeping existing bindings
and attributes like :class and :data-testid intact.
- Around line 73-74: Update the two conditional paragraphs in
HealthDashboard.vue (the <p v-if="loading" class="helper">{{ t("health.loading")
}}</p> and the <p v-else-if="error" class="error">{{ error }}</p>) to include
appropriate ARIA announcements: add aria-live="polite" and role="status" for the
loading paragraph (loading variable / t("health.loading")), and add
aria-live="assertive" and role="alert" for the error paragraph (error variable),
ensuring the helper and error classes are preserved.
- Around line 64-67: The polling interval isn't reset when gatewayUrl or
bearerToken change; update the watcher around watch(() => [props.gatewayUrl,
props.bearerToken], fetchHealth, { immediate: true }) to also clear any existing
interval and recreate pollInterval after calling fetchHealth: keep a let
pollInterval variable in the outer scope, inside the watcher
clearInterval(pollInterval) if set, call fetchHealth (or rely on the immediate
option) and then assign pollInterval = setInterval(fetchHealth, 30_000); keep
the existing onUnmounted(() => clearInterval(pollInterval)) to clean up when the
component is destroyed.

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue`:
- Around line 50-51: HeartbeatOverview.vue currently renders the loading and
error <p> nodes without live-region semantics; update the loading paragraph (the
element with v-if="loading") to include aria-live="polite" and role="status" and
update the error paragraph (the element with v-else-if="error") to include
aria-live="assertive" and role="alert", keeping the existing class names (helper
and error) so styling remains the same and screen readers announce changes
consistently like CostOverview.vue.

In `@clients/web/apps/dashboard/src/components/config/McpOverview.vue`:
- Around line 19-44: The current fetchMcp function can have out-of-order
responses overwrite state when props.gatewayUrl or props.bearerToken change; fix
it by making requests cancellable and cancelling any prior request before
starting a new one: add a component-scoped AbortController (e.g.,
activeFetchController) or a monotonic request id (e.g., fetchCounter /
activeFetchId), abort/invalidate the previous controller/id at the start of
fetchMcp, create a new controller/id for this invocation, pass controller.signal
to the fetch call, and before assigning mcp.value, error.value, or loading.value
ensure the controller/id still matches the active one so stale responses cannot
overwrite the current state (reference fetchMcp, mcp, error, loading,
props.gatewayUrl, props.bearerToken).

In `@clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts`:
- Around line 84-108: The test for MemorySettings doesn't prove the token was
cleared because it starts with memory_cerebro_auth_token_mode "unchanged" and an
already-empty memory_cerebro_auth_token_value; update the test setup in
MemorySettings.spec.ts so the mounted props use createAdminConfigForm with
memory_cerebro_auth_token_mode: "replace" and a non-empty
memory_cerebro_auth_token_value (e.g., "secret") before setting the input to
"clear", then assert the emitted update for modelValue contains
memory_cerebro_auth_token_mode: "clear" and memory_cerebro_auth_token_value: ""
to verify the component actually cleared the token.

In `@clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue`:
- Around line 57-92: Replace hardcoded unit suffixes in ReliabilityOverview.vue
for the reliability fields (provider_backoff_ms, channel_initial_backoff_secs,
channel_max_backoff_secs, scheduler_poll_secs) with i18n translation keys
similar to HeartbeatOverview (e.g., use t("heartbeat.unit") or dedicated keys
like t("reliability.unit.ms") / t("reliability.unit.s") as appropriate). Update
the displayed strings for provider_backoff_ms, channel_initial_backoff_secs,
channel_max_backoff_secs, and scheduler_poll_secs to concatenate the localized
unit via t(...) instead of literal "ms" or "s", and add any new translation keys
used to the locales so the units render correctly in other languages.
- Around line 50-51: In ReliabilityOverview.vue update the status message <p>
elements so the loading paragraph (v-if="loading") has aria-live="polite" and
role="status" and the error paragraph (v-else-if="error") has
aria-live="assertive" and role="alert" to match the pattern used in
CostOverview.vue; locate the two <p> elements in the template (the ones using
v-if="loading" and v-else-if="error") and add these attributes to improve
accessibility consistency.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue`:
- Around line 71-74: The scheduler.task_count field in SchedulerStatus.vue
always shows 0 (scheduler.task_count) which is misleading; update the template
to conditionally render the status-item for task count (the span using
t("scheduler.taskCount") and scheduler.task_count) so that if
scheduler.task_count is null/0 it is either hidden or replaced with a static
"coming soon" / "not available" annotation/badge, ensuring the UI shows the real
value only when the backend provides it.
- Around line 55-58: The decorative status indicator span in SchedulerStatus.vue
(the element with class "status-indicator" and :class bound to
scheduler.enabled) should be hidden from assistive technology; add
aria-hidden="true" to that span so screen readers ignore it (consistent with
ChannelsOverview.vue) while keeping the existing class and :class binding
intact.
- Around line 50-51: Update the two conditional paragraphs in
SchedulerStatus.vue: add accessibility live region attributes to the loading and
error messages by setting aria-live="polite" (and role="status" if desired) on
the loading paragraph rendered by <p v-if="loading" ...> and
aria-live="assertive" (and role="alert" or role="status") on the error paragraph
rendered by <p v-else-if="error" ...>; keep existing Tailwind classes (e.g.,
"helper" and "error") and the translation call t("scheduler.loading")/error
binding intact so only ARIA attributes are added to the existing <p> elements.

In `@clients/web/apps/dashboard/src/components/config/TunnelOverview.vue`:
- Around line 50-52: In TunnelOverview.vue update the template conditionals to
improve A11y and show an explicit empty state: add aria-live="polite" (and
role="status" if appropriate) to the loading <p class="helper"> and
aria-live="assertive" to the error <p class="error"> so screen readers announce
changes, and replace the current v-else-if="tunnel" branch with explicit
branches that handle three cases—when loading, when error, when tunnel exists
(render .status-grid as before) and when the fetch succeeded but
data.config?.tunnel is null render an explicit empty-state <p> (e.g.,
class="helper" or a new empty-state class) with a translatable message—update
references to the component props/computed names (loading, error, tunnel or
data.config?.tunnel) and ensure Tailwind classes follow project guidelines.

In `@clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts`:
- Around line 42-43: The test "renders fallback values when updates data is
null" is inconsistent because the fixture uses an empty object (const config:
AdminConfigView = {}) rather than a true null updates value; either rename the
test to "missing updates data" to match the fixture or change the fixture to
include updates: null so the test actually covers the null API case—locate the
UpdateSettings.spec.ts test case (the it(...) block) and update the test name or
the config fixture accordingly to ensure the test intent and input match (use
the test title "missing updates data" if you keep {} or set config.updates =
null if you want to assert null handling).
- Around line 28-35: Tests duplicate the mount + i18n setup for UpdateSettings;
extract a shared helper (e.g., a function mountWithI18n or mountUpdateSettings)
that wraps mount(UpdateSettings, { props, global: { plugins: [createI18n({
...i18nConfig, locale: "en" })] } }) and accept the config/props to pass
through, then replace each direct mount call in UpdateSettings.spec.ts with the
helper to keep fixtures focused and reduce repetition.
- Around line 37-39: Replace the brittle wrapper.text() assertions in
UpdateSettings.spec.ts with targeted selector checks: instead of
expect(wrapper.text()).toContain("1.2.3") and
expect(wrapper.text()).toContain("1.3.0"), use
wrapper.find('[data-testid="current-version"]').text() and
wrapper.find('[data-testid="available-version"]').text() (or add those
data-testid attributes to the corresponding version elements in the component)
and assert their text equals/contains the expected versions; keep the existing
button assertion using wrapper.find('button[data-testid="save"]').exists()
as-is.

In `@clients/web/apps/dashboard/src/composables/configPayload.spec.ts`:
- Around line 483-500: Add a new unit test in the same spec that uses
buildPayloadForSection (and thus exercises buildSecurityPayload) to flip the two
new security booleans by passing a form with
autonomy_require_approval_for_medium_risk and autonomy_block_high_risk_commands
set to truthy values (e.g., "on"/"true") along with the existing
auto_approve/always_ask values; assert the returned payload includes
autonomy.require_approval_for_medium_risk and autonomy.block_high_risk_commands
booleans set appropriately (true/false) so the test fails if
buildSecurityPayload does not map those form fields into the payload.

---

Outside diff comments:
In `@clients/web/packages/shared/index.ts`:
- Around line 49-51: The try block in resolvePublicUrl still uses a regex-based
trailing-slash removal (.replace(/\/$/, "")) which is inconsistent with and
defeats the purpose of the new trimTrailingSlashes helper; update
resolvePublicUrl to call trimTrailingSlashes on the URL string returned by new
URL(candidate).toString() (and ensure trimTrailingSlashes is imported/visible to
that module) so all trailing-slash trimming uses the new helper instead of a
regex.

---

Duplicate comments:
In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue`:
- Around line 35-36: The current response handling in ChannelsOverview.vue
assigns channels.value from a branch that never occurs; remove the
Array.isArray(data) branch and instead validate the payload shape returned by
the backend: after const data = await res.json(), check that data is an object
and that Array.isArray(data.channels) is true, then set channels.value =
data.channels; otherwise set channels.value = [] (and optionally log or surface
an error). Update the assignment logic around channels.value to only accept the
expected { channels: [...] } shape.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`:
- Around line 110-134: The test in ComposioSettings.spec.ts doesn't seed a
non-empty composio_api_key_value so the "unchanged -> clear" behavior can pass
even if clearing regresses; update the test that mounts ComposioSettings (using
createAdminConfigForm) to initialize modelValue with composio_api_key_mode:
"unchanged" AND a non-empty composio_api_key_value (e.g., "secret"), then change
the select to "clear" and assert the emitted update.modelValue contains
composio_api_key_mode: "clear" and composio_api_key_value: "" to prove the clear
transition works.

In `@clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts`:
- Around line 103-128: The test currently covers the "replace -> clear" branch
but misses the "replace -> unchanged" branch: update the test suite in
WebSearchSettings.spec.ts (the existing it block or by adding a new it) to also
simulate setting the select with data-testid "web_search_brave_api_key_mode" to
"unchanged" and assert that the emitted update:modelValue contains
web_search_brave_api_key_mode: "unchanged" and web_search_brave_api_key_value:
"" (since updateSecretMode() clears non-"replace" values); reference
updateSecretMode() and the select with data-testid
"web_search_brave_api_key_mode" when locating where to add the assertion.

In `@clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue`:
- Around line 24-45: The updateField function currently emits raw non-finite
numeric strings (e.g., partial/exponent input) for keys "web_search_max_results"
and "web_search_timeout_secs"; change the logic in updateField so that when
Number(value) is not finite you do NOT emit the raw malformed string—either
return early (no emit) or set clamped to a safe normalized value such as an
empty string ('' as AdminConfigForm[Key]) before emitting; update the branches
that handle "web_search_max_results" and "web_search_timeout_secs" accordingly
so malformed inputs are filtered here instead of relying on
buildWebSearchPayload to reparse them.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 652937b3-1e16-478b-b700-434320a1f73f

📥 Commits

Reviewing files that changed from the base of the PR and between c8a3ee7 and 3cc8907.

📒 Files selected for processing (26)
  • clients/agent-runtime/src/channels/media.rs
  • clients/agent-runtime/src/providers/router.rs
  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/chat/src/components/HealthIndicator.spec.ts
  • clients/web/apps/chat/src/components/HealthIndicator.vue
  • clients/web/apps/chat/src/composables/useChat.spec.ts
  • clients/web/apps/chat/src/composables/useChat.ts
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/packages/shared/index.ts
  • openspec/specs/provider-vision-gating/spec.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: sonar
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (8)
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/packages/shared/index.ts
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/chat/src/components/HealthIndicator.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/chat/src/components/HealthIndicator.spec.ts
  • clients/agent-runtime/src/providers/router.rs
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/chat/src/composables/useChat.spec.ts
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/agent-runtime/src/channels/media.rs
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/chat/src/composables/useChat.ts
  • openspec/specs/provider-vision-gating/spec.md
**/*.vue

⚙️ CodeRabbit configuration file

**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.

Files:

  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/chat/src/components/HealthIndicator.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
clients/agent-runtime/src/providers/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Implement Provider trait in src/providers/ and register in src/providers/mod.rs factory when adding a new provider

Files:

  • clients/agent-runtime/src/providers/router.rs
clients/agent-runtime/src/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency

Files:

  • clients/agent-runtime/src/providers/router.rs
  • clients/agent-runtime/src/channels/media.rs
clients/agent-runtime/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Run cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, and cargo test for code validation, or document which checks were skipped and why

Files:

  • clients/agent-runtime/src/providers/router.rs
  • clients/agent-runtime/src/channels/media.rs
**/*.rs

⚙️ CodeRabbit configuration file

**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.

Files:

  • clients/agent-runtime/src/providers/router.rs
  • clients/agent-runtime/src/channels/media.rs
clients/agent-runtime/src/channels/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests

Files:

  • clients/agent-runtime/src/channels/media.rs
**/*.{md,mdx}

⚙️ CodeRabbit configuration file

**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes.
For user-facing docs, check EN/ES parity or explicitly note pending translation gaps.

Files:

  • openspec/specs/provider-vision-gating/spec.md
🧠 Learnings (5)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/providers/**/*.rs : Implement `Provider` trait in `src/providers/` and register in `src/providers/mod.rs` factory when adding a new provider

Applied to files:

  • clients/agent-runtime/src/providers/router.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths

Applied to files:

  • clients/agent-runtime/src/providers/router.rs
  • clients/agent-runtime/src/channels/media.rs
  • openspec/specs/provider-vision-gating/spec.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/web/apps/chat/src/composables/useChat.spec.ts
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable

Applied to files:

  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks

Applied to files:

  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
🪛 LanguageTool
openspec/specs/provider-vision-gating/spec.md

[grammar] ~130-~130: Use a hyphen to join words.
Context: ...enario: Trait default rejects image turn on provider without an override - GIVEN...

(QB_NEW_EN_HYPHEN)

🔇 Additional comments (19)
clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts (1)

81-106: Security-relevant test correctly added.

This test verifies that switching the API key mode from "replace" to "clear" properly zeroes out browser_computer_use_api_key_value. This addresses the security-relevant secret-clearing behavior previously flagged as untested.

clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts (2)

1-47: Well-structured test setup and emission checks.

Good use of data-testid selectors, proper async handling, and the factory pattern for form creation. The emission assertions correctly verify both field preservation and update behavior.


49-82: Auth token visibility tests cover the security-relevant conditional rendering.

Testing both "replace" (shows input) and "clear" (hides input) modes ensures sensitive token fields aren't exposed when not needed.

clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts (1)

79-134: Good boolean parity coverage for update availability.

Nice addition validating both update_available: true → “Yes” and false → “No”, plus auto_install_enabled rendering in the false-path.

clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts (1)

83-108: Good fix: non-empty secret is now explicitly cleared on replace -> clear.

This test now proves the clearing path with a real non-empty starting value and guards against secret-retention regressions.

clients/agent-runtime/src/providers/router.rs (3)

156-162: Fail-closed image routing is now correctly enforced.

Line 155 guards image turns before dispatch, and Line 161 returns RouteNotImageCapable as a structured rejection.


207-228: Mock call accounting is now precise for router gating tests.

Tracking chat_calls separately and incrementing in Line 255 makes the “provider chat() not invoked” contract verifiable.

Also applies to: 249-263


539-544: Regression test now validates both error type and zero-dispatch behavior.

The downcast assertion plus Line 543 call-count check gives strong coverage for router pre-dispatch rejection.

clients/agent-runtime/src/channels/media.rs (1)

46-67: Image rejection taxonomy stays explicit and machine-friendly.

The variant-level canonical codes remain clear and deterministic for routing/channel error handling.

openspec/specs/provider-vision-gating/spec.md (2)

119-129: Fail-closed routing requirements are clear and testable.

The router/reliable-layer constraints and REQ-5 scenarios define pre-dispatch rejection and explicitly forbid silent image stripping.

Also applies to: 213-234


8-10: Translation parity status is now explicitly documented.

This clearly records pending EN/ES scope for the new spec.

As per coding guidelines, "**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes. For user-facing docs, check EN/ES parity or explicitly note pending translation gaps."

clients/web/packages/shared/index.ts (2)

10-17: LGTM: Safe loop-based trailing slash removal.

The implementation correctly avoids ReDoS (S5852) by using end-position scanning instead of regex.


19-40: Solid URL validation with protocol enforcement.

The function properly:

  • Rejects empty/whitespace input
  • Catches malformed URLs
  • Enforces http/https protocols
  • Sanitizes by clearing search/hash and trimming pathname slashes
clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue (1)

1-45: Well-structured component with proper URL validation and reactive fetching.

Uses validateGatewayUrl for security, watches props for reactivity, and handles all UI states (loading/error/null/data).

clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

1-45: Component follows the established pattern with proper reactivity and accessibility.

Good use of validateGatewayUrl, reactive watch, and ARIA attributes on loading/error states.

clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue (1)

1-45: Solid implementation following the established admin overview pattern.

Proper URL validation, reactive prop watching, and comprehensive reliability metrics display.

clients/web/apps/dashboard/src/components/config/TunnelOverview.vue (1)

1-45: Component properly validates URLs and handles optional tunnel fields.

Good use of conditional rendering for optional Tailscale/ngrok fields.

clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue (1)

1-45: Component follows the standard pattern with proper URL validation and reactive fetching.

clients/web/apps/dashboard/src/components/config/HealthDashboard.vue (1)

1-67: Well-implemented polling health dashboard with proper cleanup.

Good use of onUnmounted to clear the interval, reactive prop watching, and computed overallStatus.

Comment thread clients/web/apps/chat/src/App.vue Outdated
Comment thread clients/web/apps/chat/src/composables/useChat.ts
Comment thread clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue Outdated
Comment thread clients/web/apps/dashboard/src/components/config/TunnelOverview.vue Outdated
Comment thread clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/composables/configPayload.spec.ts
Address second round of code review findings:

Chat app:
- Fix stream handler to accumulate chunks instead of overwriting
- Broaden 403 handler to accept all approval-shaped errors
- Add stream chunk accumulation test

Dashboard components:
- Add ARIA live regions to all overview loading/error states
- Add aria-hidden to all decorative status indicator spans
- Reset HealthDashboard polling interval on prop changes
- Add AbortController to McpOverview for stale request cancellation
- Replace hardcoded currency/unit strings with Intl/i18n
- Show 'Not available' for zero task count in SchedulerStatus
- Add explicit empty state in TunnelOverview
- Reject non-finite numeric inputs in WebSearchSettings
- Remove Array.isArray fallback in ChannelsOverview response

Tests:
- Strengthen secret clearing tests with non-empty initial values
- Add replace→unchanged mode transition tests
- Add security booleans payload test
- Extract mountUpdateSettings helper, use data-testid selectors
- Remove unnecessary async from sync test declarations

Shared:
- Replace regex in resolvePublicUrl with trimTrailingSlashes
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

♻️ Duplicate comments (6)
clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts (1)

41-57: 🧹 Nitpick | 🔵 Trivial

Add auth-header assertion to the success test for consistency.

The refetch test (lines 87-91) correctly verifies the Authorization header, but the initial success test doesn't. For complete auth contract coverage:

Suggested addition
     const wrapper = mountComponent();
     await flushPromises();
 
+    const fetchMock = vi.mocked(fetch);
+    expect(fetchMock).toHaveBeenCalledWith(
+      expect.stringContaining("/web/admin/config"),
+      expect.objectContaining({ headers: { Authorization: "Bearer test-token" } })
+    );
+
     expect(wrapper.find('[data-testid="mcp-server-test-server"]').exists()).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts` around
lines 41 - 57, The success test uses vi.stubGlobal("fetch", ...) and mounts the
component via mountComponent() but doesn't assert the Authorization header; add
an assertion after flushPromises() that the stubbed fetch was called with an
Authorization header (matching the token used in the test setup) — e.g., inspect
vi.fn() call args from the stub created in vi.stubGlobal("fetch") to verify the
request headers include Authorization — so that the initial success test mirrors
the refetch test's header check and ensures auth contract coverage for the
component that uses mockConfig and the '[data-testid="mcp-server-test-server"]'
rendering.
clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue (1)

35-36: ⚠️ Potential issue | 🟠 Major

Validate the /web/admin/channels payload before assigning it.

res.json() is untyped here, so data.channels ?? [] will accept any truthy channels value. If that shape drifts, the template will iterate non-AdminChannelStatusView entries and then dereference ch.channel_type / ch.configured at runtime. Parse as unknown and require { channels: AdminChannelStatusView[] } before updating channels.value.

As per coding guidelines, "Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue` around
lines 35 - 36, Parse the fetch response as unknown and validate its shape before
assigning to channels.value: after calling res.json(), assert the result matches
the expected type { channels: AdminChannelStatusView[] } (e.g., check it's an
object, has a channels array, and each item contains the expected properties
like channel_type and configured) and only then set channels.value =
parsed.channels; otherwise leave channels.value as [] and log or handle the
invalid payload. Ensure you reference the existing symbols res.json(),
channels.value, and the AdminChannelStatusView shape when adding the validation.
clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts (1)

137-154: ⚠️ Potential issue | 🟡 Minor

Prove the secret is absent from the rendered output, not just the input field.

This still passes if composio_api_key_value is accidentally echoed somewhere else in unchanged mode. Keep the seeded secret and add a negative text/HTML assertion so the test actually guards against leakage.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`
around lines 137 - 154, The test "hides password input when mode is unchanged
even if secret value is present" in ComposioSettings should also assert the
secret isn't rendered anywhere in the component output; keep the seeded secret
in the props (composio_api_key_value: "my-secret") and add a negative assertion
against the rendered HTML/text (e.g., assert wrapper.html() or wrapper.text()
does not contain "my-secret") in addition to the existing check that the input
with data-testid="composio_api_key_value" does not exist, so the test fails if
the secret is echoed elsewhere.
clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts (1)

21-50: ⚠️ Potential issue | 🟡 Minor

Assert the authenticated admin-config request in the success test.

The test currently passes even if the component stops sending the bearer token or hits the wrong route. Add a fetch assertion for /web/admin/config plus the Authorization header.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts` around
lines 21 - 50, The test "renders cost data on successful fetch" stubs global
fetch but doesn't assert the request URL or Authorization header; update this
test to assert that fetch was called with the '/web/admin/config' endpoint and
that the options include an Authorization header with a Bearer token (use the
same auth setup the component uses), e.g., after flushPromises add an assertion
that vi's stubbed fetch was invoked for '/web/admin/config' and that the passed
options object contains headers.Authorization starting with 'Bearer '; reference
the test name and mountComponent/mocked fetch to locate where to add the
assertion and keep vi.unstubAllGlobals at the end.
clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts (1)

21-49: ⚠️ Potential issue | 🟡 Minor

Assert the endpoint and bearer header in the success-path test.

This only proves rendering. It will not catch regressions that hit the wrong admin route or drop auth entirely. Add a fetch assertion for /web/admin/channels plus Authorization: Bearer test-token.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts`
around lines 21 - 49, The test "renders channel list on successful fetch"
currently stubs fetch but doesn't assert the request details; after mounting via
mountComponent() and awaiting flushPromises(), add assertions that the global
fetch (stubbed via vi.stubGlobal("fetch", ...)) was called with the admin
endpoint "/web/admin/channels" and that the request included an Authorization
header "Bearer test-token" (e.g., inspect the first call to the stubbed fetch
and assert request url and headers), then keep vi.unstubAllGlobals() as cleanup.
clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

49-50: 🧹 Nitpick | 🔵 Trivial

Consider surfacing contract errors when config.cost is missing.

The current fallback data?.config?.cost ?? null silently renders nothing when the backend returns a malformed or incomplete response. For operator-facing dashboards, explicitly setting error.value when config.cost is absent helps diagnose backend issues faster.

     const data = await res.json();
-    cost.value = data?.config?.cost ?? null;
+    if (!data?.config?.cost) {
+      throw new Error("Invalid response: missing config.cost");
+    }
+    cost.value = data.config.cost;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue` around
lines 49 - 50, The code silently falls back to null when config.cost is missing;
update the fetch handling (after const data = await res.json()) to detect
missing or malformed data and set error.value with a clear message instead of
just assigning cost.value to null. Specifically, check data and data.config and
data.config.cost; if any are absent, assign a descriptive string to error.value
(including any useful context such as the raw data or HTTP status) and only set
cost.value when data.config.cost is present; reference the existing cost.value
and error.value reactive refs to implement this behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/web/apps/chat/src/App.spec.ts`:
- Around line 140-157: Add assertions to verify Authorization and X-Session-Id
are present on the streaming request (streamInit) just like you do for the
webhook request (webhookInit): after extracting const [, streamInit] =
fetchMock.mock.calls[4] ?? []; add expectations that (streamInit?.headers as
Record<string,string>).Authorization equals "Bearer zc_test_token" and that
(streamInit?.headers as Record<string,string>)["X-Session-Id"] equals
"11111111-1111-4111-8111-111111111111", while keeping the existing check for
(streamInit?.headers as Record<string,string>)["X-Request-Id"] to avoid
regressions.

In `@clients/web/apps/chat/src/App.vue`:
- Around line 222-237: The persistMessages function is not serializing
message.status so completion state is lost; update the serialization in
persistMessages (the mapping over messages.value used before
sessionStorage.setItem(messagesStorageKey(), ...)) to include m.status (and
ensure any deserialization code that reads sessionStorage via
messagesStorageKey() restores the status field into the message objects) so
messages retain their status after refresh.

In `@clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts`:
- Around line 108-123: The test "hides password input when computer use api key
mode is unchanged" only asserts the input is hidden but doesn't ensure an
existing secret is not leaked; update the test for BrowserSettings to mount with
modelValue including browser_has_computer_use_api_key: true and a non-empty
browser_computer_use_api_key_value, then assert that the rendered output neither
shows the input nor contains the secret string (e.g., by checking wrapper.text()
or that specific selector does not contain the secret); keep the test name and
use the same mount setup and i18n plugin while adding these extra assertions to
cover the no-secret-leak case.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`:
- Around line 83-135: Two tests in ComposioSettings.spec.ts ("clears non-empty
api key value when mode changes from replace to clear" and "clears api key value
when mode changes to clear") are duplicates; remove one of them (delete either
test block) or change the second to exercise a different path (e.g., switch from
"keep" to "clear" or "replace" to "keep") so it no longer repeats the same
transition. Update or remove the duplicate test that mounts ComposioSettings
with createAdminConfigForm(composio_api_key_mode: "replace") and
composio_api_key_value and that calls setValue("clear") on
'[data-testid="composio_api_key_mode"]' to avoid redundant assertions.

In `@clients/web/apps/dashboard/src/components/config/HealthDashboard.vue`:
- Around line 39-50: fetchHealth currently starts overlapping fetches and always
sets loading.value = true; modify fetchHealth to accept/use a shared
AbortController (create a new controller before starting a fetch and abort the
previous controller) so previous requests are cancelled when watchers or the
interval restart, and wire the controller cleanup into component unmount; only
set loading.value = true if there is no snapshot yet (i.e., when snapshot.value
is null/undefined) so refreshes don't re-show the full loading card, and ensure
you pass controller.signal into fetch; reuse the AbortController pattern from
McpOverview.vue and reference the fetchHealth function, loading/snapshot refs,
and the shared controller variable when implementing this.
- Around line 55-56: Validate the parsed response before assigning to
health.value: after const data = await res.json(), check that data.health is an
object and matches the minimal AdminHealthSnapshot shape (e.g. typeof
data.health.uptime_seconds === 'number' and
Array.isArray(data.health.components')), and only then set health.value =
data.health; otherwise set health.value = null and surface an error/empty state
(or log/report the mismatch) so callers of overallStatus and formatUptime won’t
receive malformed objects. Ensure this validation is applied where health.value
is assigned and references the same symbols (data.health, health.value,
AdminHealthSnapshot, overallStatus, formatUptime).

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue`:
- Around line 19-44: fetchHeartbeat can receive out-of-order responses; create
an AbortController per invocation and keep the current controller in an
outer-scoped variable so you can abort the previous request before starting a
new one, pass controller.signal to fetch, and treat aborts specially (do not set
error on AbortError). Ensure the previous controller is aborted at the start of
fetchHeartbeat, attach the new controller to that outer variable, and also abort
it in a component teardown (e.g., onBeforeUnmount) so in-flight requests are
cancelled when props change or the component unmounts; update error handling in
fetchHeartbeat to ignore AbortError while preserving other errors.

In `@clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue`:
- Around line 19-44: Add request cancellation to fetchReliability using
AbortController so stale fetches can't overwrite current state: create a new
AbortController for each invocation, pass controller.signal into fetch(...) when
calling requestUrl, and ensure previous requests are aborted—implement this by
converting the watcher to an inline async callback that receives onInvalidate
(watch(() => [props.gatewayUrl, props.bearerToken], async (_new, _old,
onInvalidate) => { ... })) or have fetchReliability accept an AbortSignal; call
onInvalidate(() => controller.abort()) so the prior controller is aborted on
rapid prop changes, and in the catch block ignore or handle AbortError specially
(do not set error.value when the fetch was aborted) while preserving current
assignments to reliability.value, loading.value, and error.value.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue`:
- Around line 19-44: The fetchSchedulerStatus flow needs request cancellation
like the other overview components: create an AbortController for each
invocation of fetchSchedulerStatus, pass controller.signal to fetch, and ensure
previous requests are aborted to avoid stale responses overwriting state; in the
watcher (watch(() => [props.gatewayUrl, props.bearerToken],
fetchSchedulerStatus, { immediate: true })) use the watch
invalidate/onInvalidate callback to call controller.abort() for the current
call, and also add an onBeforeUnmount cleanup to abort any remaining controller;
when handling errors, ignore AbortError (do not set error.value for aborts) and
only set error.value for real failures while still updating scheduler.value,
loading.value as before. Reference functions/identifiers: fetchSchedulerStatus,
watch(() => [props.gatewayUrl, props.bearerToken], ...), scheduler.value,
loading.value, error.value, and use AbortController and onBeforeUnmount.
- Around line 72-75: The UI currently treats scheduler.task_count === 0 as "not
available", conflating zero tasks with unavailable; update the rendering in
SchedulerStatus.vue to treat null/undefined as "not available" and display
numeric values (including 0) as-is by checking scheduler.task_count == null (or
typeof check) before showing t("scheduler.notAvailable"), and coordinate with
the backend to return null when the runtime enumeration is unsupported and 0
when there are genuinely zero tasks so the component (using scheduler.task_count
and t("scheduler.notAvailable")) can correctly distinguish the two cases.

In `@clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts`:
- Around line 86-106: Add a symmetric unit test to cover the false case for
auto_install_enabled: using the existing mount helper (mountUpdateSettings) add
a new it block (e.g., "renders no for auto_install_enabled when false") that
passes updates.auto_install_enabled: false (with other required fields like
enabled, restart_policy, status populated similarly) and assert that
wrapper.get('[data-testid="updates_auto_install_enabled"]').text() contains
"No"; mirror the structure of the existing positive test so it integrates with
UpdateSettings.spec.ts and uses the same mountUpdateSettings helper.

---

Duplicate comments:
In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts`:
- Around line 21-49: The test "renders channel list on successful fetch"
currently stubs fetch but doesn't assert the request details; after mounting via
mountComponent() and awaiting flushPromises(), add assertions that the global
fetch (stubbed via vi.stubGlobal("fetch", ...)) was called with the admin
endpoint "/web/admin/channels" and that the request included an Authorization
header "Bearer test-token" (e.g., inspect the first call to the stubbed fetch
and assert request url and headers), then keep vi.unstubAllGlobals() as cleanup.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue`:
- Around line 35-36: Parse the fetch response as unknown and validate its shape
before assigning to channels.value: after calling res.json(), assert the result
matches the expected type { channels: AdminChannelStatusView[] } (e.g., check
it's an object, has a channels array, and each item contains the expected
properties like channel_type and configured) and only then set channels.value =
parsed.channels; otherwise leave channels.value as [] and log or handle the
invalid payload. Ensure you reference the existing symbols res.json(),
channels.value, and the AdminChannelStatusView shape when adding the validation.

In `@clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts`:
- Around line 137-154: The test "hides password input when mode is unchanged
even if secret value is present" in ComposioSettings should also assert the
secret isn't rendered anywhere in the component output; keep the seeded secret
in the props (composio_api_key_value: "my-secret") and add a negative assertion
against the rendered HTML/text (e.g., assert wrapper.html() or wrapper.text()
does not contain "my-secret") in addition to the existing check that the input
with data-testid="composio_api_key_value" does not exist, so the test fails if
the secret is echoed elsewhere.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts`:
- Around line 21-50: The test "renders cost data on successful fetch" stubs
global fetch but doesn't assert the request URL or Authorization header; update
this test to assert that fetch was called with the '/web/admin/config' endpoint
and that the options include an Authorization header with a Bearer token (use
the same auth setup the component uses), e.g., after flushPromises add an
assertion that vi's stubbed fetch was invoked for '/web/admin/config' and that
the passed options object contains headers.Authorization starting with 'Bearer
'; reference the test name and mountComponent/mocked fetch to locate where to
add the assertion and keep vi.unstubAllGlobals at the end.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue`:
- Around line 49-50: The code silently falls back to null when config.cost is
missing; update the fetch handling (after const data = await res.json()) to
detect missing or malformed data and set error.value with a clear message
instead of just assigning cost.value to null. Specifically, check data and
data.config and data.config.cost; if any are absent, assign a descriptive string
to error.value (including any useful context such as the raw data or HTTP
status) and only set cost.value when data.config.cost is present; reference the
existing cost.value and error.value reactive refs to implement this behavior.

In `@clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts`:
- Around line 41-57: The success test uses vi.stubGlobal("fetch", ...) and
mounts the component via mountComponent() but doesn't assert the Authorization
header; add an assertion after flushPromises() that the stubbed fetch was called
with an Authorization header (matching the token used in the test setup) — e.g.,
inspect vi.fn() call args from the stub created in vi.stubGlobal("fetch") to
verify the request headers include Authorization — so that the initial success
test mirrors the refetch test's header check and ensures auth contract coverage
for the component that uses mockConfig and the
'[data-testid="mcp-server-test-server"]' rendering.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c427c374-a7c7-4f78-897a-529d892b386e

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc8907 and 6e213b2.

📒 Files selected for processing (25)
  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/chat/src/composables/useChat.ts
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/packages/locales/src/en.json
  • clients/web/packages/locales/src/es.json
  • clients/web/packages/shared/index.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: pr-checks
  • GitHub Check: sonar
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/packages/shared/index.ts
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts
  • clients/web/packages/locales/src/es.json
  • clients/web/packages/locales/src/en.json
  • clients/web/apps/chat/src/composables/useChat.ts
  • clients/web/apps/chat/src/App.vue
**/*.vue

⚙️ CodeRabbit configuration file

**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.

Files:

  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue
  • clients/web/apps/chat/src/App.vue
🧠 Learnings (4)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Include threat/risk notes and rollback strategy for security, runtime, and gateway changes; add or update tests for boundary checks and failure modes

Applied to files:

  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable

Applied to files:

  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks

Applied to files:

  • clients/web/apps/dashboard/src/composables/configPayload.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
🔇 Additional comments (30)
clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts (2)

1-16: LGTM: Helper function reduces test duplication.

The mountUpdateSettings helper cleanly addresses the past review feedback by extracting the repeated mount + i18n setup. Properly typed and reusable.


19-84: LGTM: Core test scenarios are well covered.

Tests 1-4 address past review feedback: targeted selectors prevent false positives, test naming matches fixtures, and the symmetric update_available: true case is now verified.

clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts (2)

21-47: Tests should verify Authorization header propagation.

The success-path test stubs fetch but doesn't assert that the bearer token is actually sent. Add an assertion to verify auth contract:

const fetchMock = vi.fn().mockResolvedValue({...});
vi.stubGlobal("fetch", fetchMock);
// ... mount and flush ...
expect(fetchMock).toHaveBeenCalledWith(
  expect.stringContaining("/web/admin/scheduler"),
  expect.objectContaining({ headers: { Authorization: "Bearer test-token" } })
);

49-84: LGTM!

Error and disabled state tests properly verify rendering behavior and clean up global stubs.

clients/web/packages/shared/index.ts (2)

10-40: Well-designed security-conscious URL utilities.

The non-regex trimTrailingSlashes avoids ReDoS (S5852), and validateGatewayUrl properly enforces HTTP(S) protocol, strips trailing slashes from pathname, and clears search/hash to prevent credential leakage via query params. Good defensive design.


49-53: LGTM!

Correctly calls toString() on the URL object before passing to trimTrailingSlashes, matching the expected usage pattern.

clients/web/packages/locales/src/es.json (1)

18-389: LGTM!

Comprehensive Spanish translations for all new dashboard operational sections. Keys align with component usage, including localized units for time values (reliability.unit.ms, reliability.unit.s, heartbeat.unit).

clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue (1)

47-71: LGTM!

Template properly handles all states with appropriate ARIA attributes, localized unit display via t("heartbeat.unit"), and explicit empty-state when heartbeat is null.

clients/web/apps/dashboard/src/components/config/McpOverview.vue (2)

1-65: LGTM!

Solid implementation with AbortController for request cancellation, proper cleanup on unmount, and defensive Array.isArray() guards for server/capabilities arrays. URL validation and bearer auth handling are correct.


67-113: LGTM!

Template properly renders loading/error states with ARIA attributes, includes defensive array guards, and uses aria-hidden="true" on decorative status indicators.

clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts (1)

72-116: LGTM!

Refetch and invalid URL tests provide good coverage of reactive behavior and URL validation error handling.

clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue (1)

47-95: LGTM!

Template correctly renders model fallbacks as primary → fallback, uses localized units (t("reliability.unit.ms/s")), and includes proper ARIA attributes on loading/error states.

clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue (1)

47-78: LGTM!

Template properly handles all states with ARIA attributes, decorative indicator hidden from screen readers, and localized strings.

clients/web/apps/chat/src/App.vue (3)

254-282: Stub implementations noted for Phase 5B.

handleApprove and handleReject update local UI state only. The comment indicates this is intentional. Ensure follow-up work is tracked.


151-164: Stream chunk accumulation now correct.

Using streamBuffer to concatenate chunks before updating the assistant message fixes the previously flagged critical issue where only the last chunk was displayed.


284-293: Debounced persistence addresses hot-path performance.

The 300ms debounce prevents synchronous sessionStorage.setItem on every streamed chunk, resolving the previously flagged performance concern.

clients/web/apps/dashboard/src/composables/configPayload.spec.ts (2)

444-481: Good coverage for empty-secret validation across all secret-bearing sections.

The previously requested tests for web-search, composio, and memory empty-secret errors are now in place. This locks down the contract for buildSecretUpdate across all sections.


483-519: Autonomy risk toggle coverage added.

Tests now flip autonomy_require_approval_for_medium_risk and autonomy_block_high_risk_commands, plus validate comma-separated list parsing for auto_approve/always_ask. This addresses the previously flagged gap.

clients/web/apps/dashboard/src/components/config/MemorySettings.spec.ts (1)

84-109: Test now properly validates token clearing.

Starting with memory_cerebro_auth_token_mode: "replace" and a non-empty "secret" value before switching to "clear" correctly proves the component clears the token. This addresses the previous feedback.

clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

61-95: Template handles loading, error, and data states correctly with proper ARIA semantics.

The aria-live="polite" on loading and role="alert" aria-live="assertive" on error ensure screen readers announce state changes. The status grid renders cleanly when cost is populated.

clients/web/apps/dashboard/src/components/config/WebSearchSettings.vue (2)

54-63: Secret mode validation guards against arbitrary values.

The whitelist check before calling updateSecretMode prevents injection of unexpected mode values. This addresses the past security concern.


28-38: Numeric clamping enforces bounds programmatically.

max_results is clamped to 1-10 and timeout_secs is clamped to ≥1, preventing out-of-bounds values from reaching the model regardless of HTML attribute bypass.

clients/web/apps/dashboard/src/components/config/WebSearchSettings.spec.ts (2)

103-155: Secret clearing regression tests now in place.

Both replace → clear and replace → unchanged transitions are tested, asserting that web_search_brave_api_key_value is cleared to "". This addresses the previously flagged gap.


271-288: Invalid mode rejection test prevents silent acceptance of arbitrary values.

The test confirms no update:modelValue is emitted when an invalid mode like "bogus" is set, validating the whitelist guard in the component.

clients/web/packages/locales/src/en.json (2)

18-31: Section labels extended consistently.

New navigation sections (webSearch, browser, composio, memory, etc.) follow the existing naming pattern.


281-388: New feature namespaces added with complete coverage.

Each new admin feature (webSearch, browser, composio, memory, scheduler, channels, cost, mcp, tunnel, reliability, heartbeat, updates) has its required label and status strings.

clients/web/apps/chat/src/composables/useChat.ts (4)

81-105: Approval error parsing handles multiple payload shapes.

parseApprovalErrorType correctly extracts error codes from error.code, error.type, top-level type, and string error fields, covering the various shapes the backend might return.


321-339: 403 handling now distinguishes approval from credential errors.

Using parseApprovalErrorType to check for approval_required or approval_contract before calling markCredentialInvalid fixes the previously flagged issue where approvals were misclassified.


435-506: SSE parsing correctly persists frame state across reads.

buffer, currentEvent, currentData, and doneEvent are declared outside the read loop. CRLF normalization at lines 500 and 505 handles mixed line endings. The final decoder.decode() flush at line 504 ensures trailing data is processed.


460-478: JSON.parse calls wrapped in try-catch.

Both done and error event parsing now handle malformed JSON gracefully, throwing a controlled error message instead of crashing the stream.

Comment thread clients/web/apps/chat/src/App.spec.ts Outdated
Comment thread clients/web/apps/chat/src/App.vue
Comment thread clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/HealthDashboard.vue Outdated
Comment thread clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue Outdated
Comment thread clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue Outdated
Comment thread clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue Outdated
Third round of review fixes:

Chat:
- Assert auth headers on stream request in App.spec.ts
- Serialize message.status in persistMessages for session restore

Dashboard:
- Add AbortController to HealthDashboard, HeartbeatOverview,
  ReliabilityOverview, SchedulerStatus for stale request cancellation
- Only show loading state on first fetch in HealthDashboard
- Validate response shape in HealthDashboard and ChannelsOverview
- Error on missing cost data in CostOverview instead of silent null
- Distinguish null vs 0 task_count in SchedulerStatus

Tests:
- Assert no secret leak in BrowserSettings unchanged mode
- Remove duplicate ComposioSettings clear test, add no-leak assertion
- Assert fetch URL and Authorization headers in Channels, Cost, MCP
- Add auto_install_enabled false test for UpdateSettings
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

♻️ Duplicate comments (4)
clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue (1)

93-93: ⚠️ Potential issue | 🟠 Major

task_count null fallback is out of sync with the current API contract.

clients/web/apps/dashboard/src/types/admin-config.ts defines task_count as number, and clients/agent-runtime/src/gateway/admin.rs currently emits usize (0 placeholder). With this contract, the null fallback here never triggers in production.

Please align contract end-to-end: either make backend/type nullable when unavailable, or remove the null-specific UI/test path.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management. Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue` at line
93, The UI null-check for scheduler.task_count is inconsistent with the API/Type
contract—task_count is defined as number in
clients/web/apps/dashboard/src/types/admin-config.ts and the backend emits a
usize—so remove the null-specific branch in SchedulerStatus.vue (replace the
ternary that checks scheduler.task_count == null) and render the numeric value
or a deterministic default (e.g., 0) instead; ensure corresponding tests and any
UI copy expecting "notAvailable" are updated, and if you prefer to make the
backend/type nullable instead, update clients/agent-runtime/src/gateway/admin.rs
to emit an optional value and change admin-config.ts to number | null so the
existing Vue fallback becomes meaningful (pick one approach and apply
consistently across SchedulerStatus.vue, admin-config.ts, and admin.rs).
clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts (1)

21-47: ⚠️ Potential issue | 🟠 Major

Assert the authenticated request contract in the success-path test.

This test verifies rendering but does not assert that fetch was called with /web/admin/scheduler and the Bearer header, so auth regressions can slip through undetected.

Suggested patch
 it("renders scheduler status on successful fetch", async () => {
+  const token = "test-token";
   const mockScheduler = {
     enabled: true,
     max_tasks: 64,
     max_concurrent: 4,
     task_count: 0,
   };

-  vi.stubGlobal(
+  const fetchMock = vi.fn().mockResolvedValue({
+    ok: true,
+    json: () => Promise.resolve({ scheduler: mockScheduler }),
+  });
+  vi.stubGlobal(
     "fetch",
-    vi.fn().mockResolvedValue({
-      ok: true,
-      json: () => Promise.resolve({ scheduler: mockScheduler }),
-    })
+    fetchMock
   );

   const wrapper = mountComponent();
   await flushPromises();

+  expect(fetchMock).toHaveBeenCalledWith(
+    expect.stringContaining("/web/admin/scheduler"),
+    expect.objectContaining({
+      headers: expect.objectContaining({
+        Authorization: `Bearer ${token}`,
+      }),
+    })
+  );
   expect(wrapper.find('[data-testid="scheduler-status"]').exists()).toBe(true);

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management. Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts`
around lines 21 - 47, The test "renders scheduler status on successful fetch"
stubs global fetch but doesn't assert the authenticated request contract; update
the test (around mountComponent and after flushPromises) to assert that the
stubbed fetch was called with the scheduler endpoint and an Authorization Bearer
header (e.g., assert fetch was called with a URL containing
"/web/admin/scheduler" and the init object contains headers with an
Authorization value matching /^Bearer\s+/), using the existing vi stub and
expect/Matchers so auth regressions are caught; keep vi.unstubAllGlobals()
as-is.
clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

54-60: ⚠️ Potential issue | 🟠 Major

Fail fast on malformed config.cost instead of truthy-only acceptance.

Any truthy data.config.cost is accepted, so malformed payloads can silently render invalid values. Validate the object shape and numeric boundaries before assignment, then surface a contract error when invalid.

Suggested validation guard
-    const data = await res.json();
-    if (data?.config?.cost) {
-      cost.value = data.config.cost;
-    } else {
-      cost.value = null;
-      error.value = "Cost data not available";
-    }
+    const data: unknown = await res.json();
+    const nextCost = (data as { config?: { cost?: unknown } })?.config?.cost;
+    const isValidCost =
+      typeof nextCost === "object" &&
+      nextCost !== null &&
+      typeof (nextCost as { enabled?: unknown }).enabled === "boolean" &&
+      typeof (nextCost as { daily_limit_usd?: unknown }).daily_limit_usd === "number" &&
+      (nextCost as { daily_limit_usd: number }).daily_limit_usd >= 0 &&
+      typeof (nextCost as { monthly_limit_usd?: unknown }).monthly_limit_usd === "number" &&
+      (nextCost as { monthly_limit_usd: number }).monthly_limit_usd >= 0 &&
+      typeof (nextCost as { warn_at_percent?: unknown }).warn_at_percent === "number" &&
+      (nextCost as { warn_at_percent: number }).warn_at_percent >= 0 &&
+      (nextCost as { warn_at_percent: number }).warn_at_percent <= 100 &&
+      typeof (nextCost as { allow_override?: unknown }).allow_override === "boolean";
+
+    if (!isValidCost) {
+      cost.value = null;
+      error.value = "Invalid /web/admin/config response: malformed config.cost";
+      return;
+    }
+    cost.value = nextCost as AdminCostView;

As per coding guidelines, "Validate input boundaries, auth/authz implications, and secret management. Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue` around
lines 54 - 60, The current assignment accepts any truthy data.config.cost;
instead validate the shape and numeric boundaries before setting cost.value:
after parsing res.json() inspect data?.config?.cost to ensure it's an object
with the expected numeric fields (e.g., total, monthly or whichever keys your UI
expects) and that those numbers are finite and within allowed ranges, and only
then set cost.value = data.config.cost; otherwise set cost.value = null and
error.value to a clear contract/validation error (e.g., "Invalid config.cost
shape or values") so malformed payloads fail fast; update the validation logic
near the res.json() handling and any helper used to parse/validate the cost
structure.
clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue (1)

41-48: ⚠️ Potential issue | 🟠 Major

Harden runtime parsing to enforce the backend contract types.

Current guards only check key presence; invalid channel_type/configured value types can still flow into channels.value and break UI assumptions.

Suggested fix
-    const data = await res.json();
-    if (
-      Array.isArray(data?.channels) &&
-      data.channels.every(
-        (c: unknown) =>
-          typeof c === "object" && c !== null && "channel_type" in c && "configured" in c
-      )
-    ) {
-      channels.value = data.channels;
+    const data: unknown = await res.json();
+    if (
+      data &&
+      typeof data === "object" &&
+      Array.isArray((data as { channels?: unknown }).channels)
+    ) {
+      const raw = (data as { channels: unknown[] }).channels;
+      channels.value = raw.filter(
+        (c): c is AdminChannelStatusView =>
+          typeof c === "object" &&
+          c !== null &&
+          typeof (c as { channel_type?: unknown }).channel_type === "string" &&
+          typeof (c as { configured?: unknown }).configured === "boolean"
+      );
     } else {
       channels.value = [];
     }

As per coding guidelines, "Look for behavioral regressions, missing tests, and contract breaks across modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue` around
lines 41 - 48, The runtime type guard around data?.channels only checks for
property presence and can allow wrong types through; update the check before
assigning to channels.value to validate that data.channels is an array and each
entry has channel_type as a string (or expected enum) and configured as a
boolean (or expected shape) and any other required fields, e.g., replace the
predicate used in the Array.every call to assert typeof c.channel_type ===
"string" and typeof c.configured === "boolean" (or call a small type-guard
function like isValidChannel(c)) so only fully validated channel objects are
assigned to channels.value; reference the existing data?.channels check and the
channels.value assignment and tighten the per-item checks for "channel_type" and
"configured".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts`:
- Around line 54-55: The tests stub the global fetch via vi and call
vi.unstubAllGlobals() inline (seen near vi.unstubAllGlobals() in
ChannelsOverview.spec.ts), which can be skipped if a test fails; move the
cleanup into a dedicated afterEach hook that always runs: add an afterEach block
that calls vi.unstubAllGlobals() (and explicitly restores or resets any fetch
stubs/mocks) so the global fetch is always cleaned between tests and no stub
leaks into subsequent specs.

In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue`:
- Around line 21-23: The loading flag is being cleared by any completed
request's finally block, causing stale requests to turn off the spinner; modify
fetchChannels to only update loading for the most recent fetch by introducing a
monotonically increasing request id (e.g., module-scoped let latestFetchId = 0),
incrementing it at the start of fetchChannels and capturing the current id in a
local const (e.g., const myId = ++latestFetchId), and then in the finally block
only set loading.value = false if myId === latestFetchId (or if the passed
AbortSignal indicates this is still the active request); reference the
fetchChannels function and the watcher that aborts old requests when applying
this change.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts`:
- Around line 20-69: Add a new spec in CostOverview tests that verifies the
contract-error path by stubbing fetch (used in mountComponent) to resolve
ok:true with a payload where config.cost is missing or malformed, then mount the
component (via mountComponent), await flushPromises, and assert that the
component renders the "Cost data not available" message (and/or shows the error
UI selector used for that branch); also ensure you clean up the global stub with
vi.unstubAllGlobals() at the end of the test.
- Around line 55-67: Move the global cleanup out of individual tests and into a
shared afterEach hook to ensure vi.unstubAllGlobals() always runs even if a test
fails: remove the per-test calls to vi.unstubAllGlobals() in the "shows error on
fetch failure" (and any other tests) and add a top-level afterEach(() =>
vi.unstubAllGlobals()) in this spec file so globals stubbed by
vi.stubGlobal("fetch", ...) are reliably restored; no behavior changes to
mountComponent or flushPromises are required.

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue`:
- Around line 35-77: The bug is that an earlier fetchCost invocation (using the
old abortController) can finish after a newer request started and its finally
block sets loading.value = false; fix by guarding state updates with a
request-order check: capture the current abortController (or a requestId) at the
start of fetchCost (or accept it as an argument) and in the catch/finally only
update loading.value, error.value, and cost.value if the captured controller (or
requestId) still equals the module-level abortController (or matches the latest
requestId); update references to fetchCost, abortController, loading.value and
the watcher to ensure the same controller/requestId is used for the comparison
so stale aborted responses cannot flip loading.

In `@clients/web/apps/dashboard/src/components/config/HealthDashboard.vue`:
- Around line 161-173: The hardcoded hex colors in the status indicator rules
(.health-indicator.ok, .component-indicator.ok, .health-indicator.error,
.component-indicator.error, .health-indicator.unknown) should be replaced with
CSS variables (e.g. var(--color-status-ok, `#22c55e`), var(--color-status-error,
`#ef4444`), var(--color-status-unknown, `#9ca3af`)) and then declare those variables
in your global/theme stylesheet (and override for dark/light themes as needed)
so status colors adapt to theming while keeping the hex values as sensible
fallbacks.
- Around line 63-72: The validation for assigning health.value allows components
to be null because typeof null === "object"; update the conditional that sets
health.value (the block checking data.health and data.health.components) to
explicitly reject null (e.g. add data.health.components !== null) or otherwise
ensure components is an object/array; this prevents
Object.values(health.value.components) in overallStatus from throwing when
components is null.

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue`:
- Around line 115-121: Replace the hardcoded hex values in the .configured and
.not-configured CSS rules with theme-aware CSS custom properties (for example
var(--color-success) and var(--color-muted) or existing Tailwind-derived
variables) so colors adapt to dark mode and brand theming; update the
.configured and .not-configured selectors to reference these custom properties
(and include sensible fallbacks like var(--color-success, `#22c55e`)) and ensure
use aligns with the app’s global CSS variables or Tailwind config for
accessibility/contrast.
- Around line 40-41: The response assigned to heartbeat.value is not validated
against the expected AdminHeartbeatView shape; add runtime guards before setting
heartbeat.value by checking that data exists and that data.config.heartbeat has
the required properties (e.g., hasOwnProperty('enabled') and
hasOwnProperty('interval_minutes')), that enabled is a boolean and
interval_minutes is a finite number (coerce/parse if necessary), and fall back
to null (or log an error) if validation fails; update the code around the
assignment to data = await res.json(); heartbeat.value = ... to perform these
checks and only assign a safe object matching the AdminHeartbeatView shape,
otherwise set heartbeat.value = null.

In `@clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts`:
- Line 62: Add a global cleanup hook by importing afterEach from "vitest" and
call afterEach(() => vi.unstubAllGlobals()); then remove the explicit
vi.unstubAllGlobals() calls spread through the spec (the individual calls
currently located where vi.unstubAllGlobals() is invoked) so that
vi.unstubAllGlobals() always runs regardless of test failures; ensure the new
afterEach import and hook are placed with the other imports near the top of the
file.

In `@clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue`:
- Around line 42-47: The finally block currently unconditionally sets
loading.value = false, causing a race where an aborted older fetch clears the
loading state for a newer in-flight fetch; modify the fetch logic in
ReliabilityOverview.vue to only clear loading when the finishing request is the
active one — e.g., create a local request id or capture the AbortController
instance at request start and in the finally check that it matches the
component-level current controller (or currentRequestId) before setting
loading.value = false; ensure the catch still returns on AbortError but does not
clear loading unless the request identity matches the active request.
- Line 28: Replace the hardcoded string assignment error.value = "Invalid
gateway URL" in the ReliabilityOverview.vue component with a translated string
from useI18n() (e.g., const { t } = useI18n(); error.value =
t('errors.invalidGatewayUrl')); add the key "errors.invalidGatewayUrl" with
appropriate English and Spanish translations to the locales files en.json and
es.json so the message is localized; repeat the same change for the seven other
overview components that currently hardcode this message to ensure consistency.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue`:
- Around line 21-58: An older aborted fetch can still run its finally block and
flip loading.value to false after a newer request started; modify
fetchSchedulerStatus and the watch that creates abortController to guard state
mutations so only the latest in-flight request updates loading, error, and
scheduler. Implement a request identity check: when creating a new
AbortController in the watch, set a local token/requestId (or capture the
controller instance) and read that token at the start of fetchSchedulerStatus;
before assigning scheduler.value, error.value, or toggling loading.value in the
try/catch/finally paths, verify the token/controller still matches the current
global abortController (or that the requestId is still current) and skip updates
if it does not; apply this guard around the assignments in fetchSchedulerStatus
(including where it sets scheduler.value, error.value, and in finally) to
prevent stale UI flips.

---

Duplicate comments:
In `@clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue`:
- Around line 41-48: The runtime type guard around data?.channels only checks
for property presence and can allow wrong types through; update the check before
assigning to channels.value to validate that data.channels is an array and each
entry has channel_type as a string (or expected enum) and configured as a
boolean (or expected shape) and any other required fields, e.g., replace the
predicate used in the Array.every call to assert typeof c.channel_type ===
"string" and typeof c.configured === "boolean" (or call a small type-guard
function like isValidChannel(c)) so only fully validated channel objects are
assigned to channels.value; reference the existing data?.channels check and the
channels.value assignment and tighten the per-item checks for "channel_type" and
"configured".

In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue`:
- Around line 54-60: The current assignment accepts any truthy data.config.cost;
instead validate the shape and numeric boundaries before setting cost.value:
after parsing res.json() inspect data?.config?.cost to ensure it's an object
with the expected numeric fields (e.g., total, monthly or whichever keys your UI
expects) and that those numbers are finite and within allowed ranges, and only
then set cost.value = data.config.cost; otherwise set cost.value = null and
error.value to a clear contract/validation error (e.g., "Invalid config.cost
shape or values") so malformed payloads fail fast; update the validation logic
near the res.json() handling and any helper used to parse/validate the cost
structure.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts`:
- Around line 21-47: The test "renders scheduler status on successful fetch"
stubs global fetch but doesn't assert the authenticated request contract; update
the test (around mountComponent and after flushPromises) to assert that the
stubbed fetch was called with the scheduler endpoint and an Authorization Bearer
header (e.g., assert fetch was called with a URL containing
"/web/admin/scheduler" and the init object contains headers with an
Authorization value matching /^Bearer\s+/), using the existing vi stub and
expect/Matchers so auth regressions are caught; keep vi.unstubAllGlobals()
as-is.

In `@clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue`:
- Line 93: The UI null-check for scheduler.task_count is inconsistent with the
API/Type contract—task_count is defined as number in
clients/web/apps/dashboard/src/types/admin-config.ts and the backend emits a
usize—so remove the null-specific branch in SchedulerStatus.vue (replace the
ternary that checks scheduler.task_count == null) and render the numeric value
or a deterministic default (e.g., 0) instead; ensure corresponding tests and any
UI copy expecting "notAvailable" are updated, and if you prefer to make the
backend/type nullable instead, update clients/agent-runtime/src/gateway/admin.rs
to emit an optional value and change admin-config.ts to number | null so the
existing Vue fallback becomes meaningful (pick one approach and apply
consistently across SchedulerStatus.vue, admin-config.ts, and admin.rs).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 42c0a398-5494-4988-8149-ab40729c3e5a

📥 Commits

Reviewing files that changed from the base of the PR and between 6e213b2 and a6de5ff.

📒 Files selected for processing (15)
  • clients/web/apps/chat/src/App.spec.ts
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sonar
  • GitHub Check: pr-checks
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/chat/src/App.vue
  • clients/web/apps/chat/src/App.spec.ts
**/*.vue

⚙️ CodeRabbit configuration file

**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.

Files:

  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/chat/src/App.vue
🧠 Learnings (1)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks

Applied to files:

  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
🔇 Additional comments (27)
clients/web/apps/dashboard/src/components/config/ComposioSettings.spec.ts (2)

10-46: Good contract test for field update and save emission.

This verifies both update:modelValue payload shape and single save emission, which protects component-event integration behavior.

As per coding guidelines, "Look for behavioral regressions, missing tests, and contract breaks across modules."


83-128: Secret-handling regression coverage is strong.

The replace→clear flow proves key wiping with a non-empty seed, and unchanged mode verifies no secret render/leak.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

clients/web/apps/dashboard/src/components/config/BrowserSettings.spec.ts (2)

10-44: Event contract coverage looks correct.

The test validates a single update:modelValue emission on mode change and a single save emission, reducing regression risk in parent integration.

As per coding guidelines, "Look for behavioral regressions, missing tests, and contract breaks across modules."


81-126: Secret-mode security behavior is well-covered.

These tests verify both secret clearing on replace→clear and no secret leakage in unchanged mode with a seeded secret value.

As per coding guidelines, "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."

clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts (3)

50-55: Auth-header assertions now present.

This addresses the prior security review feedback—fetch calls are now validated to include Authorization: Bearer <token>.


21-63: Good coverage of auth, rendering, and reactivity.

Tests verify the correct URL construction, auth headers on initial and subsequent fetches, and rendered server details including capabilities array joined as expected. Solid test structure.

Also applies to: 77-100


65-75: Error and edge-case handling well covered.

Tests for network failures and invalid gatewayUrl ensure the component fails gracefully with appropriate error messages and avoids unnecessary requests.

Also applies to: 102-121

clients/web/apps/dashboard/src/components/config/UpdateSettings.spec.ts (4)

9-16: LGTM!

Clean helper extraction that reduces boilerplate across tests. Properly typed and configures i18n deterministically.


19-63: LGTM!

Good coverage of data presence edge cases: full status data, completely missing updates, and missing nested status. Targeted selectors ensure assertions are precise.


65-106: LGTM!

Boolean-to-label rendering is properly tested for both true/false states of update_available. Combining related assertions in test 5 is reasonable.


108-127: LGTM!

Symmetric test coverage for auto_install_enabled: false addresses the previous feedback and completes boolean label testing.

clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue (2)

19-63: AbortController integration and cleanup look solid.

Proper cancellation on prop changes and unmount prevents stale response races. The AbortError handling (line 43) correctly avoids surfacing abort as a user-visible error.


66-90: Template states and accessibility are well-structured.

Loading, error, empty (!heartbeat), and data states are all handled distinctly. The aria-live / role attributes ensure screen reader announcements. Localized unit via t("heartbeat.unit") is correct.

clients/web/apps/dashboard/src/components/config/HealthDashboard.vue (2)

1-109: Well-implemented polling and abort logic.

The component correctly:

  • Uses AbortController to cancel in-flight requests on prop changes and unmount
  • Only shows loading state when no snapshot exists (avoids UI flicker on refresh)
  • Validates response shape before assignment
  • Properly cleans up interval and abort controller in onUnmounted

This addresses the patterns from McpOverview.vue and handles the edge cases raised in prior reviews.


112-143: Template accessibility and structure look good.

Proper use of aria-live, role, and aria-hidden attributes on dynamic content and decorative elements. The conditional rendering structure is clean.

clients/web/apps/chat/src/App.vue (7)

256-283: Stub implementations noted for Phase 5B.

handleApprove and handleReject only update local UI state without calling the backend POST /web/chat/tool-approval endpoint. The comments indicate this is intentional for now.


151-164: LGTM! Chunk accumulation is correctly implemented.

The streaming logic now properly accumulates chunks into streamBuffer before updating the assistant message, avoiding the previous overwrite bug.


165-172: LGTM! Auth errors no longer masked by fallback.

Credential invalid errors are now detected and cause an early return with error status, preventing the fallback from masking auth failures or triggering duplicate requests.


285-294: LGTM! Debounced persistence avoids hot-path writes.

The 300ms debounce prevents sessionStorage.setItem from firing on every streamed chunk, which was previously a performance concern during SSE streaming.


296-305: LGTM! Session restoration now triggers on session ID changes.

The watcher on chat.currentSessionId.value ensures messages are restored when the session becomes available, not just on mount when the ID may be empty.


225-233: LGTM! Message status is now persisted.

The serialization now includes status: m.status, so restored messages will retain their completion state after refresh.


517-520: The HealthIndicator component is secure and does not expose the bearer token. The token is used only in the fetch request Authorization header (line 40) and never appears in console logs, DOM attributes, or error messages. No action required.

clients/web/apps/chat/src/App.spec.ts (2)

140-162: LGTM! Stream request now asserts all auth and session headers.

The test verifies Authorization, X-Session-Id, and X-Request-Id on the /web/chat/stream call, plus X-Idempotency-Key on the webhook fallback. This closes the security gap from the past review.


232-298: Test validates accumulation logic but uses synchronous mock.

The Response body is delivered atomically, so this tests that the SSE parser correctly accumulates multiple chunk events—but doesn't exercise true incremental streaming (ReadableStream with chunked reads). That's acceptable for verifying the accumulation bug is fixed; consider a separate unit test in useChat.spec.ts with a mocked ReadableStream if you want to cover backpressure/partial-read scenarios.

clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts (1)

43-48: Good auth contract assertion in success path.

Nice coverage of both endpoint path and bearer header in the fetch call; this protects against silent auth regressions.

clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

88-90: A11y semantics for loading/error states look correct.

Using aria-live plus role="status"/role="alert" here is the right accessibility behavior for dynamic status updates.

clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue (1)

69-71: A11y status semantics and model fallback rendering look correct.

Loading/error regions now include appropriate aria-live + role, and fallback mappings render as primary → fallback, which improves operator visibility.

Also applies to: 89-93

Comment thread clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/CostOverview.vue
Comment thread clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts Outdated
Comment thread clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue Outdated
Fifth round of review refinements:

- Move vi.unstubAllGlobals to afterEach in 4 spec files
- Add fetchId guard to prevent loading race in 5 overview components
- Fix typeof null check in HealthDashboard response validation
- Add shape validation in HeartbeatOverview before assignment
- Add missing-cost error test in CostOverview.spec
- Assert fetch URL and auth headers in SchedulerStatus.spec
- Remove misleading null check for task_count (type is number)
- Replace 8 hardcoded error strings with i18n keys
- Clean stale biome-ignore suppressions in 8 components
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
clients/web/apps/dashboard/src/components/config/McpOverview.vue (1)

20-48: ⚠️ Potential issue | 🟡 Minor

Add fetchId guard to prevent stale responses from overwriting current state.

Unlike HeartbeatOverview.vue and SchedulerStatus.vue, this component lacks the fetchId pattern. While AbortController cancels the request, the finally block at line 46 unconditionally sets loading.value = false, which can flip loading state incorrectly if an older aborted request's finally executes after a newer request starts.

Proposed fix
 let abortController: AbortController | undefined;
+let fetchId = 0;
 
 async function fetchMcp(signal?: AbortSignal) {
+  const myId = ++fetchId;
   loading.value = true;
   error.value = null;
   try {
@@
     mcp.value = data.config?.mcp ?? null;
   } catch (e: unknown) {
     if (e instanceof DOMException && e.name === "AbortError") return;
     error.value = e instanceof Error ? e.message : String(e);
   } finally {
-    loading.value = false;
+    if (myId === fetchId) {
+      loading.value = false;
+    }
   }
 }
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/web/apps/dashboard/src/components/config/CostOverview.vue`:
- Around line 96-99: The decorative status indicator span in CostOverview.vue
(the element with class "status-indicator" and :class="cost.enabled ?
'configured' : 'not-configured'") should be hidden from assistive technologies;
add aria-hidden="true" to that span so screen readers ignore this purely visual
indicator, matching the approach used in SchedulerStatus.vue.

In `@clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue`:
- Around line 96-99: HeartbeatOverview.vue's visible status indicator is missing
the accessibility attribute used elsewhere; update the decorative status
indicator element (the same DOM node/class used to render the colored/pulse
status in HeartbeatOverview.vue) to include aria-hidden="true" so assistive tech
ignores this decorative element (matching SchedulerStatus.vue's treatment of its
status indicator).

In `@clients/web/apps/dashboard/src/components/config/TunnelOverview.vue`:
- Line 43: The watcher currently invokes fetchTunnel but doesn't abort
outstanding requests on component destroy; import onUnmounted and create an
AbortController (e.g., controller) that fetchTunnel uses (pass controller.signal
into fetch/axios calls), update the controller each time before starting a new
request (abort previous controller if exists), and call controller.abort()
inside an onUnmounted handler to cancel any pending request when the component
is destroyed; reference the existing watch(...) call and the fetchTunnel
function when adding the controller and onUnmounted cleanup.
- Around line 18-41: fetchTunnel can produce stale-overwrite and unvalidated
data; add an AbortController to cancel previous requests and a numeric fetchId
(incremented per call) to ignore out-of-order responses, ensure the finally
block only clears loading for the current fetchId, and validate the parsed
response shape before assigning tunnel.value (verify data.config?.tunnel
conforms to AdminTunnelView) rather than assigning it directly; use the existing
validateGatewayUrl/props.gatewayUrl and props.bearerToken to build the request,
abort previous controller when starting a new fetchTunnel, and only set
tunnel.value and loading.value when the response is from the latest fetchId.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca47037c-c0fc-4840-a41a-027a35356108

📥 Commits

Reviewing files that changed from the base of the PR and between a6de5ff and 74bc8c3.

📒 Files selected for processing (14)
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/packages/locales/src/en.json
  • clients/web/packages/locales/src/es.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sonar
  • GitHub Check: pr-checks
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts
  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
  • clients/web/packages/locales/src/en.json
  • clients/web/packages/locales/src/es.json
**/*.vue

⚙️ CodeRabbit configuration file

**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.

Files:

  • clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue
  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/CostOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
  • clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue
  • clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue
  • clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
  • clients/web/apps/dashboard/src/components/config/HealthDashboard.vue
🧠 Learnings (4)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Inspect existing module and adjacent tests before editing; define scope boundary with one concern per PR and avoid mixed feature+refactor+infra patches

Applied to files:

  • clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts
  • clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable

Applied to files:

  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks

Applied to files:

  • clients/web/apps/dashboard/src/components/config/McpOverview.vue
  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue
🔇 Additional comments (15)
clients/web/apps/dashboard/src/components/config/SchedulerStatus.spec.ts (1)

1-88: LGTM!

The test suite is well-structured with proper cleanup via afterEach, validates the authenticated request contract (URL + Authorization header), and covers success, error, and disabled states. Prior review concerns have been addressed.

clients/web/apps/dashboard/src/components/config/ChannelsOverview.spec.ts (1)

1-68: LGTM!

The test suite properly validates the auth contract with URL and Authorization header assertions, uses afterEach for cleanup, and covers both success and error paths. Prior review concerns are addressed.

clients/web/apps/dashboard/src/components/config/CostOverview.spec.ts (1)

1-85: LGTM!

Excellent coverage: validates auth contract, tests successful rendering with formatted currency values, handles fetch failures, and verifies the "cost not available" error path for missing payload. Prior review concerns are all addressed.

clients/web/apps/dashboard/src/components/config/HeartbeatOverview.vue (1)

1-140: LGTM!

Well-implemented component with proper request cancellation via AbortController, stale-update protection via fetchId, response shape validation, accessibility attributes (aria-live, role), and localized unit display. Prior major concerns are addressed.

clients/web/apps/dashboard/src/components/config/McpOverview.vue (1)

66-112: LGTM on template structure.

Good defensive coding with Array.isArray() guards, proper accessibility attributes, and clean conditional rendering.

clients/web/apps/dashboard/src/components/config/SchedulerStatus.vue (1)

1-142: LGTM!

Solid implementation with AbortController for cancellation, fetchId guard for stale updates, URL validation, and proper accessibility attributes. All prior review concerns have been addressed.

clients/web/apps/dashboard/src/components/config/ReliabilityOverview.vue (1)

1-144: LGTM!

Comprehensive implementation addressing all prior concerns: fetchId guard for stale updates, URL validation with i18n error message, localized units, proper accessibility attributes, and model_fallbacks now correctly displays "primary → fallback" mappings.

clients/web/apps/dashboard/src/components/config/CostOverview.vue (1)

1-166: LGTM!

Well-implemented component with proper fetchId guard for stale updates, URL validation, contract validation (surfaces "cost not available" error), Intl.NumberFormat for currency display, and accessibility attributes on loading/error states. All prior major concerns addressed.

clients/web/apps/dashboard/src/components/config/McpOverview.spec.ts (2)

25-65: Test masks potential component bug: missing filter for disabled servers.

The mock data only includes servers with enabled: true. According to the context snippet from McpOverview.vue:85-108, the component iterates over all servers without filtering by the enabled flag. If disabled servers should be hidden, this test won't catch that regression because the mock never includes enabled: false entries.

Consider adding a disabled server to the mock and asserting it either renders with appropriate styling or doesn't render at all (depending on intended behavior):

Proposed test data enhancement
 const mockConfig = {
   config: {
     mcp: {
       enabled: true,
       servers: [
         {
           name: "test-server",
           enabled: true,
           command: "npx test-mcp",
           capabilities: ["tools", "prompts"],
           startup_timeout_ms: 5000,
           call_timeout_ms: 30000,
           output_limit_bytes: 1048576,
         },
+        {
+          name: "disabled-server",
+          enabled: false,
+          command: "npx disabled-mcp",
+          capabilities: [],
+          startup_timeout_ms: 5000,
+          call_timeout_ms: 30000,
+          output_limit_bytes: 1048576,
+        },
       ],
     },
   },
 };
+// Then assert the expected behavior for disabled servers

1-118: LGTM on test structure and auth assertions.

The test suite properly:

  • Uses afterEach for cleanup (robust against test failures)
  • Validates Authorization: Bearer header on fetch calls
  • Covers refetch on prop changes and invalid URL handling
clients/web/apps/dashboard/src/components/config/ChannelsOverview.vue (1)

1-103: LGTM — well-structured component with proper safeguards.

The implementation correctly addresses:

  • Stale-request race via fetchId guard
  • Request cancellation with AbortController + cleanup on unmount
  • Gateway URL validation before attaching credentials
  • Response shape validation before assignment
  • A11y with aria-live/role on status messages and aria-hidden on decorative indicators
clients/web/apps/dashboard/src/components/config/HealthDashboard.vue (2)

1-204: LGTM on architecture — polling, cancellation, and validation are solid.

The component properly:

  • Resets the polling interval on prop changes
  • Aborts in-flight requests before starting new ones
  • Validates response shape including the components !== null guard
  • Cleans up on unmount

19-24: No action needed. The case-sensitivity concern is unfounded; the backend consistently sends lowercase status values ("ok", "error", "starting") that match the frontend's comparison logic.

clients/web/packages/locales/src/en.json (1)

1-392: LGTM — locale additions are complete and consistent.

All new dashboard sections and error/status keys are present. The structure mirrors the component requirements from the PR.

clients/web/packages/locales/src/es.json (1)

1-392: LGTM — Spanish translations are accurate and consistent.

The translations maintain proper Spanish conventions and align with the existing localization patterns.

Comment thread clients/web/apps/dashboard/src/components/config/CostOverview.vue
Comment thread clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
Comment thread clients/web/apps/dashboard/src/components/config/TunnelOverview.vue
- Add aria-hidden to CostOverview status indicator
- Add AbortController, fetchId guard, and onBeforeUnmount cleanup
  to TunnelOverview for stale request cancellation
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
76.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@yacosta738 yacosta738 merged commit 307a1f1 into main Mar 28, 2026
16 of 18 checks passed
@yacosta738 yacosta738 deleted the feat/276-expand-dashboard-and-web-operational-clients-to-match-runtime-admin-capabilities branch March 28, 2026 18:34
@yacosta738 yacosta738 mentioned this pull request Mar 28, 2026
@dallay-bot dallay-bot Bot mentioned this pull request Apr 9, 2026
@dallay-bot dallay-bot Bot mentioned this pull request May 3, 2026
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.

Expand dashboard and web operational clients to match runtime admin capabilities

1 participant