# Changelog of Page Metadata Extractor - SEO & RAG JSON Feed (`stefano_seggio/page-metadata-extractor`) Actor

- **URL**: https://apify.com/stefano\_seggio/page-metadata-extractor/changelog.md
- **Full Actor documentation**: https://apify.com/stefano\_seggio/page-metadata-extractor.md

## Changelog

### 1.1.1 - 2026-09-08

#### Fixed

- `src/state.ts` used `Actor.getValue()`/`Actor.setValue()` for delta
  state, which the Apify SDK docs confirm are shortcuts for the key-value
  store "associated with the current Actor run" - i.e. a fresh, run-scoped
  store every run, never shared across runs. Real cloud verification (two
  separate runs against the same URLs, each getting a different
  Key-value store ID and both classifying every page `NEW_URL`) caught
  this before it was called done. Fixed by opening a **named** store
  (`Actor.openKeyValueStore('primer-actor-delta-state')`), which persists
  across separate runs of this Actor. 1.1.0 was built, tested, merged and
  briefly deployed to the `latest` build tag with this bug present - local
  tests never exercised two genuinely separate runs against the SAME
  persistent store, so they passed despite it. Caught immediately by the
  cloud-verification step that's part of shipping any change in this repo
  (two real `apify actors call` runs against the same URLs, both showing
  `NEW_URL` when the second should have shown `UNCHANGED`) and fixed and
  redeployed within the same session before this was reported as done.

### 1.1.0 - 2026-09-08

#### Added

- Cross-run change detection, keyed by URL. Every scraped page now carries
  `eventType` (`NEW_URL` / `CONTENT_CHANGED` / `UNCHANGED`), `contentHash`
  and `previousScrapedAt` in the dataset. State (a content fingerprint per
  URL) persists in the default key-value store across runs.
- New optional input `onlyChanged` (default `false`, fully backward
  compatible). When enabled, a page is still crawled and its links still
  followed, but it is only delivered to the dataset - and charged - when
  it's new or its content differs from the last scrape of that same URL.
  Unchanged pages are skipped, so a scheduled re-run only pays for what
  actually changed.
- New "Change detection" dataset view surfacing `url`/`eventType`/
  `previousScrapedAt`/`scrapedAt`/`contentHash`.
- `test/fingerprint.test.ts`, `test/delta.test.ts`, `test/state.test.ts`:
  unit coverage for the new pure fingerprint/classification functions and
  state persistence. `test/main.test.ts` extended with two live-router
  scenarios: a repeat crawl of an unchanged page, and the same with
  `onlyChanged=true`.
- `npm run lint` added to CI (`.github/workflows/test.yaml`), matching the
  local validation loop already documented in this repo's `AGENTS.md`.

#### Changed

- None to the existing extraction logic, output fields, monetization
  event (`result`, unchanged), or pricing - this is a purely additive
  release. `contentHash`/`eventType`/`previousScrapedAt` are new fields
  appended to every existing output record, not a replacement for any of
  them.

#### Not added (and why)

- **No STATUS\_CHANGE or CLOSED event.** Unlike the fleet's
  registry-monitoring actors, this Actor crawls whatever URLs the caller
  supplies each run rather than discovering listings from an enumerable
  source. There is no register to walk completely, so there is no
  trustworthy way to say a URL is "gone" - and no lifecycle/status field
  on a scraped page to change in the first place. See `src/delta.ts` and
  the "Delta / change-detection mode" section of `AGENTS.md`.
- **No new paid event or price change.** `onlyChanged` reduces how many
  results are delivered (and therefore charged) using the *existing*
  `result` event - it does not add a second pricing tier or touch
  `pricingInfos` in Console. Apify allows only one "significant pricing
  change" per Actor per month with a 14-day notice period, and this
  Actor already has a live, differently-priced model from the rest of
  this developer's fleet - a new tier here is a deliberate future
  decision, not bundled into this release.
- **Version bumped to 1.1, not 2.0.** The rest of this developer's fleet
  uses "2.0" to mark a full delta-engine rewrite of previously
  stateless/parser-only actors. This release is strictly additive to an
  already-live, already-monetized Actor - no output field was removed or
  reshaped, no existing input field changed meaning - so a minor version
  is the honest label.
