star-chart-search-enhancer/externaldocs/2026-04-20-star-chart-market-after-search-rate-implementation-plan.md

801 lines
18 KiB
Markdown

# 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.