What to build
syncRysk and syncHypersurface in src/js/18-chain-sync.js both mix three concerns: parsing raw API responses, applying trades to the global trades[] array, and triggering save() + render(). The apply logic — dedup by txHash, open/close split, close-trade matching, outcome correction — has real invariants and zero test coverage.
Extract the apply logic into a new file src/js/18b-chain-apply.js with two pure, dual-exported functions:
applyCloseTrade(tradesArray, closeTrade) → boolean
applyImportedTrades(tradesArray, openTrades, closeTrades, synced)
→ { added, closedCount, corrected }
applyImportedTrades behaviour:
- Pushes each open trade to
tradesArray after stripping the isClose flag (not part of the canonical trade shape)
- Calls
applyCloseTrade for each close trade; counts successes
- Runs outcome correction for both RYSK and HSFC: any trade in
tradesArray whose t.expiry ISO date is in the past and whose outcome is 'OPEN' is corrected to 'EXPIRED' and counted in corrected
- Mutates
synced Set in-place with newly processed txHashes
applyCloseTrade is extracted from 18-chain-sync.js and parameterised on tradesArray instead of closing over the global.
syncRysk and syncHypersurface become thin wrappers: fetch → parse → applyImportedTrades(trades, ...) → if added + closedCount + corrected > 0: save(); render(); saveSynced(synced).
Acceptance criteria
Blocked by
None — can start immediately.
What to build
syncRyskandsyncHypersurfaceinsrc/js/18-chain-sync.jsboth mix three concerns: parsing raw API responses, applying trades to the globaltrades[]array, and triggeringsave()+render(). The apply logic — dedup by txHash, open/close split, close-trade matching, outcome correction — has real invariants and zero test coverage.Extract the apply logic into a new file
src/js/18b-chain-apply.jswith two pure, dual-exported functions:applyImportedTradesbehaviour:tradesArrayafter stripping theisCloseflag (not part of the canonical trade shape)applyCloseTradefor each close trade; counts successestradesArraywhoset.expiryISO date is in the past and whoseoutcomeis'OPEN'is corrected to'EXPIRED'and counted incorrectedsyncedSet in-place with newly processed txHashesapplyCloseTradeis extracted from18-chain-sync.jsand parameterised ontradesArrayinstead of closing over the global.syncRyskandsyncHypersurfacebecome thin wrappers: fetch → parse →applyImportedTrades(trades, ...)→ ifadded + closedCount + corrected > 0:save(); render(); saveSynced(synced).Acceptance criteria
src/js/18b-chain-apply.jsexists withapplyCloseTradeandapplyImportedTrades, both dual-exportedisClosefield is absent from trades pushed totradesArraysyncRyskandsyncHypersurfacecontain no dedup, split, or correction logic — delegated entirely toapplyImportedTradestest/unit/chain-apply.test.jsadded with passing pure-node tests (no jsdom) covering: already-synced txHash is skipped; close trade matches open trade and sets outcome toCLOSED; unknown close trade with no match is a no-op (closedCount: 0); OPEN trade with past expiry is corrected toEXPIRED; correction applies to both RYSK and HSFC trades;isCloseabsent from pushed tradespython3 build.py --checkpassesnpm testpassesBlocked by
None — can start immediately.