AuthFilesPage and ConfigPage duplicated the ResizeObserver-to-CSS-variable wiring for their bottom action bars; extract useActionBarHeightVar and use it in both.
Both edit pages re-implemented getTypeLabel locally although the authFiles constants module already exports it (with provider-key normalization on top).
getNextDirtyFields already had an updateScalarDirty list for twenty fields but spelled out twenty-seven more as identical hasOwnProperty/=== blocks. Fold them into the list; payload-rule and nested streaming comparisons keep their custom equality checks.
The dashboard fired eight requests on every visit although seven of them (management keys, per-provider key lists, ampcode) are already present on the normalized config it subscribes to. Keep only the auth-files fetch and read everything else from the store; fetchConfig() is deduped and TTL-cached.
DashboardPage and SystemPage carried byte-identical copies of normalizeApiKeyList plus a per-connection cache for resolving the key used by /models probes. Move the logic into useApiKeysForModels and reuse it from both pages.
The workbench page reached back into resource.raw with brand-specific parsing to derive model names and priority, bypassing the adapter layer. Normalize both in the adapters and consume the typed fields.
normalizeOpenAIBaseUrl, normalizeClaudeBaseUrl and normalizeGeminiBaseUrl were identical except for the empty-input fallback; collapse them into one helper with a fallback parameter.
isRecord was declared locally in 15 modules (with two divergent shapes) and getErrorMessage in 7. Move a single canonical pair into utils/helpers and import it everywhere. The shared isRecord excludes arrays; the call sites that previously allowed them only read named properties, so behavior is unchanged.
Sorting is computed in a useMemo over already-loaded files; changing the sort mode does not need a network round-trip. Also drop a duplicated property read in the priority comparator.
The finally block read autoLoginSuccess from the mount-time closure, so the condition was always true and the guard never did anything. The splash stays visible through the autoLoginSuccess flag.
clear()/hasItem() and the secureStorage/encryptData/decryptData/isEncrypted aliases had no callers. migratePlaintextKeys now reuses isObfuscated instead of duplicating the prefix literal.
/ai-providers/* now redirects to the single workbench page, so the per-brand route ordering and the in-section slide variant can never apply. Generic prefix matching already covers the redirect flash.
snapshot.issues was always empty and group.issue always null, so the issue badges and warning panels could never render. group.path / PROVIDER_PATHS were written but never read. Strip the fields, their render branches and the orphan styles.
updateGeminiKey, updateCodexConfig, updateClaudeConfig, updateVertexConfig and updateOpenAIProvider had no callers; the workbench saves whole lists via PUT and only updateOpenAIProviderDisabled is used for toggling.
The quota page fetched /config.yaml on mount and on every header refresh but ignored the response; quota sections never consume it. Remove the fetch to save a request per visit.
- Implemented PluginStorePage component for displaying available plugins.
- Added functionality to install and update plugins with user confirmation.
- Integrated plugin store API for fetching plugin data and handling installations.
- Enhanced PluginsPage to navigate to the new Plugin Store.
- Updated localization files for new plugin store strings in English, Russian, and Chinese.
- Added new types for plugin store entries and responses in TypeScript.
- Improved UI components and styles for better user experience in the plugin store.
- Introduced new plugin management section in the application.
- Added translations for English, Russian, Simplified Chinese, and Traditional Chinese.
- Created new API endpoints for managing plugins, including listing, enabling/disabling, and configuring plugins.
- Updated routing to include a dedicated PluginsPage.
- Defined new types for plugin configuration and metadata in TypeScript.
- Enhanced the existing API client to handle plugin-related requests.
feat(authFiles): refactor OAuth provider handling and improve alias validation
feat(config): implement unsaved changes guard in ConfigPage
feat(dashboard): extend provider stats to include vertex and ampcode
fix(logs): enhance incremental log merging and improve error handling
feat(system): format build time display using utility functions
feat(api): normalize OAuth provider keys in authFiles API
feat(provider): update OpenAI provider deletion method to use index
feat(format): add date formatting utilities for better date handling
- Refactored MainLayout to always show logs in the sidebar.
- Updated i18n files for English, Russian, Simplified Chinese, and Traditional Chinese to include new log-related messages.
- Improved LogsPage styling for better layout and added runtime notices.
- Enhanced LogsPage logic to handle different server runtime kinds (CPA and Home).
- Added new API methods to handle logs fetching and request log downloading with runtime checks.
- Introduced runtime kind detection during authentication and connection status updates.
- Updated types to include server runtime kind and adjusted API responses accordingly.
The backend OpenAICompatibilityAPIKey struct only has api-key and
proxy-url (internal/config/config.go:570-577). PUT /openai-compatibility
unmarshals into this struct (config_lists.go:439) and discards any
unknown field, so per-entry headers submitted from the UI never persist
and never reach the runtime request path. The GET response wrapper
also has no headers field (config_auth_index.go:31-34).
Drop the per-entry headers input from BaseProviderForm and the
ApiKeyEntry/ApiKeyEntryInput types. Connectivity test and model
discovery stop reading entry-level headers — they continue to apply
provider-level headers, which is the supported contract.
Backend's POST /auth-files and DELETE /auth-files single-item paths
return only {status:"ok"}, with no uploaded/deleted/files
(auth_files.go:680 and :794). Multi-item paths return the full payload.
85c8b34 simplified the normalizer assuming the full payload was always
present, which caused single-file uploads and single-item deletes to be
read as "0 succeeded" — the upload page skipped its success toast and
list refresh, and batch delete reported "(0)" with no row removal.
Re-introduce a narrow fallback: when failed is empty and the count
field is omitted, derive uploaded/deleted and files from requestedNames.
The fallback only kicks in for the documented single-item shape, not
for the partial/failure paths.
All bool-tagged config fields on the backend are Go bools and serialize
as JSON true/false (e.g. Debug, RequestLog, WebsocketAuth, Disabled,
Websockets, ForceModelPrefix). normalizeBoolean is only called against
those response paths, so the number / string / Boolean(value) fallbacks
never fire.
Backend batch upload/delete handlers always return a complete payload:
status (string), uploaded/deleted (number), files (string array), and
failed (only on partial). See auth_files.go:702-711. The fallback chains
that re-derived uploaded/deleted counts and file names from the
requestedNames list were unreachable.
Likewise, runtime_only is always a real bool from Go (auth_files.go:403),
and entry.modified is never emitted, so isRuntimeOnlyEntry collapses
to a strict equality check and readDateField drops the modified alias.
The backend api-call response schema (api_tools.go:54-56) fixes the
field names to status_code / header / body. camelCase statusCode and
plural headers are never emitted.
Backend list endpoints (config_lists.go: GetGeminiKeys / GetClaudeKeys /
GetCodexKeys / GetOpenAICompat / GetVertexCompatKeys) always wrap the
result as {"<kebab-section>": [...]}, never as a raw array, never under
"items" or "data". And /config emits the same kebab keys, so the camelCase
aliases (geminiApiKey, openAICompatibility, ...) are unreachable.
Remove RAW_SECTION_ALIASES and inline the lookup; drop the raw-array
and items/data fallbacks from extractArrayPayload.
The backend emits provider config with kebab-case JSON tags only
(internal/config/config.go: ClaudeKey/CodexKey/GeminiKey/OpenAICompatibility,
internal/api/handlers/management/config_auth_index.go for auth-index).
The camelCase / snake_case entries in PROVIDER_KEY_FIELDS,
OPENAI_PROVIDER_FIELDS, MODEL_ALIAS_FIELDS, API_KEY_ENTRY_FIELDS,
CLOAK_FIELDS, RESPONSE_ONLY_FIELDS were dead — same for the identity
helpers and the apiKeyEntries fallback in mergeOpenAIProviderPayload.
The backend always serializes config fields with kebab-case JSON tags
(internal/config/config.go). The camelCase and snake_case fallbacks in
normalizeApiKeyEntry / normalizeProviderKeyConfig / normalizeGeminiKeyConfig /
normalizeOpenAIProvider / normalizeAmpcode* / normalizeConfigResponse are
dead paths. Read kebab-case only.
The legacy openai-compatibility `api-keys` (flat string array) flat-string
branch is also gone — backend startup migration is disabled
(internal/config/config.go:657).