- Support both HTML table and div-based grid layouts on creator/market - Harden DOM insertion by using actual parent nodes (prevents NotFoundError when page nesting differs from test fixtures) - Skip malformed/empty table rows instead of throwing on missing action cell - Add rowKey to BatchLoaderRow to align LoadRowsOptions typing - Add tests for div-grid sync and controller lifecycle at document_start
18 KiB
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.tsunless runtime evidence proves content-script requests are blocked. - Market async flows must not cache raw
HTMLElementreferences across request boundaries. Cache/request layers may store only row metadata such asauthorId,listSeq, and list signature. - The market API client should expose deterministic error reasons:
timeoutforAbortError,request-failedfor network/non-2xx/parse failures, andbad-responsefor 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.
- Hold the existing detail-page controller logic currently living in
- 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.
- Store success cache and in-flight request dedupe entries by
- 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
authorIdfrom row links and fallback attributes.
- Extract
- 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%and0.02 - 0.1%.
- Normalize known rate shapes such as
- 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:
-
createDetailContentControllerinsrc/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_resourcesfor 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*tocontent_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%into0.02% - 0.1% -
mapping the known API fields into the two display fields
-
returning
bad-responsewhen either field is missing -
returning
timeoutfor aborted requests andrequest-failedfor 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_rateandpersonal_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
authorIdfrom 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-idresults -
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
authorIdreuses success cache -
same
authorIdin-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
authorIdandlistSeq -
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
-
listSeqor 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-sectionwith onecontent-column - middle metrics:
.middle-columns - right sticky area:
content-sectioncontaining multiple.content-columns, with the last column being操作
This means DOM sync cannot treat each result as a <tr> 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.tsnpm testnpm 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.pushStateorhistory.replaceStateon the market page - coalesce repeated
MutationObservercallbacks 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.