Skip to content

fix(web-studio): address #2309 review feedback + edge-case polish#2313

Open
ZaynJarvis wants to merge 3 commits into
volcengine:mainfrom
ZaynJarvis:alice/2309-settings-polish
Open

fix(web-studio): address #2309 review feedback + edge-case polish#2313
ZaynJarvis wants to merge 3 commits into
volcengine:mainfrom
ZaynJarvis:alice/2309-settings-polish

Conversation

@ZaynJarvis
Copy link
Copy Markdown
Collaborator

@ZaynJarvis ZaynJarvis commented May 29, 2026

Description

Follow-up PR for #2309. Originally just edge-case polish for the settings page; expanded to address the two blocking review comments from @qin-ctx on #2309 (auth-boundary regressions) plus the qodo bot's totalUsers bug.

Related Issue

Follow-up to #2309. Addresses inline review comments on:

  • web-studio/src/hooks/use-app-connection.tsx#L36 — non-default keys getting default/default headers
  • web-studio/src/routes/settings/route.tsx#L687 — account-admin keys locked out by root-only accountsQuery
  • web-studio/src/routes/settings/route.tsx#R682-R684 (qodo) — totalUsers || managedUsers.length masks legitimate 0

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test update

Changes Made

Blocker fixes (from @qin-ctx)

  1. Restore empty transport defaults. DEFAULT_CONNECTION had accountId: 'default' and userId: 'default', which made ovClient send X-OpenViking-Account: default / X-OpenViking-User: default headers on every request. Any user with a non-default API key (e.g. acme/alice) would get those headers overriding their key's identity and the server would reject the call before the user even chose anything. Reverted to empty strings; the settings UI still shows default as a display fallback via value || DEFAULT_X, so the visual behavior is unchanged.

  2. Decouple account-admin flow from root-only GET /admin/accounts. Previously accountsQuery.isError fed into adminUnavailable, so an account-admin key (whose root-only call returns 403) saw the empty Admin access required state and couldn't manage users in their own account. New isRootRestricted = accountsQuery.isError flag splits the two cases:

    • Connection card account field and management filter both render a free-text <Input> (instead of a <Select>) when isRootRestricted, so the user can type their accountId.
    • "Add account" button is disabled when isRootRestricted (the underlying POST /admin/accounts is root-only). "Add user" and "Regenerate" stay enabled — they're per-account and account-admins can use them on their own account.
    • New effect syncs managedAccountId to whatever the user types in the connection card when isRootRestricted, so an account admin doesn't have to enter the same accountId twice.
  3. Fix totalUsers fallback. accountOptions.reduce(...) || managedUsers.length would mask a legitimate sum of 0 from the reduce with the managed users count. Switched to a ternary so the fallback only kicks in when there's no visible account list at all.

Edge-case polish (original scope)

  1. Clipboard errors no longer disappear. navigator.clipboard.writeText can reject (permission denied, insecure context, locked-down browser). Both copyKey callers were await-ing the call and then immediately toast.success, but in the table the click handler used () => void copyKey(user.apiKey) which discarded the rejection — the user saw nothing. Both call sites now try/catch and emit toast.error(t('toast.copyFailed', { message })).

  2. Trim account / user IDs before submitting the dialogs. AddAccountDialog and AddUserDialog used to pass draft straight to onCreate, so leading/trailing whitespace would have been persisted as part of account_id / user_id.

  3. AddUserDialog reset effect: only fire on closed→open. Now uses a prevOpenRef so a parent re-render with a new managedAccountId while the dialog is open no longer wipes whatever the user already typed. defaultAccountId stays in the deps to satisfy react-hooks/exhaustive-deps if/when it's ever enabled.

  4. Robust serverMode label. t(\serverMode.${serverMode}`)now passesdefaultValue: serverModeso a future mode (e.g.oauth`) renders as the mode key instead of the raw translation path.

  5. i18n strings. Added settings.toast.copyFailed for both en and zh-CN to back the new error toast.

Testing

  • Added tests (zayn confirmed UT is out of scope for this frontend-integration PR)
  • Existing checks pass locally
  • macOS

Commands run on this iMac (darwin-arm64, Node 22.22.0, pnpm 10.33.0):

  • node_modules/.bin/tsc --noEmit — clean
  • pnpm run lint — 0 errors, 15 warnings (the same 15 pre-existing i18next/no-literal-string warnings in src/routes/resources/-components/file-preview.tsx flagged in feat: add web studio account user management #2309)
  • pnpm run build — green
  • Playwright smoke at http://localhost:3000/settings:
    • Empty state renders cleanly; H1 Connection & Identity, both account/user dropdowns show default fallback, all stats show -.
    • With a fake API key (forces accountsQuery to 404 → isRootRestricted=true): Account field becomes a typeable <Input> with team-account placeholder, management filter becomes a typeable <Input>, "Add account" is correctly disabled, "Add user" stays enabled.

I did not run live admin mutations — these fixes are pure client-side identity routing and UI gating and don't change request shapes.

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • My code follows the repo convention of putting "why" in commit messages rather than inline comments
  • My changes generate no new warnings

…follow-up)

- Wrap navigator.clipboard.writeText calls in try/catch so a denied
  permission or insecure-context failure surfaces a toast instead of
  silently disappearing (`void copyKey(...)` was swallowing rejections).
- Trim accountId / userId / adminUserId on dialog submit so users
  can't accidentally create entities with leading/trailing whitespace.
- AddUserDialog: drop defaultAccountId from the reset effect deps so
  a parent re-rendering with a new managedAccountId while the dialog
  is open no longer wipes whatever the user already typed.
- Add a defaultValue fallback to the serverMode badge translation so
  an unknown mode renders the mode key instead of the raw i18n path.
- Add settings.toast.copyFailed strings (en, zh-CN) for the new error.
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

2309 - PR Code Verified

Compliant requirements:

  • Clipboard errors now handled with try/catch and error toast
  • Account/user IDs trimmed before submission
  • AddUserDialog reset effect fixed
  • Server mode label uses translation fallback

Requires further human verification:

  • Live create/regenerate mutations against admin service
  • Cross-browser testing (Windows, Linux)
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🏅 Score: 95
🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ No major issues detected

@github-actions
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Restore missing useEffect dependency

The useEffect hook uses defaultAccountId but omits it from the dependency array.
This can cause stale accountId values in the draft if defaultAccountId changes while
the dialog is open. Add defaultAccountId back to the dependency array to ensure the
draft updates correctly.

web-studio/src/routes/settings/route.tsx [420-428]

 React.useEffect(() => {
     if (open) {
       setDraft({
         accountId: defaultAccountId || DEFAULT_ACCOUNT_ID,
         role: 'user',
         userId: '',
       })
     }
-  }, [open])
+  }, [defaultAccountId, open])
Suggestion importance[1-10]: 7

__

Why: The useEffect uses defaultAccountId but omits it from the dependency array, risking stale values. Restoring it ensures the draft updates correctly if defaultAccountId changes.

Medium

…only

Qodo flagged the missing defaultAccountId dep — restoring it directly
would re-introduce the input-wipe bug the previous commit was fixing
(parent re-render with new managedAccountId blanks the draft mid-typing).

Use a prevOpenRef so the reset only fires when `open` transitions
false→true. defaultAccountId stays in the deps to satisfy
exhaustive-deps, but a change to it while the dialog is already open
no longer triggers a reset.
…ckers)

1. Revert transport-layer connection defaults to empty (was `default`
   / `default`). The earlier defaults caused non-root keys
   (`acme/alice`) to be rejected because ovClient was sending
   `X-OpenViking-Account: default` / `X-OpenViking-User: default`
   headers that override the key's identity. The settings UI still
   shows `default` as a display fallback via `value || DEFAULT_X`.

2. Decouple `adminUnavailable` from `accountsQuery` so an
   account-admin key (whose `GET /admin/accounts` returns 403) can
   still list/regenerate/create users in its own account.
   - New `isRootRestricted = accountsQuery.isError` flag.
   - Connection card and management filter both fall back to a free-
     text `<Input>` (instead of a dropdown) when `isRootRestricted`,
     so the user can type their accountId.
   - "Add account" button is disabled when `isRootRestricted`
     (POST /admin/accounts is root-only); "Add user" and "Regenerate"
     stay enabled (per-account, allowed for account admins).
   - New effect syncs `managedAccountId` to whatever the user types
     in the connection card when `isRootRestricted`, so they don't
     have to enter it twice.

3. Fix `totalUsers` fallback: `reduce(...) || managedUsers.length`
   would mask a legitimate 0 from the reduce with the managed users
   count. Switched to `accountOptions.length ? reduce : managedUsers`
   so an account list with 0 users is shown as 0 (and the fallback
   only kicks in when there's no visible account list at all).
@ZaynJarvis ZaynJarvis changed the title fix(web-studio): polish settings page edge cases (PR #2309 follow-up) fix(web-studio): address #2309 review feedback + edge-case polish May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant