# Star Chart Market After-Search Rate 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:** Build a new Chrome MV3 extension that adds two after-search-rate columns to the Xingtu creator market page, then supports plugin-owned filtering, sorting, full-scan export, and CSV download. **Architecture:** Start from a minimal MV3 content-script extension and keep the implementation split into small modules: value normalization, API mapping, result storage, DOM sync, full-scan orchestration, filter/sort control, and CSV export. Follow TDD for every production behavior module; only project bootstrap and config files may be created directly as setup. **Tech Stack:** TypeScript, Chrome Manifest V3, Vitest, jsdom, tsup, Node.js --- ## File Structure Planned initial structure: ```text package.json package-lock.json tsconfig.json vitest.config.ts scripts/ build.mjs src/ manifest.json content/ index.ts market/ index.ts types.ts dom-sync.ts api-client.ts result-store.ts filter-sort-controller.ts full-scan-controller.ts csv-exporter.ts plugin-toolbar.ts shared/ rate-normalizer.ts csv.ts tests/ rate-normalizer.test.ts market-api-client.test.ts result-store.test.ts filter-sort-controller.test.ts market-dom-sync.test.ts full-scan-controller.test.ts csv-exporter.test.ts market-content-entry.test.ts ``` Responsibilities: - `src/shared/rate-normalizer.ts`: parse and normalize display values and comparable lower-bound values - `src/content/market/api-client.ts`: fetch and map `/gw/api/aggregator/get_author_ase_info` - `src/content/market/result-store.ts`: hold per-author status and merged market records - `src/content/market/filter-sort-controller.ts`: apply threshold filters and lower-bound sorting - `src/content/market/dom-sync.ts`: locate market rows, inject two columns, and update row visibility/order - `src/content/market/full-scan-controller.ts`: paginate through filtered result pages and hydrate the result store - `src/content/market/csv-exporter.ts`: generate CSV rows and blob/download metadata - `src/content/market/plugin-toolbar.ts`: render the plugin controls for threshold filter, sorting, and export - `src/content/market/index.ts`: compose all market modules for the live page - `src/content/index.ts`: route-match and boot the market controller ## Task 1: Initialize the Empty Project **Files:** - Create: `package.json` - Create: `tsconfig.json` - Create: `vitest.config.ts` - Create: `scripts/build.mjs` - Create: `src/manifest.json` - Create: `src/content/index.ts` - [ ] **Step 1: Initialize the repository and npm package** Run: ```bash git init npm init -y ``` Expected: a new `.git/` directory and baseline `package.json`. - [ ] **Step 2: Add build and test dependencies** Run: ```bash npm install -D typescript vitest jsdom tsup ``` Expected: `package-lock.json` created and dev dependencies added. - [ ] **Step 3: Write the minimal config and manifest files** Create direct setup files: - `package.json` scripts: `build`, `test`, `test:watch` - `tsconfig.json` - `vitest.config.ts` - `scripts/build.mjs` - `src/manifest.json` - `src/content/index.ts` The initial manifest should only match the Xingtu market page and load one content entry bundle. - [ ] **Step 4: Run the test command to verify the toolchain is wired** Run: ```bash npm test ``` Expected: Vitest runs with zero or pending test files, without config errors. - [ ] **Step 5: Run the build command to verify the extension bundle layout** Run: ```bash npm run build ``` Expected: build succeeds and writes a minimal `dist/` layout containing the manifest and content bundle. - [ ] **Step 6: Commit the bootstrap** Run: ```bash git add package.json package-lock.json tsconfig.json vitest.config.ts scripts/build.mjs src/manifest.json src/content/index.ts git commit -m "chore: bootstrap mv3 extension project" ``` ## Task 2: Implement Rate Normalization **Files:** - Create: `src/shared/rate-normalizer.ts` - Test: `tests/rate-normalizer.test.ts` - [ ] **Step 1: Write the failing tests for normalization and comparable lower bounds** Add tests covering: ```ts import { describe, expect, test } from "vitest"; import { compareRateValues, normalizeRateDisplay, parseRateLowerBound } from "../src/shared/rate-normalizer"; describe("rate-normalizer", () => { test("normalizes compact ranges", () => { expect(normalizeRateDisplay("0.5%-1%")).toBe("0.5% - 1%"); }); test("parses the lower bound of a range", () => { expect(parseRateLowerBound("0.02% - 0.1%")).toBe(0.02); }); test("treats less-than values as smaller than the boundary range", () => { expect(compareRateValues("<0.02%", "0.02% - 0.1%")).toBeLessThan(0); }); }); ``` - [ ] **Step 2: Run the rate normalizer test file and verify RED** Run: ```bash npm test -- tests/rate-normalizer.test.ts ``` Expected: FAIL because the module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement only: - display normalization for compact range strings - lower-bound parsing - comparison helper that orders invalid or missing values last - [ ] **Step 4: Run the test file and verify GREEN** Run: ```bash npm test -- tests/rate-normalizer.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor for one parsing pipeline** Keep parsing logic in one shared path so filtering, sorting, and export do not interpret values differently. - [ ] **Step 6: Re-run the test file after refactor** Run: ```bash npm test -- tests/rate-normalizer.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/shared/rate-normalizer.ts tests/rate-normalizer.test.ts git commit -m "feat: add rate normalization helpers" ``` ## Task 3: Implement the ASE API Client **Files:** - Create: `src/content/market/api-client.ts` - Create: `src/content/market/types.ts` - Test: `tests/market-api-client.test.ts` - [ ] **Step 1: Write the failing tests for response mapping and failure states** Cover: - successful mapping from `avg_search_after_view_rate` - successful mapping from `personal_avg_search_after_view_rate` - `missing` or `failed` behavior when fields are absent - timeout or non-OK response failure mapping Example test seed: ```ts test("maps a valid ASE payload into normalized rates", async () => { const client = createMarketApiClient({ fetchImpl: async () => ({ ok: true, json: async () => ({ data: { avg_search_after_view_rate: "<0.02%", personal_avg_search_after_view_rate: "0.02 - 0.1%" } }) }) }); await expect(client.loadAuthorAseInfo("123")).resolves.toMatchObject({ success: true, rates: { singleVideoAfterSearchRate: "<0.02%", personalVideoAfterSearchRate: "0.02% - 0.1%" } }); }); ``` - [ ] **Step 2: Run the API client test file and verify RED** Run: ```bash npm test -- tests/market-api-client.test.ts ``` Expected: FAIL because the client module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - request builder for `/gw/api/aggregator/get_author_ase_info` - response mapping - timeout handling - normalized success result and stable failure reasons - [ ] **Step 4: Run the API client tests and verify GREEN** Run: ```bash npm test -- tests/market-api-client.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor shared types** Move reusable market result types into `src/content/market/types.ts`. - [ ] **Step 6: Re-run the API client tests** Run: ```bash npm test -- tests/market-api-client.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/content/market/api-client.ts src/content/market/types.ts tests/market-api-client.test.ts git commit -m "feat: add ase api client" ``` ## Task 4: Implement the Result Store **Files:** - Create: `src/content/market/result-store.ts` - Modify: `src/content/market/types.ts` - Test: `tests/result-store.test.ts` - [ ] **Step 1: Write the failing tests for merged record lifecycle** Cover: - create loading records from current-page rows - update one author to success - preserve failed authors instead of dropping them - dedupe the same author across pages - keep stable major fields after repeated writes - [ ] **Step 2: Run the result store test file and verify RED** Run: ```bash npm test -- tests/result-store.test.ts ``` Expected: FAIL because the store module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - `upsertMarketRow` - `setAuthorLoading` - `setAuthorSuccess` - `setAuthorFailed` - `listRecords` - `getRecord` - [ ] **Step 4: Run the result store tests and verify GREEN** Run: ```bash npm test -- tests/result-store.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor state transitions into one reducer-style path** Keep state transitions explicit so later full-scan orchestration does not spread status logic across files. - [ ] **Step 6: Re-run the result store tests** Run: ```bash npm test -- tests/result-store.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/content/market/result-store.ts src/content/market/types.ts tests/result-store.test.ts git commit -m "feat: add market result store" ``` ## Task 5: Implement Filter and Sort Control **Files:** - Create: `src/content/market/filter-sort-controller.ts` - Modify: `src/content/market/types.ts` - Test: `tests/filter-sort-controller.test.ts` - [ ] **Step 1: Write the failing tests for threshold filtering and lower-bound sorting** Cover: - pass only when lower bound is greater than or equal to the threshold - sort single-rate descending - sort personal-rate ascending - keep failed or missing rows at the end - [ ] **Step 2: Run the filter/sort tests and verify RED** Run: ```bash npm test -- tests/filter-sort-controller.test.ts ``` Expected: FAIL because the controller module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - filter state type - sort state type - record predicate - comparator - application helper returning ordered visible record IDs - [ ] **Step 4: Run the filter/sort tests and verify GREEN** Run: ```bash npm test -- tests/filter-sort-controller.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor duplicated comparison branches** Keep one comparison path per metric and one final fallback ordering rule. - [ ] **Step 6: Re-run the filter/sort tests** Run: ```bash npm test -- tests/filter-sort-controller.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/content/market/filter-sort-controller.ts src/content/market/types.ts tests/filter-sort-controller.test.ts git commit -m "feat: add filter and sort controller" ``` ## Task 6: Implement CSV Export **Files:** - Create: `src/shared/csv.ts` - Create: `src/content/market/csv-exporter.ts` - Test: `tests/csv-exporter.test.ts` - [ ] **Step 1: Write the failing tests for CSV serialization** Cover: - header order - escaped commas and quotes - failed rows emitting empty rate fields plus status - export rows using normalized display values - [ ] **Step 2: Run the CSV exporter tests and verify RED** Run: ```bash npm test -- tests/csv-exporter.test.ts ``` Expected: FAIL because the exporter module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - CSV escaping helper - column definition list - row-to-CSV conversion - blob-ready text generation function - [ ] **Step 4: Run the CSV exporter tests and verify GREEN** Run: ```bash npm test -- tests/csv-exporter.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor field mapping into one declarative schema** Keep export field order and labels in one place. - [ ] **Step 6: Re-run the CSV exporter tests** Run: ```bash npm test -- tests/csv-exporter.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/shared/csv.ts src/content/market/csv-exporter.ts tests/csv-exporter.test.ts git commit -m "feat: add csv exporter" ``` ## Task 7: Implement Current-Page DOM Sync **Files:** - Create: `src/content/market/dom-sync.ts` - Test: `tests/market-dom-sync.test.ts` - [ ] **Step 1: Write the failing DOM tests for current-page enhancement** Use jsdom fixtures that mimic the Xingtu market grid and cover: - injecting the two header cells - injecting one pair of per-row cells - rendering loading, success, and failed states - hiding filtered rows - reordering rows based on an ordered ID list - [ ] **Step 2: Run the DOM sync tests and verify RED** Run: ```bash npm test -- tests/market-dom-sync.test.ts ``` Expected: FAIL because the module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - page structure discovery - row extraction with major field snapshots - inserted-cell rendering - row hide/show - row order application - [ ] **Step 4: Run the DOM sync tests and verify GREEN** Run: ```bash npm test -- tests/market-dom-sync.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor DOM selectors into one locator object** This keeps future layout changes isolated. - [ ] **Step 6: Re-run the DOM sync tests** Run: ```bash npm test -- tests/market-dom-sync.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/content/market/dom-sync.ts tests/market-dom-sync.test.ts git commit -m "feat: add market dom sync" ``` ## Task 8: Implement Full-Scan Pagination Control **Files:** - Create: `src/content/market/full-scan-controller.ts` - Modify: `src/content/market/types.ts` - Test: `tests/full-scan-controller.test.ts` - [ ] **Step 1: Write the failing tests for on-demand full scans** Cover: - initial page load does not start full scan - filter action starts full scan - sort action starts full scan - export action starts full scan - repeated actions do not restart a completed scan unnecessarily - failed author fetches are recorded but do not abort the whole scan - [ ] **Step 2: Run the full-scan tests and verify RED** Run: ```bash npm test -- tests/full-scan-controller.test.ts ``` Expected: FAIL because the controller module does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement the controller around injected dependencies: - current-page row reader - paginator - author metrics loader - result store writer The controller should expose one explicit method per trigger source, such as: - `ensureScanForFilter()` - `ensureScanForSort()` - `ensureScanForExport()` - [ ] **Step 4: Run the full-scan tests and verify GREEN** Run: ```bash npm test -- tests/full-scan-controller.test.ts ``` Expected: PASS. - [ ] **Step 5: Refactor to a single idempotent scan path** All trigger entry points should delegate to the same internal scan routine. - [ ] **Step 6: Re-run the full-scan tests** Run: ```bash npm test -- tests/full-scan-controller.test.ts ``` Expected: PASS. - [ ] **Step 7: Commit** Run: ```bash git add src/content/market/full-scan-controller.ts src/content/market/types.ts tests/full-scan-controller.test.ts git commit -m "feat: add full scan controller" ``` ## Task 9: Implement the Plugin Toolbar and Market Composition **Files:** - Create: `src/content/market/plugin-toolbar.ts` - Create: `src/content/market/index.ts` - Modify: `src/content/index.ts` - Test: `tests/market-content-entry.test.ts` - [ ] **Step 1: Write the failing integration tests for the page entry flow** Cover: - market controller boots on the Xingtu market URL - current page rows are hydrated on start - applying plugin filters triggers full scan and hides non-matching rows - applying plugin sorting triggers full scan and reorders rows - export triggers full scan and hands the ordered visible records to the CSV exporter - [ ] **Step 2: Run the market entry tests and verify RED** Run: ```bash npm test -- tests/market-content-entry.test.ts ``` Expected: FAIL because the market controller composition does not exist yet. - [ ] **Step 3: Write the minimal implementation** Implement: - toolbar controls for threshold inputs, sort selector, and export button - event wiring from toolbar to full scan, filter/sort controller, and exporter - market page bootstrap in `src/content/index.ts` - [ ] **Step 4: Run the market entry tests and verify GREEN** Run: ```bash npm test -- tests/market-content-entry.test.ts ``` Expected: PASS. - [ ] **Step 5: Run the whole test suite** Run: ```bash npm test ``` Expected: all test files PASS. - [ ] **Step 6: Run the production build** Run: ```bash npm run build ``` Expected: extension build succeeds with all planned bundles and manifest output. - [ ] **Step 7: Commit** Run: ```bash git add src/content/index.ts src/content/market/index.ts src/content/market/plugin-toolbar.ts tests/market-content-entry.test.ts git commit -m "feat: wire market plugin controls" ``` ## Task 10: Manual Verification and Documentation **Files:** - Modify: `externaldocs/2026-04-20-star-chart-market-after-search-rate-plugin-spec.md` (only if actual implementation forces scope adjustments) - Create: `README.md` - [ ] **Step 1: Write a minimal README** Document: - install - test - build - load unpacked extension - manual verification checklist - [ ] **Step 2: Run manual verification on the live page** Verify: - current page gets two new columns - loading, success, failed states render correctly - filter triggers scan and hides unmatched rows - sort triggers scan and reorders rows - export produces a CSV with plugin status fields - [ ] **Step 3: Update the spec if implementation realities changed any promised behavior** Only adjust documented scope if the live page proves a requirement impossible or unstable. - [ ] **Step 4: Run the full test suite again** Run: ```bash npm test npm run build ``` Expected: both commands succeed. - [ ] **Step 5: Commit** Run: ```bash git add README.md externaldocs/2026-04-20-star-chart-market-after-search-rate-plugin-spec.md git commit -m "docs: add usage and verification notes" ``` ## Notes for Execution - Do not write production behavior before the corresponding failing test exists. - Keep DOM selectors isolated; the Xingtu page is likely to shift. - Reuse the old project's verified API field names, but do not copy large modules blindly; re-derive them under tests. - For full-scan logic, dependency-inject pagination and row reading so the orchestration stays testable. - If a task reveals a missing boundary in the spec, pause and update the spec before continuing.