Skip to content

fix dashboard manage actions to use visible row numbers#91

Open
ndycode wants to merge 2 commits intomainfrom
git-plan/20-dashboard-visible-row-semantics
Open

fix dashboard manage actions to use visible row numbers#91
ndycode wants to merge 2 commits intomainfrom
git-plan/20-dashboard-visible-row-semantics

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 14, 2026

What:

  • preserve the visible dashboard row number separately from the saved/source account index in interactive manage actions
  • use the visible row number in switch, delete, toggle, refresh, and stale manage-switch error messages while keeping direct codex auth switch [index] behavior on saved-index semantics
  • add regressions for reordered dashboard rows, including the row 1 -> saved index 29 switch case and a stale-state error case

Why:

  • auto-arranged account lists can reorder the dashboard, and the CLI was leaking saved indexes back into user-facing manage confirmations

Risk:

  • low - this only changes interactive manage-mode plumbing and focused CLI regressions; direct switch commands still use saved indexes

Validation:

  • npm test -- test/cli-auth-menu.test.ts test/auth-menu-hotkeys.test.ts test/codex-manager-cli.test.ts
  • npm test -- test/codex-manager-cli.test.ts
  • npx eslint lib/cli.ts lib/codex-manager.ts test/cli-auth-menu.test.ts test/codex-manager-cli.test.ts
  • npx eslint lib/codex-manager.ts test/codex-manager-cli.test.ts
  • git diff --check
  • npm run typecheck currently fails on untouched repo-baseline issues in index.ts, lib/storage.ts, and @codex-ai/* type resolution

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this PR fixes a user-facing bug where interactive manage-mode confirmations (switch, delete, toggle, refresh) were showing saved storage indexes instead of the visible dashboard row numbers after auto-reordering. the fix threads a selectedAccountNumber field through LoginMenuResult, resolves it in buildManageResult/resolveAccountDisplayNumber, and passes it as displayAccountNumber into runSwitch and all other action handlers. direct codex auth switch <index> calls are unchanged.

  • lib/cli.ts: adds resolveAccountDisplayNumber (prefers quickSwitchNumber, falls back to index+1) and buildManageResult wrapper; all eight manage-action return sites updated
  • lib/codex-manager.ts: adds resolveManageActionAccountNumber helper and RunSwitchOptions; all confirmation console.log/console.error/console.warn messages now use displayAccountNumber; formatAccountLabel is also passed displayAccountNumber - 1, making the label number consistent with the prefix but causing the account number to appear redundantly in the output string
  • test/cli-auth-menu.test.ts: existing tests updated with selectedAccountNumber assertions; new regression for quickSwitchNumber: 1 / sourceIndex: 28 (visible row 1 → saved index 29) added
  • test/codex-manager-cli.test.ts: new switch/stale-switch/delete/toggle/refresh regressions added; the stale-switch test uses a loadCall === 3 hard-coded counter which is fragile; toggle re-enable path from a reordered row is not covered
  • no new windows filesystem concurrency protection is added — delete and toggle still do an in-memory splice/mutation followed by saveAccounts without a write lock, which is a pre-existing race on windows; no token data flows through the new selectedAccountNumber field so there is no token leakage risk

Confidence Score: 4/5

  • safe to merge with minor test-reliability fix recommended before the stale-switch test can be fully trusted
  • the core logic change is correct and well-tested for the happy path; resolveAccountDisplayNumber priority ordering is sound and all eight action return sites are updated consistently. the only actionable concern is the brittle loadCall === 3 counter in the stale-switch test which could silently mis-target a future refactor, and missing toggle re-enable coverage for reordered rows. no token leakage, no new windows concurrency risk introduced.
  • test/codex-manager-cli.test.ts — stale-switch loadCall counter and missing toggle re-enable path

Important Files Changed

Filename Overview
lib/cli.ts adds resolveAccountDisplayNumber and buildManageResult helpers; threads selectedAccountNumber through all manage action returns. logic is clean, priority of quickSwitchNumber over index+1 is correct, and defensive Math.max(1,…) guards are appropriate.
lib/codex-manager.ts adds resolveManageActionAccountNumber and RunSwitchOptions; propagates displayAccountNumber through all confirmation messages. formatAccountLabel(account, displayAccountNumber - 1) makes the label number consistent with the prefix, though it produces a redundant number in output. no new windows filesystem concurrency concerns introduced, but delete/toggle still lack a write lock around splice+save.
test/cli-auth-menu.test.ts adds selectedAccountNumber assertions to all affected existing tests and a new regression for the quickSwitchNumber: 1 / sourceIndex: 28 case. coverage is solid for the promptLoginMode surface.
test/codex-manager-cli.test.ts adds switch, stale-switch, delete, toggle, and refresh regressions for reordered rows. the stale-switch test uses a brittle loadCall === 3 counter that will silently mis-target if call order changes. toggle re-enable path from a reordered row is not covered.

Sequence Diagram

sequenceDiagram
    participant UI as auth-menu UI
    participant CLI as cli.ts (promptLoginMode)
    participant MGR as codex-manager.ts (handleManageAction)
    participant SW as runSwitch()
    participant FS as saveAccounts / loadAccounts

    UI->>CLI: action (account with index/sourceIndex/quickSwitchNumber)
    CLI->>CLI: resolveAccountSourceIndex(account) → savedIndex
    CLI->>CLI: resolveAccountDisplayNumber(account) → displayNumber
    CLI->>CLI: buildManageResult → { switchAccountIndex: savedIndex, selectedAccountNumber: displayNumber }
    CLI-->>MGR: LoginMenuResult

    MGR->>MGR: resolveManageActionAccountNumber(result, savedIndex) → displayAccountNumber
    alt switch action
        MGR->>SW: runSwitch([String(savedIndex+1)], { displayAccountNumber })
        SW->>FS: loadAccounts()
        FS-->>SW: storage
        SW->>FS: saveAccounts(storage)
        SW-->>MGR: console.log("Switched to account {displayAccountNumber}: …")
    else delete action
        MGR->>FS: storage.accounts.splice(savedIndex, 1)
        MGR->>FS: saveAccounts(storage)
        MGR-->>MGR: console.log("Deleted account {displayAccountNumber}.")
    else toggle action
        MGR->>FS: account.enabled = !account.enabled
        MGR->>FS: saveAccounts(storage)
        MGR-->>MGR: console.log("Enabled/Disabled account {displayAccountNumber}.")
    else refresh action
        MGR->>MGR: runOAuthFlow()
        MGR->>FS: persistAccountPool / syncSelectionToCodex
        MGR-->>MGR: console.log("Refreshed account {displayAccountNumber}.")
    end
Loading

Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: test/codex-manager-cli.test.ts
Line: 1345-1357

Comment:
**`loadCall === 3` is brittle and can mask bugs**

the stale-accounts test hard-codes `loadCall === 3` to determine when to return stale storage, but the exact call count depends on implementation details inside `runCodexMultiAuthCli`. if the number of `loadAccounts` calls in the flow changes (e.g., an extra check is added), the stale data will be injected at the wrong point — the test could pass while testing a completely different scenario than intended.

use a flag that is set explicitly after the promptLoginMode mock fires the first time, so stale storage is returned on any call that follows the switch decision:

```
let returnStale = false;
loadAccountsMock.mockImplementation(async () => {
  if (returnStale) {
    return { version: 3, activeIndex: 0, activeIndexByFamily: { codex: 0 }, accounts: structuredClone(staleAccounts) };
  }
  return structuredClone(storage);
});
promptLoginModeMock
  .mockImplementationOnce(async () => {
    returnStale = true;
    return { mode: "manage", switchAccountIndex: 28, selectedAccountNumber: 1 };
  })
  .mockResolvedValueOnce({ mode: "cancel" });
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: test/codex-manager-cli.test.ts
Line: 3310-3340

Comment:
**missing coverage: toggle re-enable path from reordered row**

the updated toggle test only covers the disable path (enabled → disabled). there's no vitest case for toggling a currently-disabled reordered account back to enabled, which exercises the `account.enabled === false → true` branch. since this PR's goal is to validate manage actions with reordered rows, the re-enable direction should also be covered to catch regressions.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: lib/codex-manager.ts
Line: 4087-4089

Comment:
**`formatAccountLabel` embeds the display number twice**

`formatAccountLabel(account, displayAccountNumber - 1)` produces `Account ${displayAccountNumber} (email)`, so the final string is:

`"Switched to account 1: Account 1 (account-29@example.com)"`

the account number appears in both the prefix and the label. this was already the case before the PR for the non-reordered path, but given the label already carries full account identity info (email + id suffix), it may be worth passing `targetIndex` to `formatAccountLabel` so the label reflects the saved-storage position for audit/debug logs, while the leading prefix uses `displayAccountNumber` for user-facing clarity:

```suggestion
		`Switched to account ${displayAccountNumber}: ${formatAccountLabel(account, targetIndex)}${wasDisabled ? " (re-enabled)" : ""}`,
```

no behaviour change from a user's POV on the displayed *number* (`Switched to account 1:`), but the label section would accurately identify the account by its storage position, which is valuable on Windows where audit logs are the main concurrency diagnostic tool.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: d9556ab

Greptile also left 1 inline comment on this PR.

Context used:

  • Rule used - What: Every code change must explain how it defend... (source)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • skip-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b822ce51-a796-4a48-8ee1-dab48527e50a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch git-plan/20-dashboard-visible-row-semantics
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Comment on lines +1345 to +1357
activeIndexByFamily: { codex: 0 },
accounts: Array.from({ length: 29 }, (_, index) => ({
email: `account-${index + 1}@example.com`,
accountId: `acc_${index + 1}`,
refreshToken: `refresh-${index + 1}`,
accessToken: `access-${index + 1}`,
expiresAt: now + 3_600_000,
addedAt: now - 1_000 - index,
lastUsed: now - 1_000 - index,
enabled: true,
})),
};
const staleAccounts = storage.accounts.slice(0, 28);
Copy link

Choose a reason for hiding this comment

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

loadCall === 3 is brittle and can mask bugs

the stale-accounts test hard-codes loadCall === 3 to determine when to return stale storage, but the exact call count depends on implementation details inside runCodexMultiAuthCli. if the number of loadAccounts calls in the flow changes (e.g., an extra check is added), the stale data will be injected at the wrong point — the test could pass while testing a completely different scenario than intended.

use a flag that is set explicitly after the promptLoginMode mock fires the first time, so stale storage is returned on any call that follows the switch decision:

let returnStale = false;
loadAccountsMock.mockImplementation(async () => {
  if (returnStale) {
    return { version: 3, activeIndex: 0, activeIndexByFamily: { codex: 0 }, accounts: structuredClone(staleAccounts) };
  }
  return structuredClone(storage);
});
promptLoginModeMock
  .mockImplementationOnce(async () => {
    returnStale = true;
    return { mode: "manage", switchAccountIndex: 28, selectedAccountNumber: 1 };
  })
  .mockResolvedValueOnce({ mode: "cancel" });
Prompt To Fix With AI
This is a comment left during a code review.
Path: test/codex-manager-cli.test.ts
Line: 1345-1357

Comment:
**`loadCall === 3` is brittle and can mask bugs**

the stale-accounts test hard-codes `loadCall === 3` to determine when to return stale storage, but the exact call count depends on implementation details inside `runCodexMultiAuthCli`. if the number of `loadAccounts` calls in the flow changes (e.g., an extra check is added), the stale data will be injected at the wrong point — the test could pass while testing a completely different scenario than intended.

use a flag that is set explicitly after the promptLoginMode mock fires the first time, so stale storage is returned on any call that follows the switch decision:

```
let returnStale = false;
loadAccountsMock.mockImplementation(async () => {
  if (returnStale) {
    return { version: 3, activeIndex: 0, activeIndexByFamily: { codex: 0 }, accounts: structuredClone(staleAccounts) };
  }
  return structuredClone(storage);
});
promptLoginModeMock
  .mockImplementationOnce(async () => {
    returnStale = true;
    return { mode: "manage", switchAccountIndex: 28, selectedAccountNumber: 1 };
  })
  .mockResolvedValueOnce({ mode: "cancel" });
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

@ndycode ndycode added the enhancement New feature or request label Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant