Skip to content

feat(gateway): unified settings page with subtabs#1191

Open
ilblackdragon wants to merge 12 commits intostagingfrom
settings-page-polish
Open

feat(gateway): unified settings page with subtabs#1191
ilblackdragon wants to merge 12 commits intostagingfrom
settings-page-polish

Conversation

@ilblackdragon
Copy link
Member

@ilblackdragon ilblackdragon commented Mar 15, 2026

Summary

Replaces the separate Extensions and Skills top-level tabs with a single Settings tab containing a sidebar with subtabs:

  • Inference — LLM provider/model settings (fetched from /api/settings, grouped by prefix)
  • Agent — agent behavior settings (system prompt length, compaction, etc.)
  • Channels — connected channel status via /api/gateway/status + extensions APIs, with configure/reconnect actions
  • Networking — tunnel, proxy, and network-related settings
  • Extensions — installed WASM extensions, available registry extensions, install form
  • MCP — MCP servers list with add/remove/configure
  • Skills — ClawHub search, installed skills, install-by-URL

Key changes

  • Unified settings layout: sidebar + content panel with subtab navigation, search toolbar, export/import
  • Settings API integration: fetches all settings from /api/settings/export, renders grouped key-value editors with save/reset per setting, tiered into Inference/Agent/Channels/Networking by prefix
  • Channels subtab: shows built-in channel status (from /api/gateway/status) and messaging channels (WASM channel extensions) with full stepper/pairing UI
  • Extension cards polished: state-based styling (active/needs-setup/inactive), configure/reconfigure buttons, cleaner card layout
  • MCP servers separated: moved to their own subtab with dedicated loadMcpServers() function
  • Removed "Registered Tools" debug section: the tools table in Extensions was internal debug info not useful to end users — removed HTML, CSS, JS fetch, and i18n keys
  • Confirmation modal: added a reusable confirm dialog for destructive actions (remove extension/skill/MCP server)
  • SSE event handlers updated: auth_completed and extension_status now refresh the correct subtab via refreshCurrentSettingsTab()
  • 204 response handling: apiFetch now handles HTTP 204 (No Content) without trying to parse JSON
  • i18n: added new keys for settings subtabs, channels, networking, toolbar (en + zh-CN); removed unused tools.* keys; hardcoded strings migrated to I18n.t()

Test plan

  • Open web gateway → verify single "Settings" tab replaces old Extensions/Skills tabs
  • Click through each subtab (Inference, Agent, Channels, Networking, Extensions, MCP, Skills) — content loads without errors
  • Edit a setting value, save, verify it persists on refresh
  • Export/import settings round-trip
  • Install/remove an extension from the Extensions subtab
  • Add/remove an MCP server from the MCP subtab
  • Search and install a skill from the Skills subtab
  • Verify no "Registered Tools" section appears anywhere
  • Test i18n switching (EN ↔ zh-CN) on settings page
  • Check no console errors from removed /api/extensions/tools fetch

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 15, 2026 04:05
@github-actions github-actions bot added scope: channel/web Web gateway channel size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Mar 15, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive overhaul of the web gateway's settings and configuration pages. The primary goal is to enhance usability and clarity by consolidating various settings into a new, well-organized 'Settings' tab with distinct sub-panels. This refactoring improves the visual presentation of configuration options, streamlines the management of extensions and channels, and removes unnecessary debug information from the user interface. It also lays the groundwork for more robust settings management with import/export capabilities and a standardized confirmation flow for user actions.

Highlights

  • Settings Page Refactor: The web gateway's settings interface has been completely refactored into a new, dedicated 'Settings' tab. This tab now features a sidebar with sub-panels for different configuration categories, including Inference, Agent, Channels, Networking, Extensions, MCP, and Skills, significantly improving organization and user experience.
  • UI Polish and Visual Hierarchy: Extension cards now feature state-based styling (active, inactive, error, pairing) for improved visual feedback. General layout refinements and enhanced visual hierarchy have been applied across the settings pages to create a cleaner and more intuitive interface.
  • Removal of Debug Tools Section: The 'Registered Tools' debug section has been removed from the Extensions tab, as it contained internal information not relevant to end-users. Associated CSS styles, i18n keys, and JavaScript fetch/render code for this section have also been cleaned up.
  • Structured Settings Management: A new structured approach to managing settings has been implemented, allowing for dynamic rendering of various input types (text, number, boolean, select) with reactivity for conditional display. This also includes functionality for exporting and importing settings configurations.
  • Backend Configuration Exposure: A new ActiveConfigSnapshot struct has been introduced in the Rust backend to expose the currently active (resolved) configuration, including LLM backend, model, and enabled channels, to the frontend for display in the settings UI.
  • Confirmation Modal: A generic confirmation modal has been added to provide a consistent user experience for sensitive actions, such as removing extensions or skills.
Changelog
  • src/channels/web/mod.rs
    • Added active_config field to GatewayChannelState and its initialization.
    • Implemented with_active_config method for GatewayChannel to inject configuration snapshots.
  • src/channels/web/server.rs
    • Defined ActiveConfigSnapshot struct for exposing active LLM and channel configurations.
    • Added active_config to GatewayState and included its fields in GatewayStatusResponse.
  • src/channels/web/static/app.js
    • Introduced currentSettingsSubtab for managing active settings panel.
    • Updated apiFetch to handle 204 No Content responses.
    • Modified SSE event listeners to refresh data based on the active settings subtab.
    • Refactored switchTab to navigate to the new 'settings' tab and load its sub-panels.
    • Updated loadExtensions to filter extension types and remove the 'Registered Tools' API call.
    • Replaced direct loadExtensions calls with refreshCurrentSettingsTab in extension management functions.
    • Added state-based CSS class logic to renderExtensionCard.
    • Implemented refreshCurrentSettingsTab to dynamically reload the current settings subtab.
    • Added renderCardsSkeleton to loadSkills and state-active class to renderSkillCard.
    • Replaced confirm() dialogs with showConfirmModal() for skill and extension removal.
    • Adjusted keyboard shortcuts (Mod+1-5) to align with the new tab structure.
    • Added extensive JavaScript for structured settings rendering, including INFERENCE_SETTINGS, AGENT_SETTINGS, NETWORKING_SETTINGS definitions, and associated rendering/loading functions.
    • Implemented saveSetting with restart banner logic for critical settings.
    • Extracted and implemented loadMcpServers and loadChannelsStatus functions.
    • Added renderBuiltinChannelCard for displaying built-in channels.
    • Implemented settings export and import functionality.
    • Added a generic showConfirmModal and closeConfirmModal for user confirmations.
    • Integrated settings search functionality for filtering settings rows.
  • src/channels/web/static/i18n/en.js
    • Added new tab.settings and settings.* keys for the new settings structure.
    • Updated extensions.available and extensions.installWasm translations.
    • Removed tools.* translations.
    • Added new settings.export, settings.import, settings.searchPlaceholder, settings.restartRequired, and related messages.
  • src/channels/web/static/i18n/zh-CN.js
    • Updated Chinese translations to reflect the new settings tab and its sub-panels, and removed 'tools' related entries.
  • src/channels/web/static/index.html
    • Replaced top-level 'Extensions' and 'Skills' tabs with a single 'Settings' tab.
    • Introduced a new tab-settings panel with a sidebar containing sub-tabs for various settings categories.
    • Migrated content from old 'Extensions' and 'Skills' tabs into respective settings sub-panels.
    • Removed the 'Registered Tools' section HTML.
    • Added a settings toolbar with search, export, and import buttons.
    • Included HTML structure for the new confirmation modal.
  • src/channels/web/static/style.css
    • Added new CSS variables for colors, backgrounds, and transitions.
    • Updated styling for restart loader and modal to use new CSS variables.
    • Adjusted font sizes and styling for section headers (h3, h4).
    • Implemented state-based styling for .ext-card (e.g., state-active, state-error).
    • Added styling for built-in extension kinds (.ext-kind.kind-builtin).
    • Enhanced .btn-ext styles with transitions and active states.
    • Added backdrop-filter to modal overlays.
    • Removed all CSS rules related to .tools-table.
    • Added extensive new CSS for the settings tab layout, sidebar, sub-tabs, structured settings rows, inputs, indicators, and the restart banner.
    • Included styles for the new confirmation modal and its buttons.
    • Added mobile responsiveness for settings layout.
    • Introduced loading skeleton styles for settings and cards.
    • Added styles for the settings search empty state.
  • src/channels/web/test_helpers.rs
    • Initialized active_config in TestGatewayBuilder for consistent state management in tests.
  • src/channels/web/ws.rs
    • Initialized active_config in test state creation.
  • src/main.rs
    • Added logic to ensure the WASM channels directory exists for proper runtime initialization.
    • Populated and passed ActiveConfigSnapshot to the GatewayChannel during server setup.
  • tests/openai_compat_integration.rs
    • Initialized active_config in test server setup functions.
  • tests/support/gateway_workflow_harness.rs
    • Initialized active_config in GatewayWorkflowHarness constructor.
  • tests/ws_gateway_integration.rs
    • Initialized active_config in test server setup.
Activity
  • The pull request was generated using Claude Code, indicating an initial submission with no prior human review activity.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant polish to the settings page UI, refactoring the previous 'Extensions' and 'Skills' tabs into a unified 'Settings' area with sub-tabs. The debug 'Registered Tools' section has been removed, and the associated backend and frontend code cleaned up. The changes are well-structured, using a data-driven approach for the new settings UI and improving user experience with loading skeletons and better confirmation dialogs. I've identified a couple of areas for minor refactoring in the JavaScript to improve code reuse and remove dead code.

Comment on lines +384 to +386
if (currentTab === 'settings' && currentSettingsSubtab === 'extensions') loadExtensions();
if (currentTab === 'settings' && currentSettingsSubtab === 'mcp') loadMcpServers();
if (currentTab === 'settings' && currentSettingsSubtab === 'channels') loadChannelsStatus();
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

You've introduced a great helper function refreshCurrentSettingsTab() later in the file. To improve maintainability and reduce duplication, you can use it here as well.

Suggested change
if (currentTab === 'settings' && currentSettingsSubtab === 'extensions') loadExtensions();
if (currentTab === 'settings' && currentSettingsSubtab === 'mcp') loadMcpServers();
if (currentTab === 'settings' && currentSettingsSubtab === 'channels') loadChannelsStatus();
if (currentTab === 'settings') refreshCurrentSettingsTab();

Copy link
Member Author

Choose a reason for hiding this comment

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

Done — replaced with if (currentTab === 'settings') refreshCurrentSettingsTab(); in f8ffd9e.

Comment on lines +391 to +393
if (currentTab === 'settings' && currentSettingsSubtab === 'extensions') loadExtensions();
if (currentTab === 'settings' && currentSettingsSubtab === 'mcp') loadMcpServers();
if (currentTab === 'settings' && currentSettingsSubtab === 'channels') loadChannelsStatus();
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the previous comment, you can use the refreshCurrentSettingsTab() helper function here to avoid code duplication.

Suggested change
if (currentTab === 'settings' && currentSettingsSubtab === 'extensions') loadExtensions();
if (currentTab === 'settings' && currentSettingsSubtab === 'mcp') loadMcpServers();
if (currentTab === 'settings' && currentSettingsSubtab === 'channels') loadChannelsStatus();
if (currentTab === 'settings') refreshCurrentSettingsTab();

Copy link
Member Author

Choose a reason for hiding this comment

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

Done — same fix applied here in f8ffd9e.

Comment on lines +4835 to +4842
function formatGroupName(name) {
return name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
}

function formatSettingLabel(name) {
var s = name.replace(/_/g, ' ');
return s.charAt(0).toUpperCase() + s.slice(1);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

These helper functions formatGroupName and formatSettingLabel appear to be unused. To keep the codebase clean, it's best to remove them.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed both functions in a7aeff4.

ilblackdragon and others added 2 commits March 14, 2026 21:09
- Backend: add ActiveConfigSnapshot to expose resolved LLM backend,
  model, and enabled channels via /api/gateway/status
- Add missing Agent settings (daily cost cap, actions/hour, local tools)
- Add Sandbox, Routines, Safety, Skills, and Search setting groups
- Settings import/export (JSON download + file upload)
- Active env defaults shown as placeholders in Inference settings
- Styled confirmation modals replace window.confirm() for remove actions
- Global restart banner persists across settings subtab switches
- Client-side validation with min/max constraints on number inputs
- Accessibility: aria-label on inputs, role=status on save indicators
- Settings search filters rows across current subtab
- Smooth CSS transitions for conditional field visibility (showWhen)
- Tunnel settings in Channels subtab
- Mobile responsive settings layout at 768px breakpoint
- i18n keys for toolbar, search, and import/export in en + zh-CN

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

Remove the "Registered Tools" table from the extensions tab (debug info
not useful to end users), clean up associated CSS/i18n/JS. Additional
settings page UI polish: extension card state styling, layout refinements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ilblackdragon ilblackdragon changed the title feat(gateway): settings page polish and remove debug tools list feat(gateway): unified settings page with subtabs Mar 15, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the web gateway’s configuration UI by consolidating Extensions/Skills into a new Settings tab layout, enhancing extension card styling, and removing the end-user-facing “Registered Tools” debug section. It also wires the frontend to the settings import/export APIs and exposes resolved runtime config via /api/gateway/status.

Changes:

  • Replace separate Extensions/Skills top-level tabs with a Settings tab + subtabs (Inference/Agent/Channels/Networking/Extensions/MCP/Skills).
  • Remove “Registered Tools” UI, associated CSS, i18n keys, and frontend fetch/render logic.
  • Add ActiveConfigSnapshot to gateway state and extend /api/gateway/status with LLM + enabled-channels info.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Update GatewayState construction with new active_config field.
tests/support/gateway_workflow_harness.rs Update GatewayState construction with new active_config field.
tests/openai_compat_integration.rs Update GatewayState construction with new active_config field in test servers.
src/main.rs Ensure WASM channels dir exists; populate gateway active_config snapshot.
src/channels/web/ws.rs Update test GatewayState with active_config.
src/channels/web/test_helpers.rs Update TestGatewayBuilder state with active_config.
src/channels/web/static/style.css Add Settings layout/styles, extension card state styling, and remove tools table CSS.
src/channels/web/static/index.html Introduce Settings tab layout + confirmation modal; remove Extensions/Skills top-level tabs and tools table section.
src/channels/web/static/i18n/zh-CN.js Add Settings-related keys; remove Registered Tools keys; adjust extensions labels.
src/channels/web/static/i18n/en.js Add Settings-related keys; remove Registered Tools keys; adjust extensions labels.
src/channels/web/static/app.js Add Settings subtab routing, settings import/export/search, channels status UI; remove tools fetch/render.
src/channels/web/server.rs Add ActiveConfigSnapshot to GatewayState; extend /api/gateway/status response.
src/channels/web/mod.rs Add builder method with_active_config and plumb snapshot through state rebuild.
Comments suppressed due to low confidence (3)

src/channels/web/static/app.js:4341

  • renderCardsSkeleton() wraps skeleton cards in a new <div class="extensions-list">…</div>. Several callers (e.g. #extensions-list, #skills-list, #mcp-servers-list) are already .extensions-list containers, so this creates nested .extensions-list elements and can break layout/styling. Consider returning only the card markup (no outer wrapper) or making the wrapper optional based on the caller.
    e.preventDefault();
    if (currentTab === 'memory') {
      document.getElementById('memory-search').focus();
    } else {
      document.getElementById('chat-input').focus();
    }
    return;

src/channels/web/static/app.js:5032

  • Export/import UX strings are hard-coded in English (and export doesn’t surface any success toast), even though i18n keys like settings.exportSuccess, settings.importSuccess, and settings.importFailed were added. To meet the PR’s i18n test plan, use I18n.t(...) for these messages and show a localized success toast after export.
  },
];

function loadNetworkingSettings() {
  var container = document.getElementById('settings-networking-content');
  container.innerHTML = renderSettingsSkeleton(4);

  apiFetch('/api/settings/export').then(function(data) {
    var settings = data.settings || {};
    container.innerHTML = '';
    renderStructuredSettingsInto(container, NETWORKING_SETTINGS, settings, {});
  }).catch(function(err) {
    container.innerHTML = '<div class="empty-state">Failed to load settings: '
      + escapeHtml(err.message) + '</div>';
  });
}

function formatGroupName(name) {
  return name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
}

function formatSettingLabel(name) {
  var s = name.replace(/_/g, ' ');
  return s.charAt(0).toUpperCase() + s.slice(1);
}

// --- Toasts ---

function showToast(message, type) {
  const container = document.getElementById('toasts');
  const toast = document.createElement('div');
  toast.className = 'toast toast-' + (type || 'info');
  toast.textContent = message;
  container.appendChild(toast);
  // Trigger slide-in
  requestAnimationFrame(() => toast.classList.add('visible'));
  setTimeout(() => {
    toast.classList.remove('visible');

src/channels/web/static/app.js:4229

  • The structured settings definitions introduce many user-visible labels/descriptions as hard-coded English strings (group names, labels, descriptions). This prevents the Settings tab from being fully localized for zh-CN as described in the test plan. Consider moving these strings into i18n keys and referencing them via I18n.t(...) when rendering rows/groups.
    installBtn.addEventListener('click', (function(s, btn) {
      return function() {
        if (!confirm('Install skill "' + s + '" from ClawHub?')) return;
        btn.disabled = true;
        btn.textContent = I18n.t('extensions.installing');
        installSkill(s, null, btn);
      };
    })(slug, installBtn));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4971 to +4979
label.textContent = active ? 'Active' : 'Inactive';
actions.appendChild(label);
card.appendChild(actions);

return card;
}

// --- Networking Settings ---

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch — removed data-i18n="btn.confirm" from the button in a7aeff4. The label is now always set dynamically by showConfirmModal().

Comment on lines +2257 to 2261
return e.kind !== 'mcp_server' && e.kind !== 'wasm_channel' && e.kind !== 'channel' && !e.installed;
});

// Available WASM extensions
var wasmSection = document.getElementById('available-wasm-section');
if (wasmEntries.length === 0) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — Configure/Reconfigure now uses I18n.t('ext.configure') / I18n.t('ext.reconfigure') (keys already existed). Done in a7aeff4.

Comment on lines +283 to +285
<button class="settings-subtab" data-settings-subtab="skills" data-i18n="tab.skills">Skills</button>
</div>
<div class="extensions-section" id="available-wasm-section">
<h3 data-i18n="extensions.available">Available WASM Extensions</h3>
<div class="extensions-list" id="available-wasm-list">
<div class="empty-state" data-i18n="common.loading">Loading...</div>
<div class="settings-content">
Copy link
Member Author

Choose a reason for hiding this comment

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

Added data-i18n-placeholder="settings.searchPlaceholder" to the input in a7aeff4.

ilblackdragon and others added 2 commits March 14, 2026 21:25
- Use refreshCurrentSettingsTab() in SSE event handlers to reduce duplication
- Remove unused formatGroupName/formatSettingLabel helpers
- Use i18n keys for MCP Configure/Reconfigure buttons
- Add data-i18n-placeholder to settings search input
- Remove data-i18n from confirm modal button (set dynamically by showConfirmModal)
- Fix cargo fmt in main.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on-check]

- Update TABS list: replace extensions/skills with settings
- Add settings_subtab/settings_subpanel selectors to helpers
- Update test_connection, test_skills, test_extensions, test_wasm_lifecycle
  to navigate via Settings > subtab instead of top-level tabs
- Move MCP card tests to use go_to_mcp() helper (MCP is now a separate subtab)
- Remove tools table tests and mock_ext_apis tools= parameter
- Fix CSP violation: replace inline onclick on confirm modal cancel button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 15, 2026 04:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates the web gateway’s Extensions/Skills UI into a single Settings tab with sidebar subtabs, adds settings import/export + search, and extends the gateway status payload to support richer “active config” display.

Changes:

  • Replaces top-level Extensions/Skills tabs with a unified Settings tab + subtabs (Inference/Agent/Channels/Networking/Extensions/MCP/Skills).
  • Adds frontend structured settings editor (load/save/delete), import/export, search UI, and a reusable confirmation modal for destructive actions.
  • Extends /api/gateway/status to include an active_config snapshot (LLM backend/model + enabled channels) and updates tests accordingly.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Adds active_config to test gateway state construction.
tests/support/gateway_workflow_harness.rs Adds active_config to harness gateway state.
tests/openai_compat_integration.rs Adds active_config to OpenAI-compat test gateway state.
tests/e2e/scenarios/test_wasm_lifecycle.py Updates navigation to Settings > Extensions subtab.
tests/e2e/scenarios/test_skills.py Updates navigation to Settings > Skills subtab via helper.
tests/e2e/scenarios/test_extensions.py Updates Extensions/MCP E2E tests for Settings subtabs and removes tools-table checks.
tests/e2e/helpers.py Adds Settings subtab selectors; updates top-level tabs list.
src/main.rs Ensures WASM channels dir exists; injects ActiveConfigSnapshot into gateway.
src/channels/web/ws.rs Updates web channel test state with active_config.
src/channels/web/test_helpers.rs Updates TestGatewayBuilder to set default active_config.
src/channels/web/static/style.css Adds Settings layout/toolbar/modal/skeleton styling and refines extension card styling.
src/channels/web/static/index.html Introduces Settings tab layout, subtabs, toolbar, and confirm modal markup.
src/channels/web/static/i18n/en.js Adds settings-related i18n keys; updates extensions strings; removes tools keys.
src/channels/web/static/i18n/zh-CN.js Adds settings-related i18n keys; updates extensions strings; removes tools keys.
src/channels/web/static/app.js Implements settings subtabs, structured settings editor, channels/MCP views, confirm modal, and 204 handling.
src/channels/web/server.rs Adds ActiveConfigSnapshot + includes it in /api/gateway/status.
src/channels/web/mod.rs Adds with_active_config() builder method and stores snapshot in gateway state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4815 to +4819
mcpList.innerHTML = '<div class="empty-state">No MCP servers available</div>';
}
}).catch(function(err) {
mcpList.innerHTML = '<div class="empty-state">Failed to load MCP servers: '
+ escapeHtml(err.message) + '</div>';
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — empty state now uses I18n.t('mcp.noServers') and error uses I18n.t('common.loadFailed'). Done in 93cd14b.

Comment on lines +5171 to +5172
}).catch(function(err) {
showToast('Export failed: ' + err.message, 'error');
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — export now shows I18n.t('settings.exportSuccess') on success and uses i18n for the error toast. Done in 93cd14b.

Comment on lines +5189 to +5197
}).then(function() {
showToast('Settings imported successfully', 'success');
loadSettingsSubtab(currentSettingsSubtab);
}).catch(function(err) {
showToast('Import failed: ' + err.message, 'error');
});
} catch (e) {
showToast('Invalid JSON file', 'error');
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — all three import toasts now use I18n.t('settings.importSuccess') and I18n.t('settings.importFailed', { message }). Done in 93cd14b.

<div class="settings-content">
<div class="settings-toolbar">
<div class="settings-search">
<input type="text" id="settings-search-input" data-i18n-placeholder="settings.searchPlaceholder" placeholder="Search settings..." aria-label="Search settings">
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — added data-i18n-attr="aria-label" + data-i18n="settings.searchPlaceholder" so both the placeholder and aria-label get translated. Done in 93cd14b.

Comment on lines +4827 to +4830
Promise.all([
apiFetch('/api/gateway/status').catch(function() { return {}; }),
apiFetch('/api/extensions').catch(function() { return { extensions: [] }; }),
apiFetch('/api/extensions/registry').catch(function() { return { entries: [] }; }),
Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch — there is no /api/channels/status endpoint; the channels subtab fetches from /api/gateway/status + extensions APIs. Updated the PR description to reflect the actual implementation.

Comment on lines +5210 to +5217
var rows = activePanel.querySelectorAll('.settings-row');
var visibleCount = 0;
rows.forEach(function(row) {
var text = row.textContent.toLowerCase();
if (query === '' || text.indexOf(query) !== -1) {
row.classList.remove('search-hidden');
visibleCount++;
} else {
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — visibleCount now only increments for rows that are not .hidden (i.e. not hidden by showWhen conditions). Done in 93cd14b.

builtinList.appendChild(renderBuiltinChannelCard(
'CLI',
'Terminal UI with Ratatui',
enabledChannels.indexOf('repl') !== -1,
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — CLI card now checks for 'cli' channel key instead of 'repl'. Done in 93cd14b.

Comment on lines +4397 to +4420
group: 'LLM Provider',
settings: [
{ key: 'llm_backend', label: 'Backend', description: 'LLM inference provider',
type: 'select', options: ['nearai', 'anthropic', 'openai', 'ollama', 'openai_compatible', 'tinfoil', 'bedrock'] },
{ key: 'selected_model', label: 'Model', description: 'Model name or ID for the selected backend', type: 'text' },
{ key: 'ollama_base_url', label: 'Ollama URL', description: 'Base URL for Ollama API', type: 'text',
showWhen: { key: 'llm_backend', value: 'ollama' } },
{ key: 'openai_compatible_base_url', label: 'OpenAI-compatible URL', description: 'Base URL for OpenAI-compatible API', type: 'text',
showWhen: { key: 'llm_backend', value: 'openai_compatible' } },
{ key: 'bedrock_region', label: 'Bedrock Region', description: 'AWS region for Bedrock', type: 'text',
showWhen: { key: 'llm_backend', value: 'bedrock' } },
{ key: 'bedrock_cross_region', label: 'Cross-Region', description: 'Enable cross-region inference', type: 'text',
showWhen: { key: 'llm_backend', value: 'bedrock' } },
{ key: 'bedrock_profile', label: 'AWS Profile', description: 'AWS profile for Bedrock auth', type: 'text',
showWhen: { key: 'llm_backend', value: 'bedrock' } },
]
},
{
group: 'Embeddings',
settings: [
{ key: 'embeddings.enabled', label: 'Enabled', description: 'Enable vector embeddings for memory search', type: 'boolean' },
{ key: 'embeddings.provider', label: 'Provider', description: 'Embeddings API provider',
type: 'select', options: ['openai', 'nearai'] },
{ key: 'embeddings.model', label: 'Model', description: 'Embedding model name', type: 'text' },
Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed this should be localized. Adding i18n keys for all settings labels/descriptions is a bigger change (100+ strings across two locale files) — will address in a follow-up PR to keep this one focused.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done — all settings labels, descriptions, group titles, and channel card strings now use I18n.t() keys. Added 120+ cfg.* and channels.* keys to both en.js and zh-CN.js. Done in 9885f7e.

…sion-check]

- Use I18n.t() for MCP empty state, export/import toasts, confirm modal
- Fix CLI channel card using wrong channel key ('repl' -> 'cli')
- Fix settings search counting hidden rows as visible
- Add aria-label i18n for settings search input
- Add common.loadFailed i18n key (en + zh-CN)
- Update E2E tests: WASM channel tests use Channels subtab,
  remove tests use custom confirm modal instead of window.confirm

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Review: Unified settings page with subtabs

Ambitious feature PR -- settings page with subtabs, import/export, search, mobile responsive, i18n. The backend changes (ActiveConfigSnapshot, /api/gateway/status) are reasonable. The frontend scope is very large (836 lines added to app.js alone).

Blocking

1. E2E failures (4 tests)

Extension card rendering tests fail because the UI restructuring changed button elements:

  • test_wasm_channel_setup_states -- "Setup" button selector no longer matches
  • test_wasm_channel_pairing_state -- "Reconfigure" button missing
  • test_wasm_channel_active_state -- "Reconfigure" button missing
  • test_wasm_channel_failed_renders -- "Reconfigure" button missing

The ext_configure_btn selector can't find elements with the expected text. The PR's HTML changes likely restructured the extension card buttons. Please update the E2E selectors or restore the expected button structure.

2. Features E2E also failing -- needs investigation.

Non-blocking observations

  • 1704 additions is very large for a single PR. Consider whether the settings import/export, search, mobile responsive layout, and accessibility improvements could be separate follow-up PRs.
  • The main.rs changes add config resolution logic -- this should follow the module-owned initialization rule (config resolution belongs in src/config/, not main.rs).
  • Good i18n coverage (en + zh-CN).

Title check: accurate -- this is a unified settings page with subtabs.

…kip-regression-check]

- WASM channel tests: filter by display name to avoid matching built-in
  channel cards in the Channels subtab
- Skills remove test: click confirm modal button instead of using
  window.confirm (skill removal now uses custom confirm modal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 15, 2026 05:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Unifies the web gateway’s configuration UI by replacing separate Extensions/Skills top-level tabs with a single Settings tab that contains multiple subtabs (Inference, Agent, Channels, Networking, Extensions, MCP, Skills). This is supported by new frontend settings tooling (search, import/export, confirm modal), updated SSE refresh behavior, and a status endpoint enhancement to expose resolved runtime configuration.

Changes:

  • Introduces a new Settings tab layout (sidebar subtabs + toolbar), moving Extensions/MCP/Skills content under Settings.
  • Adds structured settings editors backed by /api/settings/* endpoints, plus import/export and search.
  • Extends /api/gateway/status to include active LLM/channel config snapshot and updates tests/builders accordingly.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Initializes new active_config field in test server state.
tests/support/gateway_workflow_harness.rs Initializes new active_config field in harness-built gateway state.
tests/openai_compat_integration.rs Initializes new active_config field in OpenAI compat integration tests.
tests/e2e/scenarios/test_wasm_lifecycle.py Updates navigation to Settings > Extensions subtab.
tests/e2e/scenarios/test_skills.py Updates navigation to Settings > Skills subtab and confirms modal-based removal flow.
tests/e2e/scenarios/test_extensions.py Refactors E2E tests for Settings subtabs; removes tools-table coverage; adds modal confirmation expectations.
tests/e2e/helpers.py Adds Settings subtab selectors and confirm modal selectors; updates tab list.
src/main.rs Ensures WASM channels directory exists; injects active config snapshot into gateway state.
src/channels/web/ws.rs Updates WS test helper state initialization to include active_config.
src/channels/web/test_helpers.rs Updates test gateway builder state initialization to include active_config.
src/channels/web/static/style.css Adds Settings layout styling, card state styling, toolbar/modal styles; removes tools table styles.
src/channels/web/static/index.html Replaces Extensions/Skills tabs with Settings tab + subtabs and adds confirmation modal markup.
src/channels/web/static/i18n/zh-CN.js Adds Settings-related i18n keys and removes unused tools keys.
src/channels/web/static/i18n/en.js Adds Settings-related i18n keys and removes unused tools keys.
src/channels/web/static/app.js Implements Settings subtabs, settings editor/search/import/export, modal confirmation, 204 handling, and updated SSE refresh logic.
src/channels/web/server.rs Adds ActiveConfigSnapshot to GatewayState and exposes it in /api/gateway/status.
src/channels/web/mod.rs Adds with_active_config() to inject active config snapshot into gateway state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +368 to +369
// Extension setup flows can surface approvals while user is on Settings > Extensions.
if (currentTab === 'settings' && currentSettingsSubtab === 'extensions') loadExtensions();
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — now calls refreshCurrentSettingsTab() for any settings subtab, not just extensions. Done in d63883a.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in d63883a — now calls refreshCurrentSettingsTab() for any settings subtab, not just Extensions.

Comment on lines +4509 to +4513
var html = '<div class="extensions-list">';
for (var i = 0; i < (count || 3); i++) {
html += '<div class="skeleton-card"><div class="skeleton-bar" style="width:60%;height:14px"></div><div class="skeleton-bar" style="width:90%;height:10px"></div><div class="skeleton-bar" style="width:40%;height:10px"></div></div>';
}
html += '</div>';
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — removed the <div class="extensions-list"> wrapper from renderCardsSkeleton(). It now returns only the skeleton card markup, avoiding nested grid issues. Done in d63883a.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in d63883a — removed the <div class="extensions-list"> wrapper. renderCardsSkeleton() now returns only skeleton card markup.

var query = this.value.toLowerCase();
var activePanel = document.querySelector('.settings-subpanel.active');
if (!activePanel) return;
var rows = activePanel.querySelectorAll('.settings-row');
…ion-check]

- approval_needed SSE: refresh any active settings subtab, not just
  Extensions — approvals can surface from Channels/MCP setup flows too
- renderCardsSkeleton: remove nested .extensions-list wrapper that
  caused skeleton cards to render constrained inside grid cells

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ilblackdragon
Copy link
Member Author

Addressing the blocking review from @zmanian:

E2E failures — all resolved:

  • test_wasm_channel_setup_states, test_wasm_channel_pairing_state, test_wasm_channel_active_state, test_wasm_channel_failed_renders — fixed in e110159 (WASM channel cards moved to Channels subtab; updated selectors to filter by display name)
  • test_skills_install_and_remove — fixed in e110159 (skill removal now uses custom confirm modal)

CI is fully green as of e110159. Latest commit d63883a addresses remaining review items (approval_needed subtab refresh + renderCardsSkeleton wrapper).

…ion-check]

Use expect_response to deterministically wait for the /api/extensions
reload triggered by handleAuthCompleted → refreshCurrentSettingsTab,
instead of a fixed 600ms sleep that was too short under CI load.
Also remove stale /api/extensions/tools route handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 15, 2026 07:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates the web gateway’s Extensions and Skills top-level tabs into a single Settings tab with sidebar subtabs, and expands the gateway status/config surface to support the new UI (settings import/export, channels status, and config snapshotting).

Changes:

  • Replaces Extensions/Skills tabs with a unified Settings page (subtabs + toolbar + confirm modal) and updates styling/i18n accordingly.
  • Updates SSE/UI refresh behaviors and adds apiFetch support for HTTP 204 responses (used by settings PUT/DELETE/import).
  • Extends /api/gateway/status to include an active config snapshot (LLM backend/model + enabled channels) and updates Rust + E2E/integration tests for the new state fields and navigation.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Adds active_config to gateway state initialization for tests.
tests/support/gateway_workflow_harness.rs Adds active_config to harness-created gateway state.
tests/openai_compat_integration.rs Adds active_config to test server state initializations.
tests/e2e/scenarios/test_wasm_lifecycle.py Updates navigation to Settings > Extensions subtab.
tests/e2e/scenarios/test_skills.py Updates navigation to Settings > Skills and confirm-modal removal flow.
tests/e2e/scenarios/test_extensions.py Refactors E2E flows for Settings subtabs (Extensions/Channels/MCP) and removes tools-table checks.
tests/e2e/helpers.py Adds selectors for Settings subtabs/panels, confirm modal, channels cards; updates tab list.
src/main.rs Ensures WASM channels directory exists; injects active config snapshot into gateway channel.
src/channels/web/ws.rs Updates test state to include active_config.
src/channels/web/test_helpers.rs Updates test gateway builder state to include active_config.
src/channels/web/static/style.css Adds Settings layout/toolbar/modal styling and refines extension card styling states.
src/channels/web/static/index.html Introduces Settings tab/subpanels and confirm modal; removes Extensions/Skills top-level panels.
src/channels/web/static/i18n/zh-CN.js Adds Settings-related keys; removes unused tools keys; updates Extensions wording.
src/channels/web/static/i18n/en.js Adds Settings-related keys; removes unused tools keys; updates Extensions wording.
src/channels/web/static/app.js Implements Settings subtab loading (settings editor, channels status, MCP separation), confirm modal, 204 handling, and refresh logic.
src/channels/web/server.rs Adds ActiveConfigSnapshot to GatewayState and returns it in /api/gateway/status.
src/channels/web/mod.rs Plumbs active_config through GatewayChannel with with_active_config().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4663 to +4666
cb.checked = !!value;
cb.setAttribute('aria-label', ariaLabel);
cb.addEventListener('change', function() { saveSetting(def.key, cb.checked); });
inputWrap.appendChild(cb);
document.querySelectorAll('.settings-subpanel').forEach(function(p) {
p.classList.toggle('active', p.id === 'settings-' + subtab);
});
loadSettingsSubtab(subtab);
Comment on lines +4837 to +4842
// Built-in Channels section
var builtinSection = document.createElement('div');
builtinSection.className = 'extensions-section';
var builtinTitle = document.createElement('h3');
builtinTitle.textContent = 'Built-in Channels';
builtinSection.appendChild(builtinTitle);
Comment on lines +4938 to +4967
var kindEl = document.createElement('span');
kindEl.className = 'ext-kind kind-builtin';
kindEl.textContent = 'Built-in';
header.appendChild(kindEl);

var statusDot = document.createElement('span');
statusDot.className = 'ext-auth-dot ' + (active ? 'authed' : 'unauthed');
statusDot.title = active ? 'Active' : 'Inactive';
header.appendChild(statusDot);

card.appendChild(header);

var desc = document.createElement('div');
desc.className = 'ext-desc';
desc.textContent = description;
card.appendChild(desc);

if (detail) {
var detailEl = document.createElement('div');
detailEl.className = 'ext-url';
detailEl.textContent = detail;
card.appendChild(detailEl);
}

var actions = document.createElement('div');
actions.className = 'ext-actions';
var label = document.createElement('span');
label.className = 'ext-active-label';
label.textContent = active ? 'Active' : 'Inactive';
actions.appendChild(label);
ilblackdragon and others added 2 commits March 15, 2026 00:25
…p-regression-check]

Inject a counter wrapper around refreshCurrentSettingsTab to verify it's
actually called, and wait for the async fetch to complete before
asserting the reload count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l cards [skip-regression-check]

Move 120+ hardcoded strings in settings definitions (INFERENCE_SETTINGS,
AGENT_SETTINGS, NETWORKING_SETTINGS) and channel card labels to i18n
keys. Render functions now resolve labels via I18n.t() so the settings
page translates when switching locales.

Covers: group titles, setting labels/descriptions, built-in channel
names/descriptions, and the "No settings found" empty state.

Both en.js and zh-CN.js updated with all new cfg.* and channels.* keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 15, 2026 07:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates the web gateway’s Extensions and Skills top-level tabs into a single Settings tab with sidebar subtabs (Inference/Agent/Channels/Networking/Extensions/MCP/Skills), updating the frontend UI, i18n strings, and E2E/integration tests accordingly. It also extends /api/gateway/status to expose an “active config” snapshot used by the new Channels/Inference UIs.

Changes:

  • Replaces Extensions/Skills top-level tabs with a unified Settings tab + subpanels, plus a reusable confirmation modal.
  • Adds ActiveConfigSnapshot to the web gateway state and includes llm_backend, llm_model, enabled_channels in /api/gateway/status.
  • Updates Rust integration tests and Python E2E tests/selectors to match the new navigation and modal flows.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Initializes GatewayState.active_config in test server state.
tests/support/gateway_workflow_harness.rs Initializes GatewayState.active_config in workflow harness state.
tests/openai_compat_integration.rs Initializes GatewayState.active_config in OpenAI compat test servers.
tests/e2e/scenarios/test_wasm_lifecycle.py Updates navigation from Extensions tab to Settings > Extensions subtab.
tests/e2e/scenarios/test_skills.py Updates navigation to Settings > Skills and confirms removal via modal.
tests/e2e/scenarios/test_extensions.py Refactors navigation helpers for Settings subtabs; removes tools-table tests; adapts flows to custom confirm modal and new Channels/MCP subtabs.
tests/e2e/helpers.py Adds selectors for settings subtabs/subpanels, confirm modal, and channels cards; updates tab list.
src/main.rs Ensures WASM channels dir exists; injects ActiveConfigSnapshot into gateway for status endpoint.
src/channels/web/ws.rs Updates test gateway state with active_config.
src/channels/web/test_helpers.rs Updates TestGatewayBuilder to include active_config.
src/channels/web/static/style.css Adds styling for Settings layout, toolbars, skeletons, and confirmation modal; tweaks extension card states.
src/channels/web/static/index.html Replaces Extensions/Skills panels with Settings layout + subpanels; adds confirmation modal markup.
src/channels/web/static/i18n/en.js Adds Settings/Channels/settings-editor strings; removes tools.* strings.
src/channels/web/static/i18n/zh-CN.js Same as en.js for zh-CN locale.
src/channels/web/static/app.js Implements Settings subtab routing, settings editor, channels/mcp loaders, confirm modal, import/export; updates SSE refresh behavior; handles HTTP 204 in apiFetch().
src/channels/web/server.rs Introduces ActiveConfigSnapshot and returns new fields from /api/gateway/status.
src/channels/web/mod.rs Adds GatewayChannel::with_active_config to populate the status snapshot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

container.innerHTML = '';
renderStructuredSettingsInto(container, NETWORKING_SETTINGS, settings, {});
}).catch(function(err) {
container.innerHTML = '<div class="empty-state">Failed to load settings: '
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — all 3 occurrences of hardcoded "Failed to load settings" now use I18n.t('common.loadFailed'). Done in a657ddd.

URL.revokeObjectURL(url);
showToast(I18n.t('settings.exportSuccess'), 'success');
}).catch(function(err) {
showToast(I18n.t('settings.importFailed', { message: err.message }), 'error');
Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch — was using settings.importFailed for export errors. Added settings.exportFailed key and switched the export catch to use it. Done in a657ddd.

Comment on lines +4862 to +4873
builtinList.appendChild(renderBuiltinChannelCard(
I18n.t('channels.cli'),
I18n.t('channels.cliDesc'),
enabledChannels.indexOf('cli') !== -1,
I18n.t('channels.runWith', { cmd: 'ironclaw run --cli' })
));

builtinList.appendChild(renderBuiltinChannelCard(
I18n.t('channels.repl'),
I18n.t('channels.replDesc'),
enabledChannels.indexOf('repl') !== -1,
I18n.t('channels.runWith', { cmd: 'ironclaw run --repl' })
Copy link
Member Author

Choose a reason for hiding this comment

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

The CLI TUI (CliChannel, Ratatui-based) is a separate channel from REPL (ReplChannel, simple stdin). They have distinct channel names — 'cli' is correct here. No change needed.

Comment on lines +4938 to +4967
var kindEl = document.createElement('span');
kindEl.className = 'ext-kind kind-builtin';
kindEl.textContent = 'Built-in';
header.appendChild(kindEl);

var statusDot = document.createElement('span');
statusDot.className = 'ext-auth-dot ' + (active ? 'authed' : 'unauthed');
statusDot.title = active ? 'Active' : 'Inactive';
header.appendChild(statusDot);

card.appendChild(header);

var desc = document.createElement('div');
desc.className = 'ext-desc';
desc.textContent = description;
card.appendChild(desc);

if (detail) {
var detailEl = document.createElement('div');
detailEl.className = 'ext-url';
detailEl.textContent = detail;
card.appendChild(detailEl);
}

var actions = document.createElement('div');
actions.className = 'ext-actions';
var label = document.createElement('span');
label.className = 'ext-active-label';
label.textContent = active ? 'Active' : 'Inactive';
actions.appendChild(label);
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — "Built-in" now uses I18n.t('ext.builtin'), and Active/Inactive labels use I18n.t('ext.active') / I18n.t('ext.inactive'). Done in a657ddd.

Comment on lines +4657 to +4676
var ariaLabel = I18n.t(def.label) + (def.description ? '. ' + I18n.t(def.description) : '');
var placeholderText = activeValue ? 'env: ' + activeValue : (def.placeholder || 'env default');

if (def.type === 'boolean') {
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !!value;
cb.setAttribute('aria-label', ariaLabel);
cb.addEventListener('change', function() { saveSetting(def.key, cb.checked); });
inputWrap.appendChild(cb);
} else if (def.type === 'select' && def.options) {
var sel = document.createElement('select');
sel.className = 'settings-select';
sel.setAttribute('data-setting-key', def.key);
sel.setAttribute('aria-label', ariaLabel);
var emptyOpt = document.createElement('option');
emptyOpt.value = '';
emptyOpt.textContent = activeValue ? '\u2014 env: ' + activeValue + ' \u2014' : '\u2014 use env default \u2014';
if (!value && value !== false && value !== 0) emptyOpt.selected = true;
sel.appendChild(emptyOpt);
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — placeholder strings now use I18n.t('settings.envValue', { value }), I18n.t('settings.envDefault'), and I18n.t('settings.useEnvDefault'). Done in a657ddd.


var saved = document.createElement('span');
saved.className = 'settings-saved-indicator';
saved.textContent = '\u2713 Saved';
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed — now uses I18n.t('settings.saved'). Done in a657ddd.

…n-check]

- Fix export error toast using wrong i18n key (importFailed → exportFailed)
- Replace "Failed to load settings:" with I18n.t('common.loadFailed')
- Localize renderBuiltinChannelCard: "Built-in", "Active", "Inactive"
- Localize settings placeholders: "env: ", "env default", "use env default"
- Localize "✓ Saved" indicator
- Add new i18n keys to both en.js and zh-CN.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates the web gateway UI’s Extensions and Skills into a single Settings tab with sidebar subtabs, adds settings import/export/search, and updates the gateway status API to surface “active config” details needed by the new UI.

Changes:

  • Replaces top-level Extensions/Skills tabs with a unified Settings tab and subtabs (Inference/Agent/Channels/Networking/Extensions/MCP/Skills) plus a reusable confirmation modal.
  • Updates the web gateway frontend to fetch/edit settings via /api/settings/export and refresh correct subtabs on SSE events; adds 204-handling to apiFetch.
  • Extends /api/gateway/status to include llm_backend, llm_model, and enabled_channels, and updates Rust + E2E/integration tests accordingly.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/ws_gateway_integration.rs Updates gateway state initialization for new active_config field.
tests/support/gateway_workflow_harness.rs Adds default active_config to test gateway state.
tests/openai_compat_integration.rs Adds default active_config in integration test servers.
tests/e2e/scenarios/test_wasm_lifecycle.py Updates navigation for Settings > Extensions subtab.
tests/e2e/scenarios/test_skills.py Updates navigation to Settings > Skills and confirm-modal removal flow.
tests/e2e/scenarios/test_extensions.py Refactors tests for new Settings subtabs (Extensions/Channels/MCP) and confirm modal.
tests/e2e/helpers.py Adds selectors for settings subtabs/subpanels and confirm modal; updates tab list.
src/main.rs Ensures WASM channels dir exists; injects ActiveConfigSnapshot into gateway channel.
src/channels/web/ws.rs Updates web WS test state with default active_config.
src/channels/web/test_helpers.rs Updates test gateway builder to include default active_config.
src/channels/web/static/style.css Adds styling for settings layout, toolbar, skeletons, modal, and updated card states.
src/channels/web/static/index.html Replaces Extensions/Skills panels with unified Settings layout + confirm modal markup.
src/channels/web/static/i18n/zh-CN.js Adds new Settings/Channels i18n keys and removes tools-table keys.
src/channels/web/static/i18n/en.js Adds new Settings/Channels i18n keys and removes tools-table keys.
src/channels/web/static/app.js Implements settings subtabs, structured settings editor, search/export/import, confirm modal, and updated SSE refresh logic.
src/channels/web/server.rs Introduces ActiveConfigSnapshot and includes it in /api/gateway/status response.
src/channels/web/mod.rs Adds gateway builder support for with_active_config() and stores snapshot in state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5134 to +5156
function showConfirmModal(title, message, onConfirm, confirmLabel, confirmClass) {
var modal = document.getElementById('confirm-modal');
document.getElementById('confirm-modal-title').textContent = title;
document.getElementById('confirm-modal-message').textContent = message || '';
document.getElementById('confirm-modal-message').style.display = message ? '' : 'none';
var btn = document.getElementById('confirm-modal-btn');
btn.textContent = confirmLabel || I18n.t('btn.confirm');
btn.className = confirmClass || 'btn-danger';
_confirmModalCallback = onConfirm;
modal.style.display = 'flex';
}

function closeConfirmModal() {
document.getElementById('confirm-modal').style.display = 'none';
_confirmModalCallback = null;
}

document.getElementById('confirm-modal-btn').addEventListener('click', function() {
if (_confirmModalCallback) _confirmModalCallback();
closeConfirmModal();
});
document.getElementById('confirm-modal-cancel-btn').addEventListener('click', closeConfirmModal);

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in c59ae34 — added Escape key handler, click-outside-to-dismiss on overlay, and focus on the confirm button when modal opens.

Comment on lines +4862 to +4874
builtinList.appendChild(renderBuiltinChannelCard(
I18n.t('channels.cli'),
I18n.t('channels.cliDesc'),
enabledChannels.indexOf('cli') !== -1,
I18n.t('channels.runWith', { cmd: 'ironclaw run --cli' })
));

builtinList.appendChild(renderBuiltinChannelCard(
I18n.t('channels.repl'),
I18n.t('channels.replDesc'),
enabledChannels.indexOf('repl') !== -1,
I18n.t('channels.runWith', { cmd: 'ironclaw run --repl' })
));
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as prior comment — CLI TUI (CliChannel) and REPL (ReplChannel) are distinct backend channels with separate names. 'cli' is the correct identifier here.

Comment on lines +5206 to +5239
document.getElementById('settings-search-input').addEventListener('input', function() {
var query = this.value.toLowerCase();
var activePanel = document.querySelector('.settings-subpanel.active');
if (!activePanel) return;
var rows = activePanel.querySelectorAll('.settings-row');
var visibleCount = 0;
rows.forEach(function(row) {
var text = row.textContent.toLowerCase();
if (query === '' || text.indexOf(query) !== -1) {
row.classList.remove('search-hidden');
if (!row.classList.contains('hidden')) visibleCount++;
} else {
row.classList.add('search-hidden');
}
});
// Show/hide group titles based on visible children
var groups = activePanel.querySelectorAll('.settings-group');
groups.forEach(function(group) {
var visibleRows = group.querySelectorAll('.settings-row:not(.search-hidden):not(.hidden)');
if (visibleRows.length === 0 && query !== '') {
group.style.display = 'none';
} else {
group.style.display = '';
}
});
// Show/hide empty state
var existingEmpty = activePanel.querySelector('.settings-search-empty');
if (existingEmpty) existingEmpty.remove();
if (query !== '' && visibleCount === 0) {
var empty = document.createElement('div');
empty.className = 'settings-search-empty';
empty.textContent = I18n.t('settings.noMatchingSettings', { query: this.value });
activePanel.appendChild(empty);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in c59ae34 — search handler now returns early if the active panel has no .settings-row elements, avoiding the spurious empty-state on Extensions/MCP/Skills/Channels subtabs.

Comment on lines +384 to +394
<!-- Confirmation Modal -->
<div id="confirm-modal" class="modal-overlay" style="display:none">
<div class="modal">
<h3 id="confirm-modal-title"></h3>
<p id="confirm-modal-message"></p>
<div class="modal-actions">
<button id="confirm-modal-cancel-btn" class="btn-secondary" data-i18n="btn.cancel">Cancel</button>
<button id="confirm-modal-btn" class="btn-danger">Confirm</button>
</div>
</div>
</div>
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in c59ae34 — added role="dialog", aria-modal="true", aria-labelledby="confirm-modal-title" to the modal overlay, and focus is set on the confirm button when the modal opens.

…ip-regression-check]

- Add role="dialog", aria-modal="true", aria-labelledby to confirm modal
- Focus confirm button when modal opens
- Close modal on Escape key or overlay click
- Skip settings search on non-settings panels (Extensions/MCP/Skills/Channels)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Large frontend PR (3400+ lines) that replaces separate Extensions/Skills tabs with a unified Settings page. CI is green with all checks passing.

Given this is primarily frontend HTML/CSS/JS changes in the web gateway, a few observations:

1. PR scope is very large

This touches settings API integration, channels UI, extension cards, MCP servers, confirmation modals, SSE handlers, i18n, and removes the tools debug section -- all in one PR. Consider whether any of these could be split out (e.g., the confirmation modal, the 204 handling fix, the i18n changes) to make review more tractable.

2. Title accuracy

Title says "unified settings page with subtabs" which accurately describes the change. Good.

3. Test coverage

The test plan is manual-only (checkboxes). For a UI refactor of this size, E2E tests covering the basic navigation flow (open settings, click through subtabs, verify content loads) would provide meaningful regression protection.

4. Removed "Registered Tools" debug section

The PR description mentions removing the tools table. Confirm this doesn't break any E2E tests that depend on /api/extensions/tools endpoint.

This needs a thorough manual review of the frontend behavior. I'll defer final judgment to a reviewer who can test the UI interactively. The Rust-side changes appear minimal (likely just the 204 handling in apiFetch).

Copy link
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Re-review: unified settings page with subtabs

Both blocking items from my previous review have been resolved:

  1. E2E failures (4 tests) -- Fixed in commits f8ffd9e and e110159. Selectors updated from tab_button(extensions) to tab_button(settings) + settings_subtab(extensions). Skills remove confirm also fixed.

  2. Features E2E -- All E2E tests now passing (core, extensions, features all green).

Additional improvements since last review:

  • Full i18n for settings labels, descriptions, and channel cards (9885f7e, a657ddd)
  • Confirm modal a11y: role="dialog", aria-modal="true", Esc/click-outside dismiss (c59ae34)
  • Search guard for empty row sets
  • Cancel button added to confirm modal with CSP-compliant event listener

CI fully green including E2E tests and all Clippy variants.

Non-blocking notes (unchanged from last review):

  • Config resolution logic in main.rs should eventually move to src/config/ per module-owned initialization rule
  • [skip-regression-check] used on most commits -- acceptable for frontend-only changes

LGTM.

Copy link
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Withdrawing approval -- code looks good but the settings page redesign needs manual user testing before merge (subtab navigation, import/export, search, mobile layout). Leaving as comment review until testing is complete.

Copy link
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Pending manual user testing (see previous comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: channel/web Web gateway channel size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants