# 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 --- ## Runtime DOM Note After testing against the real logged-in `creator/market` page, the market result area was confirmed to be a column-major sticky grid instead of a row-major HTML table: - outer list root: `.base-author-list` - header root: `.section-wrapper.sticky-header.hide-scrollbar` - body root: `.section-wrapper.hide-scrollbar` - left author area: sticky `content-section` with one `content-column` - middle metrics: `.middle-columns` - right sticky area: `content-section` containing multiple `.content-column`s, with the last column being `操作` This means DOM sync cannot treat each result as a `` or assume the injected cells live under the same row container. The implementation now reconstructs each logical row by cell index across columns and stamps row metadata onto: - the author-side row anchor cell - the injected `单视频看后搜率` cell - the injected `个人视频看后搜率` cell ## Verification Note Verified in this workspace after the column-major DOM fix: - `npm test -- tests/market-dom-sync.test.ts tests/market-controller.test.ts` - `npm test` - `npm run build` All commands exited successfully. ## Runtime Stability Note After manual browser verification on the real `creator/market` page, an additional runtime issue was identified: refreshing the market page could leave the result area blank or stuck loading. The market controller was then hardened with three runtime constraints: - do not start observing the full market DOM while `document.readyState === "loading"` - do not override `history.pushState` or `history.replaceState` on the market page - coalesce repeated `MutationObserver` callbacks into one scheduled sync cycle instead of launching unbounded concurrent sync passes during page boot These constraints are now covered by `tests/market-controller.test.ts` together with the existing market-page behavior checks.