# Market Audience Profile Export 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 a selected-creators-only `导出画像CSV` flow that exports current market columns plus detail-page "连接用户" audience profile distributions. **Architecture:** Keep the existing CSV export untouched and add a separate profile export path. The content controller reuses current selection and market row hydration, loads one selected creator profile at a time through a focused detail-page profile client, then writes a separate CSV with structured audience columns. **Tech Stack:** TypeScript, Chrome MV3 content scripts, Xingtu authenticated pages, Vitest, jsdom, tsup --- ## File Map - Modify: `src/background/auth/controller.ts` - Keep token-readable auth state behavior from the previous fix. - Modify: `src/background/auth/state.ts` - Keep logged-out `lastError` support from the previous fix. - Modify: `tests/background-auth-controller.test.ts` - Keep token-expired regression coverage. - Modify: `src/content/market/auth-gate.ts` - Render expired-login text when auth state carries a token-expired error. - Modify: `src/content/index.ts` - Pass auth failure text into the market auth gate if needed. - Modify: `src/content/market/plugin-toolbar.ts` - Add a `导出画像CSV` button and handler. - Create: `src/content/market/audience-profile-types.ts` - Define normalized distribution and export-row types. - Create: `src/content/market/audience-profile-client.ts` - Load one creator detail page and extract normalized audience profile data. - Create: `src/content/market/audience-profile-csv.ts` - Build CSV columns from market records plus profile distributions. - Modify: `src/content/market/index.ts` - Add selected-only profile export flow and serial profile loading. - Test: `tests/market-auth-gating.test.ts` - Verify expired-login text. - Test: `tests/plugin-toolbar.test.ts` - Verify new toolbar button wiring. - Test: `tests/audience-profile-csv.test.ts` - Verify structured CSV column expansion. - Test: `tests/audience-profile-client.test.ts` - Verify parser behavior against representative detail-page payload/state shapes. - Modify: `tests/market-content-entry.test.ts` - Verify selected-only export behavior and failure handling. ## Task 1: Expired Login Message **Files:** - Modify: `src/content/market/auth-gate.ts` - Modify: `src/content/index.ts` - Test: `tests/market-auth-gating.test.ts` - [ ] **Step 1: Write the failing auth gate test** Add a test where `sendAuthMessage` returns: ```ts { ok: true, type: "auth:state", value: { isAuthenticated: false, lastError: "Token 已过期" } } ``` Assert the page shows `登录已过期,请重新登录`. - [ ] **Step 2: Run the failing test** Run: ```bash npm test -- tests/market-auth-gating.test.ts ``` Expected: FAIL because the gate only renders `请先登录插件`. - [ ] **Step 3: Implement minimal auth gate text support** Update `renderMarketAuthGate` to accept an optional message string and render it instead of the default title. Update `bootContentScript` to pass `登录已过期,请重新登录` when `lastError` contains `token` or `过期`. - [ ] **Step 4: Verify** Run: ```bash npm test -- tests/market-auth-gating.test.ts tests/popup-entry.test.ts ``` Expected: PASS. ## Task 2: Toolbar Button **Files:** - Modify: `src/content/market/plugin-toolbar.ts` - Test: `tests/plugin-toolbar.test.ts` - [ ] **Step 1: Write failing toolbar tests** Create tests that: - render the toolbar - assert a button with text `导出画像CSV` exists - click it and assert `onExportAudienceProfile` was called - assert `setToolbarBusyState` disables the new button - [ ] **Step 2: Run the failing tests** Run: ```bash npm test -- tests/plugin-toolbar.test.ts ``` Expected: FAIL because the button and handler do not exist. - [ ] **Step 3: Implement toolbar support** Add `onExportAudienceProfile` to `PluginToolbarHandlers`, add `audienceProfileExportButton` to `PluginToolbarDom`, render the new button, wire click handling, and include it in busy-state disabling. - [ ] **Step 4: Verify** Run: ```bash npm test -- tests/plugin-toolbar.test.ts tests/market-content-entry.test.ts ``` Expected: PASS. ## Task 3: Profile CSV Builder **Files:** - Create: `src/content/market/audience-profile-types.ts` - Create: `src/content/market/audience-profile-csv.ts` - Test: `tests/audience-profile-csv.test.ts` - [ ] **Step 1: Write failing CSV tests** Define a sample market record and sample profile: ```ts const profile = { status: "success", gender: [ { label: "男性", value: "40.6%" }, { label: "女性", value: "59.4%" } ], age: [{ label: "18-23", value: "28.6%" }], province: [{ label: "广东", value: "15%" }], regionTop: [{ label: "北京", value: "15%" }], cityTier: [{ label: "一线", value: "20%" }], interestTop: [{ label: "亲子", value: "18%" }], crowd: [{ label: "精致妈妈", value: "12%" }] }; ``` Assert the CSV contains separate headers like `连接用户-男性占比`, `省份-广东占比`, `地域TOP1名称`, `地域TOP1占比`. - [ ] **Step 2: Run the failing tests** Run: ```bash npm test -- tests/audience-profile-csv.test.ts ``` Expected: FAIL because the files do not exist. - [ ] **Step 3: Implement CSV builder** Create: - `AudienceProfileDistributionItem` - `AudienceProfileResult` - `buildAudienceProfileCsv(records, profilesByAuthorId)` Reuse `escapeCsvCell` and existing base/rate/backend metric column conventions. Add `画像抓取状态`. - [ ] **Step 4: Verify** Run: ```bash npm test -- tests/audience-profile-csv.test.ts tests/csv-exporter.test.ts ``` Expected: PASS. ## Task 4: Detail Profile Client and Parser **Files:** - Create: `src/content/market/audience-profile-client.ts` - Test: `tests/audience-profile-client.test.ts` - [ ] **Step 1: Use a logged-in browser to identify the real data source** Run a Playwright probe against an authenticated `https://xingtu.cn/ad/creator/author-homepage/douyin-video/` page. Capture only `/gw/api/...` JSON responses and page Vue/ECharts state. Record representative payload/state samples in the test file as small fixtures. - [ ] **Step 2: Write failing parser tests** Use the captured fixture to assert the parser returns normalized arrays for gender, age, province, region top 10, city tier, interest top 10, and crowd. - [ ] **Step 3: Run the failing tests** Run: ```bash npm test -- tests/audience-profile-client.test.ts ``` Expected: FAIL because the client/parser does not exist. - [ ] **Step 4: Implement parser and client** Implement a small parser first. Then implement the client with injectable dependencies: ```ts createAudienceProfileClient({ fetchDetailPage?: (authorId: string) => Promise; readProfileFromPage?: (authorId: string) => Promise; }) ``` Prefer parsed API JSON. Fall back to page state when API JSON is unavailable. - [ ] **Step 5: Verify** Run: ```bash npm test -- tests/audience-profile-client.test.ts ``` Expected: PASS. ## Task 5: Controller Export Flow **Files:** - Modify: `src/content/market/index.ts` - Test: `tests/market-content-entry.test.ts` - [ ] **Step 1: Write failing selected-only export tests** Add tests that: - select one of two visible rows - click `导出画像CSV` - assert only the selected author profile is requested - assert `onCsvReady` receives a CSV containing profile columns - [ ] **Step 2: Write failing no-selection test** Assert clicking `导出画像CSV` with no selected rows sets status to `请先勾选需要导出画像的达人` and makes no profile requests. - [ ] **Step 3: Write failing partial-failure test** Mock two selected profiles where one succeeds and one fails. Assert CSV is still generated with one success row and one `画像抓取状态=失败` row. - [ ] **Step 4: Run failing tests** Run: ```bash npm test -- tests/market-content-entry.test.ts ``` Expected: FAIL because the controller has no profile export flow. - [ ] **Step 5: Implement controller flow** Add an injected option `loadAudienceProfile?: (record: MarketRecord) => Promise`. Add handler: - sync selected state from DOM - reject empty selection - hydrate current-page selected records - load profiles serially - cache successful results by author ID - build profile CSV - call `onCsvReady` - update toolbar progress/status - [ ] **Step 6: Verify** Run: ```bash npm test -- tests/market-content-entry.test.ts tests/audience-profile-csv.test.ts tests/plugin-toolbar.test.ts ``` Expected: PASS. ## Task 6: Full Verification **Files:** - Modify only if failures identify necessary scoped fixes. - [ ] **Step 1: Run focused suite** Run: ```bash npm test -- tests/market-auth-gating.test.ts tests/plugin-toolbar.test.ts tests/audience-profile-csv.test.ts tests/audience-profile-client.test.ts tests/market-content-entry.test.ts ``` Expected: PASS. - [ ] **Step 2: Run all tests** Run: ```bash npm test ``` Expected: PASS. - [ ] **Step 3: Run build** Run: ```bash npm run build ``` Expected: PASS. - [ ] **Step 4: Manual logged-in browser verification** Load the built extension in Chrome, select one or two market creators, click `导出画像CSV`, and verify the downloaded CSV contains structured profile columns with detail-page data.