# Changelog of Refurb Mac Sniper (`pyralislabs/refurb-mac-sniper`) Actor

- **URL**: https://apify.com/pyralislabs/refurb-mac-sniper/changelog.md
- **Full Actor documentation**: https://apify.com/pyralislabs/refurb-mac-sniper.md

## Changelog

### \[0.3.1] - 2026-06-12

#### Crash fix + the test gates that make this class impossible

- **Fix:** `ReferenceError: eventCounts is not defined` — a v0.3.0 refactor left the final log line referencing a variable that moved into records.js; the error boundary masked it (fired after pushData, pushed an error record, exited 0), so both local verification and the platform run looked superficially fine
- **Static gate:** ESLint (`no-undef`, `no-unused-vars`) added and wired as `pretest` — `npm test` now fails on this bug class before any run; verified it flags the exact shipped bug at src/main.js:225
- **Dynamic gate:** integration harness now captures child process output, and every successful-path E2E runs `assertRunSucceeded()` — zero error records, no "Actor failed" in output, success log line present
- New live E2E for `productUrls` mode (the mode the regression was reported in)

### \[0.3.0] - 2026-06-12

#### Every run returns the matched inventory (UX bug fix)

- **Bug:** after a watch's first run, re-running the same watch returned only a `run_summary` — matched listings were invisible unless `alwaysSnapshot` was on (Console runs of e.g. Studio+mini+MBP "returned nothing" while correctly matching 46 MacBook Pros; adding iMac looked like a fix only because the changed model set created a new watch with a fresh baseline)
- **Fix:** every run now pushes one `listing` record per currently-matched listing, with a `status` field (`baseline`/`active`/`new`/`returned`/`price-dropped`); alerts remain diff-based; the Output tab defaults to the Listings view
- `alwaysSnapshot` input removed (always on, never charged); `listing-snapshot` PPE event removed — pricing simplifies to `stock-check` + `alert-dispatched`
- Record assembly extracted to pure `src/records.js`; unit tests now cover the exact multi-model matrix scenarios from the bug report, and the integration idempotency test asserts listings are present on every run (it previously asserted the buggy behavior as correct)

### \[0.2.0] - 2026-06-12

#### Pricing standardization alignment ([Apify blog, June 2026](https://blog.apify.com/standardizing-actor-pricing/))

- Tiered event pricing adopted (FREE -> GOLD+ at -10/-20/-30%): stock-check $0.005->$0.0035, alert-dispatched $0.02->$0.014, listing-snapshot $0.0005->$0.00035
- The user's "Maximum cost per run" is now honored: charging stops at the limit (`eventChargeLimitReached`), delivered data is never clawed back, and a warning is logged

#### Platform schema-validation fix + full test suite

- **Test suite added per `docs/test-standards.md`**: 26 unit/contract tests (parsers, diff transitions, filters, webhook payloads, AJV dataset-schema contract incl. the null-bearing regression shapes) + 4 live integration tests (grid/product structure canaries, full child-process E2E with schema validation of every pushed record, forced-transition webhook delivery to a real local receiver). Fixtures captured from live apple.com with a refresh script.
- Unit tests caught and fixed a parser crash on malformed/null grid tiles

#### The schema-validation fix itself

- First platform run with MacBook Pro selected failed with `Schema validation failed` on pushItems: 28 of 46 MacBook Pro listings have no "Was" price on Apple's grid, so `originalPrice`/`savingsUsd`/`savingsPct` were null — and the dataset field schema typed them as strict numbers
- Fix (both sides): null/undefined fields are now stripped from records before push (absent = unknown), and all typed dataset-schema fields accept null as a backstop
- Reproduced and verified locally with AJV against live grid data before shipping

### \[0.1.0] - 2026-06-11

#### Initial release

- Watches Apple Certified Refurbished (Mac grid or exact product URLs) across 10 countries
- Pure-fetch engine via the embedded REFURB\_GRID\_BOOTSTRAP JSON — no browser, no proxy, seconds per run
- Spec filters: model family, minimum unified memory, price band, keyword
- Persistent per-watch state with four diff events: new-listing, back-in-stock, price-drop, gone
- Instant webhooks: Discord and Slack auto-detected (rich embeds/blocks), generic JSON with optional bearer token
- First-run baseline (no alert blast on new watches)
- Savings vs new ($ and %) straight from Apple's grid data
- PPE: stock-check $0.005/run, alert-dispatched $0.02, listing-snapshot $0.0005
