diff --git a/docs/superpowers/plans/2026-04-27-account-bulk-edit-scope-and-compact.md b/docs/superpowers/plans/2026-04-27-account-bulk-edit-scope-and-compact.md new file mode 100644 index 00000000..42b76664 --- /dev/null +++ b/docs/superpowers/plans/2026-04-27-account-bulk-edit-scope-and-compact.md @@ -0,0 +1,359 @@ +# Account Bulk Edit Scope And Compact Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add filter-result bulk edit to admin accounts, unify the table-level bulk-edit entry, and align OpenAI bulk-edit controls with the existing compact-related single-account settings. + +**Architecture:** Extend the existing `/admin/accounts/bulk-update` flow to accept either explicit account IDs or a server-resolved filter target. Reuse the current account-list filter contract for scope resolution, then update the accounts view and bulk-edit modal so the UI can launch either selected-account edits or current-filter-result edits from one compact dropdown. Keep the existing bulk-edit form, but expand its target contract and OpenAI-specific field coverage. + +**Tech Stack:** Vue 3, TypeScript, Vitest, Gin, Go service/repository layer, existing admin accounts API. + +--- + +### Task 1: Add backend test coverage for filter-target bulk update + +**Files:** +- Modify: `backend/internal/handler/admin/account_handler_mixed_channel_test.go` +- Modify: `backend/internal/service/admin_service_bulk_update_test.go` +- Test: `backend/internal/handler/admin/account_handler_mixed_channel_test.go` +- Test: `backend/internal/service/admin_service_bulk_update_test.go` + +- [ ] **Step 1: Write the failing handler test for filter-target request acceptance** + +```go +func TestBulkUpdateAcceptsFilterTargetRequest(t *testing.T) { + // add a request body that omits account_ids and submits filters instead + // assert the route does not reject the request as malformed once service stubs are wired +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go test ./backend/internal/handler/admin -run TestBulkUpdateAcceptsFilterTargetRequest -count=1` +Expected: FAIL because `BulkUpdateAccountsRequest` does not yet support `filters`. + +- [ ] **Step 3: Write the failing service test for resolving IDs from filters** + +```go +func TestAdminServiceBulkUpdateAccounts_ResolvesIDsFromFilters(t *testing.T) { + // construct BulkUpdateAccountsInput with Filters and no AccountIDs + // stub repository list/search path to return matching IDs + // assert BulkUpdate is called with all matching account IDs +} +``` + +- [ ] **Step 4: Run test to verify it fails** + +Run: `GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go test ./backend/internal/service -run TestAdminServiceBulkUpdateAccounts_ResolvesIDsFromFilters -count=1` +Expected: FAIL because `BulkUpdateAccountsInput` and service logic only use explicit `AccountIDs`. + +- [ ] **Step 5: Commit** + +```bash +git add backend/internal/handler/admin/account_handler_mixed_channel_test.go backend/internal/service/admin_service_bulk_update_test.go +git commit -m "test: cover filter-target account bulk update" +``` + +### Task 2: Implement backend filter-target bulk update + +**Files:** +- Modify: `backend/internal/handler/admin/account_handler.go` +- Modify: `backend/internal/service/admin_service.go` +- Modify: `backend/internal/repository/account_repo.go` +- Modify: `backend/internal/service/account_service.go` +- Test: `backend/internal/handler/admin/account_handler_mixed_channel_test.go` +- Test: `backend/internal/service/admin_service_bulk_update_test.go` + +- [ ] **Step 1: Implement request structs and validation for filter targets** + +```go +type BulkUpdateAccountFilters struct { + Platform string `json:"platform"` + Type string `json:"type"` + Status string `json:"status"` + Group string `json:"group"` + Search string `json:"search"` + PrivacyMode string `json:"privacy_mode"` +} + +type BulkUpdateAccountsRequest struct { + AccountIDs []int64 `json:"account_ids"` + Filters *BulkUpdateAccountFilters `json:"filters"` + // existing fields remain unchanged +} +``` + +- [ ] **Step 2: Resolve filter targets in the service layer with one canonical path** + +```go +type BulkUpdateAccountsInput struct { + AccountIDs []int64 + Filters *BulkUpdateAccountFilters + // existing fields remain unchanged +} + +if len(input.AccountIDs) == 0 && input.Filters != nil { + ids, err := s.resolveBulkUpdateTargetIDs(ctx, input.Filters) + if err != nil { + return nil, err + } + input.AccountIDs = ids +} +``` + +- [ ] **Step 3: Reuse existing account-search/repository logic to resolve all matching IDs** + +```go +func (s *AdminService) resolveBulkUpdateTargetIDs(ctx context.Context, filters *BulkUpdateAccountFilters) ([]int64, error) { + // call the existing repository list/search path with the submitted filters + // page through all matching rows or use a dedicated ID-only query helper + // return unique IDs in stable order +} +``` + +- [ ] **Step 4: Run targeted backend tests** + +Run: `GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go test ./backend/internal/handler/admin ./backend/internal/service -run 'TestBulkUpdateAcceptsFilterTargetRequest|TestAdminServiceBulkUpdateAccounts_ResolvesIDsFromFilters' -count=1` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add backend/internal/handler/admin/account_handler.go backend/internal/service/admin_service.go backend/internal/repository/account_repo.go backend/internal/service/account_service.go backend/internal/handler/admin/account_handler_mixed_channel_test.go backend/internal/service/admin_service_bulk_update_test.go +git commit -m "feat: support filter-target account bulk update" +``` + +### Task 3: Add frontend API and modal tests for target scope + +**Files:** +- Modify: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` +- Create: `frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` +- Modify: `frontend/src/api/admin/accounts.ts` +- Test: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` +- Test: `frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` + +- [ ] **Step 1: Write the failing modal test for filter-target payload submission** + +```ts +it('submits bulk edit using current filters when target mode is filtered-results', async () => { + // mount BulkEditAccountModal with targetMode='filtered' + // submit a minimal change + // expect adminAPI.accounts.bulkUpdate to receive { filters: ... } rather than account_ids +}) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts -t "filtered-results"` +Expected: FAIL because the modal only accepts `accountIds`. + +- [ ] **Step 3: Write the failing accounts-view test for dropdown launch actions** + +```ts +it('opens bulk edit for current filtered results from the table action dropdown', async () => { + // mount AccountsView with filters set + // click Bulk edit > current filtered results + // assert modal props contain filter target metadata +}) +``` + +- [ ] **Step 4: Run test to verify it fails** + +Run: `pnpm -C frontend test:run src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` +Expected: FAIL because the dropdown action and target scope state do not exist yet. + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts frontend/src/api/admin/accounts.ts +git commit -m "test: cover account bulk edit target scopes" +``` + +### Task 4: Implement unified frontend bulk-edit target scope flow + +**Files:** +- Modify: `frontend/src/views/admin/AccountsView.vue` +- Modify: `frontend/src/components/admin/account/AccountBulkActionsBar.vue` +- Modify: `frontend/src/components/account/BulkEditAccountModal.vue` +- Modify: `frontend/src/api/admin/accounts.ts` +- Modify: `frontend/src/i18n/locales/zh.ts` +- Modify: `frontend/src/i18n/locales/en.ts` +- Test: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` +- Test: `frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` + +- [ ] **Step 1: Add a typed frontend target contract for bulk edit** + +```ts +export type AccountBulkEditTarget = + | { mode: 'selected'; accountIds: number[]; selectedPlatforms: AccountPlatform[]; selectedTypes: AccountType[] } + | { mode: 'filtered'; filters: AccountListFilters; previewCount: number; selectedPlatforms: AccountPlatform[]; selectedTypes: AccountType[] } +``` + +- [ ] **Step 2: Replace the single selected-row edit button with one dropdown** + +```vue + +``` + +- [ ] **Step 3: Snapshot current filters and preview count when launching filtered mode** + +```ts +const openBulkEditFiltered = async () => { + const filters = toBulkEditFilterSnapshot(params) + const preview = await adminAPI.accounts.list(1, 1, filters) + bulkEditTarget.value = { + mode: 'filtered', + filters, + previewCount: preview.pagination.total, + selectedPlatforms: collectPlatforms(preview.data), + selectedTypes: collectTypes(preview.data) + } + showBulkEdit.value = true +} +``` + +- [ ] **Step 4: Update modal submission to call `bulkUpdate` with either `account_ids` or `filters`** + +```ts +if (props.target.mode === 'selected') { + await adminAPI.accounts.bulkUpdate({ account_ids: props.target.accountIds, ...updates }) +} else { + await adminAPI.accounts.bulkUpdate({ filters: props.target.filters, ...updates }) +} +``` + +- [ ] **Step 5: Run targeted frontend tests** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add frontend/src/views/admin/AccountsView.vue frontend/src/components/admin/account/AccountBulkActionsBar.vue frontend/src/components/account/BulkEditAccountModal.vue frontend/src/api/admin/accounts.ts frontend/src/i18n/locales/zh.ts frontend/src/i18n/locales/en.ts frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts +git commit -m "feat: add filtered-result account bulk edit" +``` + +### Task 5: Add failing tests for missing OpenAI bulk-edit fields + +**Files:** +- Modify: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` +- Test: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` + +- [ ] **Step 1: Write the failing OAuth test for `codex_cli_only`** + +```ts +it('OpenAI OAuth bulk edit can submit codex_cli_only', async () => { + // enable the toggle and submit + // expect extra.codex_cli_only to be sent +}) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts -t "codex_cli_only"` +Expected: FAIL because the modal has no such control or payload mapping. + +- [ ] **Step 3: Write the failing API key test for API key WS mode** + +```ts +it('OpenAI API key bulk edit submits API key WS mode fields', async () => { + // enable the API key WS mode selector and submit + // expect openai_apikey_responses_websockets_v2_mode and enabled flag +}) +``` + +- [ ] **Step 4: Run test to verify it fails** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts -t "API key WS mode"` +Expected: FAIL because the modal only submits OAuth WS mode. + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +git commit -m "test: cover missing OpenAI bulk edit fields" +``` + +### Task 6: Implement missing OpenAI bulk-edit controls and payload wiring + +**Files:** +- Modify: `frontend/src/components/account/BulkEditAccountModal.vue` +- Modify: `frontend/src/i18n/locales/zh.ts` +- Modify: `frontend/src/i18n/locales/en.ts` +- Test: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` + +- [ ] **Step 1: Add UI controls for OAuth `codex_cli_only` and API key WS mode** + +```vue +
+ + +
+ +
+ +
+``` + +- [ ] **Step 2: Mirror single-account payload semantics in the bulk-edit submit builder** + +```ts +if (enableCodexCLIOnly.value) { + const extra = ensureExtra() + extra.codex_cli_only = codexCLIOnlyEnabled.value +} + +if (enableOpenAIAPIKeyWSMode.value) { + const extra = ensureExtra() + extra.openai_apikey_responses_websockets_v2_mode = openaiAPIKeyResponsesWebSocketV2Mode.value + extra.openai_apikey_responses_websockets_v2_enabled = isOpenAIWSModeEnabled(openaiAPIKeyResponsesWebSocketV2Mode.value) +} +``` + +- [ ] **Step 3: Run focused modal tests** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add frontend/src/components/account/BulkEditAccountModal.vue frontend/src/i18n/locales/zh.ts frontend/src/i18n/locales/en.ts frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +git commit -m "feat: align OpenAI bulk edit compact settings" +``` + +### Task 7: Final regression verification + +**Files:** +- Modify: none expected +- Test: `frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts` +- Test: `frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` +- Test: `backend/internal/handler/admin/account_handler_mixed_channel_test.go` +- Test: `backend/internal/service/admin_service_bulk_update_test.go` + +- [ ] **Step 1: Run frontend typecheck** + +Run: `pnpm -C frontend typecheck` +Expected: PASS + +- [ ] **Step 2: Run focused frontend test suite** + +Run: `pnpm -C frontend test:run src/components/account/__tests__/BulkEditAccountModal.spec.ts src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts` +Expected: PASS + +- [ ] **Step 3: Run focused backend test suite** + +Run: `GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go test ./backend/internal/handler/admin ./backend/internal/service -run 'BulkUpdate|bulk update' -count=1` +Expected: PASS + +- [ ] **Step 4: Commit final integration fixes if needed** + +```bash +git add frontend/src/components/account/BulkEditAccountModal.vue frontend/src/views/admin/AccountsView.vue frontend/src/components/admin/account/AccountBulkActionsBar.vue frontend/src/api/admin/accounts.ts frontend/src/i18n/locales/zh.ts frontend/src/i18n/locales/en.ts backend/internal/handler/admin/account_handler.go backend/internal/service/admin_service.go backend/internal/repository/account_repo.go backend/internal/service/account_service.go frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts frontend/src/views/admin/__tests__/AccountsView.bulkEdit.spec.ts backend/internal/handler/admin/account_handler_mixed_channel_test.go backend/internal/service/admin_service_bulk_update_test.go +git commit -m "feat: finish account bulk edit scope and compact support" +```