410 lines
16 KiB
Markdown
410 lines
16 KiB
Markdown
# Star Chart Market Visible Page Columns 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 two after-search-rate columns to the Xingtu creator market list page, auto-load them for the current visible result page, and support cache-backed retryable row rendering without regressing the existing detail-page experiment.
|
|
|
|
**Architecture:** Keep the existing detail-page hook flow intact, split the content entry into route-aware controllers, and implement the market-page feature entirely inside `src/content/market/`. Market-page data loading uses a dedicated same-origin API client plus a list-sequence-aware controller so DOM sync, cache reuse, retry, and stale-result suppression stay deterministic and testable.
|
|
|
|
**Tech Stack:** TypeScript, Vitest, jsdom, tsup, Chrome Extension Manifest V3
|
|
|
|
---
|
|
|
|
## Implementation Notes
|
|
|
|
- Current workspace already contains working detail-page code, build scripts, and tests. This plan assumes incremental change, not greenfield scaffolding.
|
|
- Current workspace is still not a git repository, so each “commit” step is replaced with a verification note.
|
|
- This plan only covers stage 1: current visible result page enhancement.
|
|
- Follow TDD strictly: failing test first, then minimal code.
|
|
- Do not route the market-page feature through `src/page/hook.ts` unless runtime evidence proves content-script requests are blocked.
|
|
- Market async flows must not cache raw `HTMLElement` references across request boundaries. Cache/request layers may store only row metadata such as `authorId`, `listSeq`, and list signature.
|
|
- The market API client should expose deterministic error reasons: `timeout` for `AbortError`, `request-failed` for network/non-2xx/parse failures, and `bad-response` for structurally valid responses missing either target field.
|
|
- Every async writeback must re-check current DOM markers (`data-sces-author-id`, `data-sces-list-seq`) before rendering, instead of trusting a stale row reference captured earlier.
|
|
|
|
## Planned File Map
|
|
|
|
- Modify: `src/manifest.json`
|
|
- Add market-page match coverage while preserving detail-page coverage.
|
|
- Modify: `src/content/index.ts`
|
|
- Reduce to route bootstrap and controller selection.
|
|
- Create: `src/content/detail/index.ts`
|
|
- Hold the existing detail-page controller logic currently living in `src/content/index.ts`.
|
|
- Create: `src/content/market/index.ts`
|
|
- Orchestrate market-page DOM sync, list sessions, loading, cache reuse, and retry.
|
|
- Create: `src/content/market/api-client.ts`
|
|
- Build request URLs, issue same-origin fetches with timeout, and map the known API response into normalized rates.
|
|
- Create: `src/content/market/batch-loader.ts`
|
|
- Coordinate concurrency-limited loading, stale-result suppression, and row-level retry.
|
|
- Create: `src/content/market/cache-store.ts`
|
|
- Store success cache and in-flight request dedupe entries by `authorId`.
|
|
- Create: `src/content/market/dom-sync.ts`
|
|
- Find the target table, insert headers/cells, and bind row metadata markers.
|
|
- Create: `src/content/market/id-extractor.ts`
|
|
- Extract `authorId` from row links and fallback attributes.
|
|
- Create: `src/content/market/list-signature.ts`
|
|
- Produce deterministic list/session signatures from current rows and URL state.
|
|
- Create: `src/content/market/row-render.ts`
|
|
- Render loading, success, and error states into the two injected cells.
|
|
- Create: `src/content/market/row-state.ts`
|
|
- Define row-status types and transition helpers.
|
|
- Create: `src/shared/normalize-rate-value.ts`
|
|
- Normalize known rate shapes such as `<0.02%` and `0.02 - 0.1%`.
|
|
- Modify: `src/shared/extract-after-search-rates.ts`
|
|
- Reuse the extracted shared normalizer so detail-page behavior stays consistent.
|
|
- Modify: `tests/build-layout.test.ts`
|
|
- Extend manifest/build assertions for the market route.
|
|
- Modify: `tests/content-bridge.test.ts`
|
|
- Point detail-page tests at the extracted detail controller module.
|
|
- Create: `tests/content-entry.test.ts`
|
|
- Cover route bootstrap behavior for detail, market, and unsupported URLs.
|
|
- Create: `tests/market-api-client.test.ts`
|
|
- Cover request URL generation, timeout handling, and response mapping.
|
|
- Create: `tests/market-id-extractor.test.ts`
|
|
- Cover author-id extraction from row fixtures.
|
|
- Create: `tests/market-dom-sync.test.ts`
|
|
- Cover header/cell insertion and duplicate protection.
|
|
- Create: `tests/market-row-render.test.ts`
|
|
- Cover row rendering transitions and retry affordance.
|
|
- Create: `tests/market-batch-loader.test.ts`
|
|
- Cover cache reuse, in-flight dedupe, concurrency, and retry behavior.
|
|
- Create: `tests/market-controller.test.ts`
|
|
- Cover list changes, stale-result suppression, and cache-backed rehydration.
|
|
- Modify: `tests/readme.test.ts`
|
|
- Require README coverage for the market-page feature.
|
|
- Modify: `README.md`
|
|
- Document market-page support and manual verification.
|
|
|
|
### Task 1: Split the Content Entry Into a Route Bootstrap and a Detail Controller
|
|
|
|
**Files:**
|
|
- Modify: `src/content/index.ts`
|
|
- Create: `src/content/detail/index.ts`
|
|
- Modify: `tests/content-bridge.test.ts`
|
|
- Create: `tests/content-entry.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing route-bootstrap tests**
|
|
|
|
Add tests asserting:
|
|
- detail URLs bootstrap the detail controller
|
|
- non-detail, non-market URLs no-op cleanly
|
|
- the detail controller still injects the page hook exactly once
|
|
|
|
- [ ] **Step 2: Run the tests to verify they fail**
|
|
|
|
Run: `npm test -- tests/content-entry.test.ts tests/content-bridge.test.ts`
|
|
Expected: FAIL because route bootstrap and extracted detail controller do not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal route bootstrap split**
|
|
|
|
Implement:
|
|
- `createDetailContentController` in `src/content/detail/index.ts`
|
|
- route matching in `src/content/index.ts`
|
|
- no-op behavior on unsupported URLs
|
|
- updated imports in the existing detail tests
|
|
|
|
- [ ] **Step 4: Run the tests to verify they pass**
|
|
|
|
Run: `npm test -- tests/content-entry.test.ts tests/content-bridge.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Record the verification note**
|
|
|
|
Record which tests passed and that detail-page behavior remained intact
|
|
|
|
### Task 2: Extend Manifest Coverage to the Market Page
|
|
|
|
**Files:**
|
|
- Modify: `src/manifest.json`
|
|
- Modify: `tests/build-layout.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing manifest assertions**
|
|
|
|
Extend the build-layout test to assert:
|
|
- the content script matches both detail and market URLs
|
|
- the emitted JS asset list is unchanged
|
|
- `web_accessible_resources` for the detail hook still stay present
|
|
|
|
- [ ] **Step 2: Run the test to verify it fails**
|
|
|
|
Run: `npm test -- tests/build-layout.test.ts`
|
|
Expected: FAIL because the manifest only targets the detail page
|
|
|
|
- [ ] **Step 3: Implement the minimal manifest update**
|
|
|
|
Implement:
|
|
- add `https://*.xingtu.cn/ad/creator/market*` to `content_scripts.matches`
|
|
- keep existing detail-page matches and page-hook asset coverage
|
|
|
|
- [ ] **Step 4: Run the test to verify it passes**
|
|
|
|
Run: `npm test -- tests/build-layout.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Verify the build still works**
|
|
|
|
Run: `npm run build`
|
|
Expected: PASS and `dist/manifest.json` includes the market match
|
|
|
|
- [ ] **Step 6: Record the verification note**
|
|
|
|
Record the passing build-layout test and build output
|
|
|
|
### Task 3: Add the Shared Rate Normalizer and the Market API Client
|
|
|
|
**Files:**
|
|
- Create: `src/shared/normalize-rate-value.ts`
|
|
- Modify: `src/shared/extract-after-search-rates.ts`
|
|
- Create: `src/content/market/api-client.ts`
|
|
- Create: `tests/market-api-client.test.ts`
|
|
- Modify: `tests/extract-after-search-rates.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing API-client and normalization tests**
|
|
|
|
Cover:
|
|
- normalizing `<0.02%` without changing it
|
|
- normalizing `0.02 - 0.1%` into `0.02% - 0.1%`
|
|
- mapping the known API fields into the two display fields
|
|
- returning `bad-response` when either field is missing
|
|
- returning `timeout` for aborted requests and `request-failed` for non-OK/network failures
|
|
- issuing fetch with `credentials: "include"` and a timeout signal
|
|
|
|
- [ ] **Step 2: Run the tests to verify they fail**
|
|
|
|
Run: `npm test -- tests/market-api-client.test.ts tests/extract-after-search-rates.test.ts`
|
|
Expected: FAIL because the shared normalizer and market API client do not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal shared normalizer and API client**
|
|
|
|
Implement:
|
|
- `normalizeRateValue`
|
|
- market request URL builder for `get_author_ase_info`
|
|
- response mapper for `avg_search_after_view_rate` and `personal_avg_search_after_view_rate`
|
|
- explicit error classification for timeout vs request-failed vs bad-response
|
|
- reuse the shared normalizer from the detail extractor
|
|
|
|
- [ ] **Step 4: Run the tests to verify they pass**
|
|
|
|
Run: `npm test -- tests/market-api-client.test.ts tests/extract-after-search-rates.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Record the verification note**
|
|
|
|
Record that both the new market mapper tests and the existing detail extractor tests passed
|
|
|
|
### Task 4: Add Author-ID Extraction and List-Signature Utilities
|
|
|
|
**Files:**
|
|
- Create: `src/content/market/id-extractor.ts`
|
|
- Create: `src/content/market/list-signature.ts`
|
|
- Create: `tests/market-id-extractor.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing extractor tests**
|
|
|
|
Cover:
|
|
- extracting `authorId` from a row detail link
|
|
- extracting from fallback row attributes when available
|
|
- returning an explicit missing-id result when no stable source exists
|
|
- producing a deterministic list signature from author IDs plus relevant URL state
|
|
|
|
- [ ] **Step 2: Run the test to verify it fails**
|
|
|
|
Run: `npm test -- tests/market-id-extractor.test.ts`
|
|
Expected: FAIL because the market row extraction utilities do not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal extractor utilities**
|
|
|
|
Implement:
|
|
- detail-link parsing via `getStarIdFromUrl`
|
|
- fallback attribute parsing
|
|
- explicit `missing-author-id` results
|
|
- deterministic list-signature generation
|
|
|
|
- [ ] **Step 4: Run the test to verify it passes**
|
|
|
|
Run: `npm test -- tests/market-id-extractor.test.ts tests/get-star-id.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Record the verification note**
|
|
|
|
Record the passing extractor and shared ID tests
|
|
|
|
### Task 5: Insert the Two Columns and Render Row States
|
|
|
|
**Files:**
|
|
- Create: `src/content/market/dom-sync.ts`
|
|
- Create: `src/content/market/row-render.ts`
|
|
- Create: `src/content/market/row-state.ts`
|
|
- Create: `tests/market-dom-sync.test.ts`
|
|
- Create: `tests/market-row-render.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing DOM and rendering tests**
|
|
|
|
Cover:
|
|
- inserting two headers before the `操作` column
|
|
- inserting two cells before the action cell for each row
|
|
- tagging injected cells with stable `data-*` markers
|
|
- avoiding duplicate insertion on repeated sync
|
|
- rendering `加载中...`, success values, and `加载失败`
|
|
- exposing retryable error rows without creating per-cell state divergence
|
|
- avoiding any design that requires batch/cache layers to hold row elements after the sync pass ends
|
|
|
|
- [ ] **Step 2: Run the tests to verify they fail**
|
|
|
|
Run: `npm test -- tests/market-dom-sync.test.ts tests/market-row-render.test.ts`
|
|
Expected: FAIL because the market DOM modules do not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal DOM sync and row rendering**
|
|
|
|
Implement:
|
|
- table/header lookup
|
|
- injected header/cell creation
|
|
- row-state types
|
|
- row rendering with shared row-level status
|
|
- ephemeral DOM references only inside the sync/render step
|
|
|
|
- [ ] **Step 4: Run the tests to verify they pass**
|
|
|
|
Run: `npm test -- tests/market-dom-sync.test.ts tests/market-row-render.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Record the verification note**
|
|
|
|
Record the passing DOM and row-render test output
|
|
|
|
### Task 6: Add Cache, In-Flight Dedupe, Concurrency Control, and Row Retry
|
|
|
|
**Files:**
|
|
- Create: `src/content/market/batch-loader.ts`
|
|
- Create: `src/content/market/cache-store.ts`
|
|
- Create: `tests/market-batch-loader.test.ts`
|
|
- Modify: `src/content/market/api-client.ts`
|
|
- Modify: `src/content/market/row-state.ts`
|
|
|
|
- [ ] **Step 1: Write the failing batch-loader tests**
|
|
|
|
Cover:
|
|
- current page rows auto-enter loading state
|
|
- same `authorId` reuses success cache
|
|
- same `authorId` in-flight requests are deduplicated
|
|
- concurrency cap is honored
|
|
- failed rows transition to `加载失败`
|
|
- clicking one failed cell retries the whole row
|
|
- loader outputs can be dropped safely when the consumer reports a newer `listSeq`
|
|
|
|
- [ ] **Step 2: Run the test to verify it fails**
|
|
|
|
Run: `npm test -- tests/market-batch-loader.test.ts`
|
|
Expected: FAIL because cache/dedupe/loader behavior does not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal cache and loader pipeline**
|
|
|
|
Implement:
|
|
- in-memory cache entries keyed by `authorId`
|
|
- in-flight request reuse
|
|
- concurrency-limited scheduling
|
|
- row retry behavior
|
|
- timeout and request-failed transitions
|
|
- result delivery based on row metadata and callbacks, not long-lived DOM node ownership
|
|
|
|
- [ ] **Step 4: Run the test to verify it passes**
|
|
|
|
Run: `npm test -- tests/market-batch-loader.test.ts tests/market-api-client.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Record the verification note**
|
|
|
|
Record the passing loader and API-client tests
|
|
|
|
### Task 7: Build the Market Controller and Activate It on the Market Route
|
|
|
|
**Files:**
|
|
- Create: `src/content/market/index.ts`
|
|
- Modify: `src/content/index.ts`
|
|
- Create: `tests/market-controller.test.ts`
|
|
- Modify: `tests/content-entry.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing controller tests**
|
|
|
|
Cover:
|
|
- initial market page auto-load for all current rows
|
|
- pagination/filter/search/sort DOM changes trigger a fresh sync
|
|
- stale async results do not overwrite a newer list
|
|
- cached rows rehydrate immediately when they reappear
|
|
- the content entry now selects the market controller on market URLs
|
|
- writeback only succeeds when fresh DOM markers still match the returning `authorId` and `listSeq`
|
|
|
|
- [ ] **Step 2: Run the test to verify it fails**
|
|
|
|
Run: `npm test -- tests/market-controller.test.ts tests/content-entry.test.ts`
|
|
Expected: FAIL because the market controller and route activation do not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal market controller**
|
|
|
|
Implement:
|
|
- table observation
|
|
- `listSeq` or equivalent sync-session tracking
|
|
- fresh sync on list changes
|
|
- stale result suppression before DOM writeback
|
|
- re-scan current DOM on each sync instead of holding old row node references
|
|
- market route activation in `src/content/index.ts`
|
|
|
|
- [ ] **Step 4: Run the test to verify it passes**
|
|
|
|
Run: `npm test -- tests/market-controller.test.ts tests/content-entry.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Run the focused market suite**
|
|
|
|
Run: `npm test -- tests/market-api-client.test.ts tests/market-id-extractor.test.ts tests/market-dom-sync.test.ts tests/market-row-render.test.ts tests/market-batch-loader.test.ts tests/market-controller.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 6: Record the verification note**
|
|
|
|
Record that the market suite and route-entry tests passed
|
|
|
|
### Task 8: Update README and Run Final Verification
|
|
|
|
**Files:**
|
|
- Modify: `README.md`
|
|
- Modify: `tests/readme.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing README expectation**
|
|
|
|
Extend README tests to assert documentation now includes:
|
|
- market page enhancement scope
|
|
- the two inserted column names
|
|
- loading/failure/retry behavior
|
|
- how to manually verify on `creator/market`
|
|
- the fact that detail-page behavior still exists
|
|
|
|
- [ ] **Step 2: Run the test to verify it fails**
|
|
|
|
Run: `npm test -- tests/readme.test.ts`
|
|
Expected: FAIL because README does not document market-page behavior yet
|
|
|
|
- [ ] **Step 3: Update README minimally**
|
|
|
|
Add:
|
|
- market page support notes
|
|
- manual verification steps for both detail and market pages
|
|
- current stage-1 limitations
|
|
|
|
- [ ] **Step 4: Run the test to verify it passes**
|
|
|
|
Run: `npm test -- tests/readme.test.ts`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Run the full verification suite**
|
|
|
|
Run: `npm test`
|
|
Expected: all tests PASS
|
|
|
|
- [ ] **Step 6: Run a fresh production build**
|
|
|
|
Run: `npm run build`
|
|
Expected: build exits 0 and `dist/` contains the updated extension assets
|
|
|
|
- [ ] **Step 7: Record the verification note**
|
|
|
|
Record the passing full test run and build output
|