Patcher has module store search links, but users cannot see current store-by-store price or availability inside Patcher. This pilot should let users check module availability and prices store by store, with source links and freshness labels, while keeping unverified stores as search-link fallbacks.
The complete planning record below documents the applied additive Price Hub tables, approved public-read/service-role-write boundary, local store adapters and matching safeguards, module-detail Store prices UI, manual Edge Function deployment status, imported store coverage, and known deferred retailers.
Complete the approved Price Hub experimental snapshot workflow through a capped manual worker, store/listing health diagnostics, currency-aware parsing and validated backfill, bounded snapshot retention/compaction, and explicit external listing links. Preserve the existing freshness rules, conservative matcher, disappearance safeguards, and local crawl/import paths.
Keep current and stale data visibly distinct, show source/shipping-origin labels and availability states, preserve search fallbacks, and keep wrong-match handling internal/operator-owned.
Use the existing local crawler/importer and Supabase-backed snapshot model. Apply schema or data changes only after the documented backend preflight, approval, advisor checks, and before/after validation.
Run the existing Price Hub worker, crawler, importer, parser, currency, retention, backfill, and module-detail tests; perform read-only/advisor checks around approved data changes; smoke-test the Edge Function only after token configuration; and compare before/after counts, currency distributions, active-listing invariants, and retention/diagnostic placement.
No Cron, unrelated RLS/policy change, production-branch work, release, or push is authorized. Migration, backfill, retention, diagnostics, and generated-type work must follow schema-change preflight, approved scope, advisor checks, and before/after validation. Edge Function invocation remains operator-controlled.
This issue preserves the complete migrated planning document below. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Source: Supabase pricing/docs checked 2026-07-01.
Additive schema applied remotely 2026-07-02 under the schema-only approval gate. No RLS/policy/GRANT changes were included.
Registry for known retailers/search targets.
Curated canonical product URL per module/store.
Append-only observed price/availability history.
The local crawler is the current safe path for broad product discovery. It reads only the approved HTTPS store surfaces (WooCommerce Store API, BigCommerce product sitemaps plus product-page metadata, or Shopware sitemaps plus product-page metadata) and writes local files; it does not read/write Supabase, verify matches, deploy an Edge Function, create Cron, or add UI.
Curated local crawler matches can now be imported into the schema. The import keeps listing identity/current URL in module_store_listings and inserts a new append-only module_price_snapshots row on every run.
The first UI surface is implemented on module detail, directly under the primary module card on desktop so price/availability is visible without scrolling past the secondary rail.
Additive to the pilot; do not require broader UX changes. Correctness and safety first, then structural gaps.
-
2026-07-08T15:35+02:00 — Completed frontend-only cleanup after user declined wrong-product reporting: removed the Report mismatch prototype/local acknowledgement state and restored each Store prices listing row as a single explicit external link. Validation passed with focused module-price listing specs, pnpm lint, docs check, and git diff --check; no backend/schema/RLS/Cron/data/release/push work was performed.
-
2026-07-08T15:20+02:00 — Staged as next coordinator-loop task after User Area Cool List Preview Alignment because it has an approved frontend-only cleanup slice: remove the declined report-wrong-product prototype and restore row-wide external listing navigation. Keep Supabase Cron manual-only, and do not perform retention/backfill/schema/data mutations without explicit schema-change preflight and approval.
-
2026-07-07T21:20+02:00 — Report-wrong-product UI prototype checkpoint completed locally. Store prices rows were restructured from a row-wide anchor into a non-interactive row with an explicit external listing link, avoiding nested interactive controls while adding a low-weight Report mismatch button. The button only records local acknowledgement state and shows prototype-only/not-saved copy. Validation: pnpm test-headless --include="**/module-price-listings-card.component.spec.ts" passed. Scope stayed frontend-only: no backend calls, Supabase methods, migrations, RLS/policy changes, Edge Function deploy, Cron, release, push, or data writes.
-
2026-07-08T14:13+02:00 — User chose Price Hub retention policy: keep detailed daily snapshots for 30 days, then compact older data indefinitely using a Home Assistant-style long-term statistics/checkpoint strategy rather than hard-deleting all older price history.
-
2026-07-08T14:13+02:00 — User declined Price Hub wrong-product reporting as a user-facing workflow. Future work should remove the Report mismatch prototype, make the whole listing row open the external store listing again, and treat wrong matches as internal/operator-managed data quality.
-
2026-07-07T21:12+02:00 — Sparse Price Hub summary/header history checkpoint completed locally. Added an explicit-column read path for recent active-listing snapshots, a typed sparse-history summary helper, module detail data-service stream, and existing title price-badge rendering (~€399 · ↓5% 60d) with accessible tooltip caveats. Reviewer follow-up suppresses the badge when the recent snapshot read would be capped/truncated, rather than deriving a trend from an incomplete sample. Validation: pnpm test-headless --include="**/module-price-summary.utils.spec.ts", pnpm test-headless --include="**/module-minimal.component.spec.ts", pnpm test-headless --include="**/module-detail-data.service.spec.ts", node scripts/checks/check-docs.cjs, and pnpm lint passed. Scope stayed read-only: no migrations, RLS/policy changes, Edge Function deploy, Cron, release, push, or data writes.
-
2026-07-07T20:55+02:00 — Local crawler timeout hardening checkpoint: added fetchTimeoutMs (default 30s), abortable fetch wrappers, response-body timeout guards, and Shopify curl timeout/signal handling across WooCommerce, Shopify, BigCommerce/Shopware/custom metadata, and Signal Sounds inventory override paths. Validation: implementation subagent, independent reviewer approval, pnpm test:functions:price-hub-local-crawler, git diff --check, and pnpm lint passed. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed.
-
2026-07-07T15:08+02:00 — Deployed manual/on-demand snapshot-store-listings Edge Function, then redeployed version 2 after the auth-status follow-up, to Supabase project sozmatmywjpstwidzlss with verify_jwt=false because the function uses its own PRICE_HUB_SNAPSHOT_TOKEN authorization. No Cron was created. Unauthenticated smoke check fails closed because PRICE_HUB_SNAPSHOT_TOKEN is not configured in the deployed function environment yet; configure that secret before manual production invocation. Bad/missing request tokens return HTTP 401 once the secret exists, while missing server config remains HTTP 500.
-
2026-07-07T16:47+02:00 — SchneidersLaden stale-listing resolution completed. Root causes were stacked: skipped Shopware metadata pages blocked broad imports, active-listing refresh only read the first 500 DB rows, existing active listing URLs were not refreshed unless they were current strong matcher rows, duplicate crawled product URLs could duplicate active-refresh upserts, low-price observed rows stayed active with old snapshots, and 404 skipped metadata URLs were protected as if transient. Local refresh now warns on skipped metadata pages, paginates active listing reads at 500 rows, refreshes observed active listing URLs, dedupes refresh rows, marks observed unimportable active listings stale, and allows 404/410 skipped URLs to deactivate. Final targeted refresh imported 1910 fresh SchneidersLaden snapshots/listings and DB verification showed 1919/1919 active listings fresh, 0 active stale, and 0 active listings older than 30 days.
-
2026-07-07T14:42+02:00 — SchneidersLaden stale-listing diagnosis: the all-store refresh skipped import because refresh-local treated skipped Shopware metadata pages as a blocking partial-crawl condition. That was too strict for broken/non-product sitemap URLs and left previous snapshots visible. Skipped metadata pages are now warning-only for import while disappearance deactivation remains guarded; targeted live refresh imported 652 fresh SchneidersLaden snapshots/listings, skipped deactivation because 99 product pages lacked usable metadata, and left previous unmatched data intact.
-
2026-07-07T15:02+02:00 — Zero-decimal local code checkpoint: Price Hub parsing and display now use currency fraction digits instead of assuming /100 everywhere. Shopify catalog JSON, product metadata pages, Shopify product.js scheduled refresh normalization, estimated EUR rollups, and Store prices formatting now treat JPY/other zero-decimal currencies as whole minor units. This is a code/test-only checkpoint: no Supabase migration, remote backfill, Edge Function deploy, Cron, RLS/policy, release, push, or data write was performed. Existing over-scaled stored rows still need a separate read-only data audit plus reviewed backfill plan before any remote mutation.
-
2026-07-07T15:02+02:00 — Local refresh skipped-metadata policy hardened: crawls that skip individual metadata product pages now warn and may import matched rows, while disappearance deactivation remains guarded for those crawls. Truncation from max products/pages/sitemap files still blocks broad --store=all imports. No data write, release, push, deploy, Cron, or RLS/policy change was performed.
-
2026-07-07T14:42+02:00 — Local-only worker dispatch follow-up: snapshot-store-listings now includes Shopify product JSON and custom metadata adapters alongside WooCommerce, BigCommerce, and Shopware. Shopify scheduled refreshes build one /products/{handle}.js URL from external_handle or product URL slug and use the store currency_hint when selected; custom refreshes reuse the shared metadata normalizer with store-slug context. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed; Supabase Cron remains manual/on-demand only.
-
2026-07-07T14:33+02:00 — Follow-up from the daily local refresh: Whimsical Raps was recoverable as a local curl trust-store issue, not a bad public feed. The Shopify curl helper now retries TLS certificate failures with --ca-native, and a targeted whimsical-raps refresh imported 5 snapshots/listings with 0 deactivations. TechnoSynth still returns a Cloudflare 403 challenge from the WooCommerce Store API even with crawler headers; keep prior data intact and do not bypass bot protection.
-
2026-07-07T12:40+02:00 — Zero-decimal storage/backfill remains approval-gated: do not treat parseDecimalPriceMinor as a display-only cleanup. The safe default is to keep the current /100 frontend workaround until the Clockface Modular JPY backfill/migration is explicitly approved.
-
2026-07-07T12:40+02:00 — Snapshot retention/compaction and moving stable diagnostics out of module_price_snapshots.raw_meta are schema/data-retention changes, not passive cleanup. Keep append-only snapshots and compact diagnostics in raw_meta until approved.
-
2026-07-07T12:40+02:00 — module_store_listing_flags and any report-wrong-product affordance need schema/RLS/UX placement approval. Default is no new table or row-level action until that approval exists.
-
2026-07-07T12:40+02:00 — Sparse price history is a real user-visible addition; if approved later, the default placement is a tiny 30-day per-listing sparkline inside Store prices rows, with no new page or card.
-
2026-07-07T12:40+02:00 — Edge Function deploy and Supabase Cron remain separate approvals. Shopify/custom scheduled dispatch work can continue as code-only validation follow-ups, but they do not become Supabase approval items unless deploy/Cron is involved.
-
2026-07-07T14:07+02:00 — User approved a safe additive zero-decimal storage migration/backfill with validation queries/checks before and after. User also approved additive retention/diagnostics schema/backend work for snapshot retention state, scrape diagnostics, and failure metadata.
-
2026-07-07T14:07+02:00 — User approved report-wrong-product UI prototype work only; persistence/RLS/backend support remains deferred. Sparse price-history sparklines should first appear in the module Price Hub summary/header, not retailer rows.
-
2026-07-07T14:07+02:00 — User approved future Edge Function deploy/update after local tests and configuration checks pass. Supabase Cron is not approved; keep snapshots manual/on-demand for now.
-
2026-07-07T12:33+02:00 — Safe UI fallback checkpoint: extended the existing Search on > Other stores retailer chips for imported Price Hub stores that had configs/data but no chip. The batch uses straightforward Shopify /search?type=product&q= or WooCommerce ?s=&post_type=product search URLs and store-slug aliases so verified listings can still suppress matching chips while unverified/untracked stores remain searchable. No backend, migration, RLS, Edge Function, Cron, release, push, or new UI surface was changed.
-
2026-07-07T12:29+02:00 — Partial worker dispatch checkpoint: snapshot-store-listings now includes bigcommerce_metadata in scheduled adapter support and routes metadata product pages through the shared BigCommerce normalizer. This deliberately stops short of Shopify product JSON because scheduled endpoint semantics need separate validation, and stops short of custom because compatible metadata behavior must be proven per custom store. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed.
-
2026-07-07T12:28+02:00 — Store prices no longer hide the broader retailer search safety net unless a store has a verified current Price Hub listing. Candidate/stale/untracked store links remain under Search on > Other stores, preserving the previous search-only fallback for stores not yet trusted as current listings.
-
2026-07-07T12:14+02:00 — Behavior-preserving local importer split: import-local-snapshots.ts remains the public CLI/Supabase write facade, while pure import row planning and validation helpers live under scripts/price-hub/import-local/row-planning.ts. Public imports from refresh/tests continue through the facade; validation kept the 23/23 import-local test coverage passing.
-
2026-07-07T11:51+02:00 — Behavior-preserving local crawler split: catalog-crawler.ts now stays as the public facade used by local crawl/refresh/tests, while adapter and helper code lives under scripts/price-hub/crawlers/. Validation: pnpm test:functions:price-hub-local-crawler passed before and after the split.
-
2026-07-07T11:23+02:00 — Moved Price Hub matcher review/strong thresholds and shared noise terms into store-configs.ts match defaults, with matcher options accepting the approved store config. Local crawl/refresh now pass the store into matching; --min-score remains an explicit override. Found Sound preorder tags stay valid availability metadata via ignoredMatchNoiseTags, while preorder in titles/handles still triggers normal noise penalties.
-
2026-07-07T11:19+02:00 — Added scripts/price-hub/README.md as the local store-adapter onboarding guide, covering approved config shape, adapter choice, crawl/match/dry-run/live import gates, service-role env handling, partial-crawl/deactivation safety, noise-term/productBrandHint guidance, shipping origin/currency review, and the no Edge/Cron/deploy boundary.
-
2026-07-01T11:08+02:00 — User requested an experimental pilot plan for store-by-store module price/availability tracking, with daily Supabase Edge Function execution if cost/Free-plan feasibility checks out.
-
2026-07-01T11:12+02:00 — Supabase organization MAIN is on the Free plan and Patcher project sozmatmywjpstwidzlss is active/healthy. Supabase Free includes 500,000 Edge Function invocations/month, 100 functions/project, 150s function wall-clock, 256 MB memory, 500 MB database, and 5 GB egress. A daily capped pilot should fit Free-plan limits.
-
2026-07-01T11:12+02:00 — Pilot environment recommendation: Supabase Cron invoking a DB-queued Edge Function once daily. Do not add a separate server or browser automation for the pilot.
-
2026-07-01T11:12+02:00 — Parser priority: start with WooCommerce Store API stores (Elevator Sound, New Groove), then Shopify JSON stores (Control, Found Sound, Synthshop), then Signal Sounds EU BigCommerce metadata.
-
2026-07-01T11:50+02:00 — MVP local-preparation layer drafted: local additive migration, table constants, WooCommerce-only Edge Function worker shape, and parser fixtures/tests. module_store_listings seed rows remain blocked on real module IDs plus verified canonical product URLs; the next approval gate is remote migration application.
-
2026-07-01T11:50+02:00 — Review findings addressed before handoff: WooCommerce fetches have a timeout, search result normalization fails closed instead of snapshotting a first-product fallback, and the worker fails closed if PRICE_HUB_SNAPSHOT_TOKEN is not configured.
-
2026-07-01T12:03+02:00 — Created verified checkpoint commit 3918cf03 for the local MVP preparation and stopped at the remote migration approval gate.
-
2026-07-01T13:16+02:00 — Local-only refinement extracted Deno-free snapshot worker helpers and Node tests for limit parsing, fail-closed auth, WooCommerce URL construction/slug fallback, failure backoff/stale threshold, and error message clipping. recordFailure now checks update errors and surfaces them in the listing result instead of silently ignoring them. No migration, RLS/policy/GRANT, Edge Function deploy, Cron, UI, push, or release was performed.
-
2026-07-01T13:16+02:00 — Created verified checkpoint commit e5998b2d for the local-only worker helper refinement. The next step remains blocked on remote migration approval.
-
2026-07-01T14:27+02:00 — Added a local-only mode=probe PoC path for the snapshot Edge Function. It is intentionally DB-free, requires explicit probe JSON input, returns the WooCommerce API URL plus normalized snapshot, and keeps deploy/migration/RLS/Cron/UI/push gates closed.
-
2026-07-01T14:27+02:00 — Review flagged arbitrary probe hosts as an SSRF risk; probe fetch targets are now restricted to HTTPS on the approved WooCommerce pilot hosts only.
-
2026-07-01T14:27+02:00 — Created verified checkpoint commit 2a04f353 for the no-database probe mode. The next step remains blocked on explicit deploy/push approval; remote migrations, RLS/policy/GRANT changes, Cron, and UI remain unapproved.
-
2026-07-02T09:38+02:00 — Added local-only crawler/matcher scripts for approved WooCommerce pilot stores. This checkpoint keeps Price Hub data discovery in local JSON files under tmp/price-hub, treats Supabase translation as a later decision, and never marks a product/module match as verified.
-
2026-07-02T09:38+02:00 — Review flagged zero-score generic module rows as a matches.json explosion risk; matcher now drops zero-score candidates and keeps ignored rows only when there was real positive signal.
-
2026-07-02T09:38+02:00 — Created verified checkpoint commit e7e3bd1b for the local catalog crawler/matcher. The next step remains local analysis of crawler output or explicit approval for any remote/Supabase/UI work.
-
2026-07-02T10:04+02:00 — Live New Groove inspection found real module products where the WooCommerce Store API returns price, regular_price, and sale_price as 0 with empty price_html. The normalizer now treats zero prices as unknown/suspicious (priceAmountMinor: null, rawMeta.priceWasZero: true) so Patcher never shows impossible 0.00 EUR module prices.
-
2026-07-02T10:17+02:00 — Added Signal Sounds as two local-only stores: signal-sounds-uk (signalsounds.com, GBP) and signal-sounds-eu (signalsounds.eu, EUR). Both crawl product sitemap URLs and normalize BigCommerce product metadata, preserving separate price/availability observations per domain.
-
2026-07-02T10:33+02:00 — Added SchneidersLaden as a local-only Shopware metadata store. The crawler follows the allowed sitemap index to its gzipped URL set, skips non-product/category pages without price metadata, reports skipped broken product URLs, and normalizes EUR price plus stock text from product pages.
-
2026-07-02T11:36+02:00 — Signal Sounds availability metadata can be stale: Melotus Versio EU reported og:availability=instock, then the runtime widget rendered "Sorry, this item is out of stock". The local crawler now extracts SKU (NSEE70 in the observed case), calls Randem Retail location inventory, and overrides availability using the storefront-specific warehouse quantity.
-
2026-07-02T11:43+02:00 — Review approved the Signal Sounds availability fix with no findings. A real-page local crawl against Melotus Versio EU returned availability: out_of_stock, signalSoundsStoreExternalId: SS Europe, and signalSoundsInventoryQuantity: 0; writing corrected snapshots to Supabase remains an explicit approval gate.
-
2026-07-02T11:40+02:00 — User approved and received one curated SchneidersLaden import for module 4524 (ADDAC812VU): product URL https://schneidersladen.de/en/addac-systems-812v-led-voltage-meter, 139.00 EUR, initially marked in_stock, source scraper. This was not a broad Schneiders import.
-
2026-07-02T11:43+02:00 — User caught that the SchneidersLaden ADDAC812VU page is actually archived. The parser now scans availability by priority rather than first text position, so Product is archived / Sorry folks beats earlier generic in stock text. A corrected latest snapshot was appended with availability = discontinued.
-
2026-07-02T14:13+02:00 — User flagged panel variants for AJH Synth Finaliser R-EQ (4263): Silver is in stock while Black is preorder. The parser now records panel variants, the importer prefers the best available equal-score panel variant, and the latest 4263 snapshot records Silver as selected plus Black as an alternate in rawMeta.
-
2026-07-02T14:30+02:00 — Broader SchneidersLaden preview seed imported 501 deduped listing/snapshot rows from a 1500-product local crawl. The import surfaced a real schema constraint interaction: the same product URL can strongly match multiple modules, so importer output now deduplicates by normalized product URL after per-module selection and records alternate module candidates in snapshot metadata.
-
2026-07-02T14:30+02:00 — Module detail Store prices moved from below the secondary Search on area to directly under the primary module card. Availability pills now include colored status dots so full-screen desktop browsing can scan current stock state quickly.
-
2026-07-02T15:07+02:00 — User caught SchneidersLaden Nibbler (4831) as a false preorder. The actual page has schema.org/InStock, a green delivery block, and an enabled Add to cart button; the parser had matched preorder from a manufacturer link in the description. Shopware availability now prefers structured/buy-widget in-stock signals over descriptive preorder text, and a corrected latest snapshot marks Black in stock with Silver recorded as an in-stock alternate.
-
2026-07-02T15:09+02:00 — User granted autonomy to fully crawl the currently approved Signal Sounds EU and SchneidersLaden sources and correct stale DB data. The crawler now applies Randem inventory in batches rather than per product, accepts storeName as fallback for EU rows missing storeExternalId, treats shippable positive Randem rows as available, and marks missing authoritative Signal stock as unknown.
-
2026-07-02T15:09+02:00 — Full imports appended 1339 Signal EU snapshots and 1841 SchneidersLaden snapshots after skipping normalized-URL conflicts already linked to other modules. Finaliser R-EQ (4263) and Nibbler (4831) now show both stores in stock, while ADDAC812VU (4524) remains SchneidersLaden discontinued.
-
2026-07-02T18:25+02:00 — Elevator Sound official-domain crawl/import completed. www.elevatorsound.com returned 2887 normalized WooCommerce Store API products over 29 pages; after filtering with --include-ignored-matches=false, matcher output contained 1927 candidates, 1708 strong candidates, and 1077 accepted deduped import rows. The import upserted 1077 active Elevator Sound listings and appended 1077 snapshots without schema, RLS/policy/GRANT, Edge Function, Cron, push, or release changes.
-
2026-07-02T18:25+02:00 — Elevator Sound crawl inspection found condition/order/accessory pages (B-Stock, Ex-Demo, pre-order, accessory) that otherwise scored as strong exact matches, plus Tiptop 909-family false positives from shared numeric compact-code aliases. The matcher now detects hyphen/space noise terms and only uses bare compact-code aliases when the code contains letters, preserving ADDAC812VU-style matching while blocking shared numeric suffixes.
-
2026-07-02T18:29+02:00 — Runtime snapshot of /modules/details/4263 confirms Elevator Sound renders in Store prices with £380.00, Available now, and United Kingdom, alongside four other store rows for comparison.
-
2026-07-02T18:53+02:00 — Control was selected as the next easiest store based on live probes. www.ctrl-mod.com/products.json is a straightforward Shopify catalog; Found Sound also has Shopify JSON but its first sample and product taxonomy are heavily used/condition oriented; Synthshop products.json failed TLS handshakes from this environment. Control official pages identify the store as Brooklyn, New York / US.
-
2026-07-02T18:53+02:00 — Added shopify_product_json local-crawler support and Control normalization. The normalizer records USD prices from decimal Shopify variant prices, product URL from handle, availability from variants unless pre-order tags/titles are present, image URL, vendor, product type, tags, selected variant ID/title/SKU, and available variant IDs.
-
2026-07-02T18:53+02:00 — Control full crawl/import completed without schema/RLS/policy/GRANT/deploy/Cron/push/release changes. products.json returned 3930 normalized products over 16 pages; matcher output contained 658 candidates and 630 strong candidates; importer accepted 524 deduped rows, upserted 524 active listings, and appended 524 snapshots. Latest accepted distribution is 88 in_stock and 436 out_of_stock.
-
2026-07-02T19:17+02:00 — Found Sound was selected after Control based on live probes. https://foundsound.com.au/products.json works but is broad, condition-heavy, and rate-limited; the official https://foundsound.com.au/collections/eurorack/products.json surface is narrower and returned 2737 normalized Eurorack products over 11 pages. Synthshop remained blocked by TLS/connection failures from this environment.
-
2026-07-02T19:17+02:00 — Official Found Sound shipping policy says the store is a brick-and-mortar shop with pickup at 110 Albert Street, Brunswick East, Victoria 3057 and worldwide shipping, so the remote/local store row uses country_code = AU, currency_hint = AUD, and adapter shopify_product_json.
-
2026-07-02T19:17+02:00 — Found Sound full crawl/import completed without schema/RLS/policy/GRANT/deploy/Cron/push/release changes. The matcher output contained 1749 candidates; importer accepted 1100 deduped strong rows after excluding used/consignment/accessory hazards, upserted 1100 active listings, and appended 1100 snapshots through the existing import-local path. Latest accepted distribution is 39 in_stock, 1026 preorder, and 35 out_of_stock.
-
2026-07-02T19:17+02:00 — Found Sound hazard handling differs from Control: Found Sound uses the configured preorder tag as match-noise suppression only, so valid upcoming-stock rows stay importable while preserving availability = preorder. Used/consignment rows, covers, stackcables, and power adapter accessories still fall below importable status.
-
2026-07-02T19:49+02:00 — Elevator Sound back-order correction: module 2100 exposed that WooCommerce can report is_in_stock: true while stock_availability.text says Available on back-order. The parser now treats backorder, back-order, and back order in stock status/text/class as backorder before considering in-stock flags.
-
2026-07-02T19:49+02:00 — After the parser fix, the Elevator catalog recrawl normalized https://www.elevatorsound.com/product/intellijel-multi-fx-1u/ as backorder; 539 latest Elevator snapshots with back-order evidence were corrected by appending new backorder snapshots. Runtime /modules/details/2100 now shows Elevator Sound as Backorder.
-
2026-07-02T19:54+02:00 — Superseded during follow-up QA: availability classification preserves explicit Shopify preorder tags even when those tags are ignored for match-noise scoring. Do not convert Found Sound preorder catalog entries into available-now rows solely because a selected Shopify variant is available.
-
2026-07-02T21:38+02:00 — Added five more official Shopify-backed stores to the local crawler/import path and database registry without UI, schema, RLS/policy/GRANT, deploy, Cron, push, or release changes: Rubadub (GB/GBP), Detroit Modular (US/USD), Nightlife Electronics (CA/CAD), Clockface Modular (JP/JPY), and Moog Audio (CA/CAD).
-
2026-07-02T21:38+02:00 — Store discovery choices: Rubadub uses /collections/eurorack/products.json; Detroit Modular uses /collections/eurorack-modules/products.json because /collections/eurorack/products.json was empty; Nightlife Electronics uses /collections/modular/products.json because the narrower Eurorack collection only produced 25 accepted rows; Clockface Modular uses broad /products.json because no simple module collection handle was exposed; Moog Audio uses /collections/modules/products.json because the guessed Eurorack collection was empty.
-
2026-07-02T21:38+02:00 — Imported append-only snapshots/listings for the five stores after dry-runs: Rubadub 454, Detroit Modular 1337, Nightlife Electronics 1141, Clockface Modular 758, and Moog Audio 421. Current active coverage is 12,378 active imported store listings across 3,058 distinct modules.
-
2026-07-02T21:38+02:00 — Shopify crawler reliability and correctness fixes from the five-store run: use curl for Shopify JSON fetches because urllib hit 429s where curl succeeded, make Shopify catalog path/currency/noise config store-specific, expand matcher noise for open-box/special-order/opening-soon/no-longer-available hazards, and normalize Shopify discontinued/no-longer-available text to explicit discontinued availability.
-
2026-07-03T10:55+02:00 — User asked for five more store crawlers/imports and explicitly prioritized correct database data over UI. Added RobotSpeak, Cicada Sound, Intellijel, Schlappi Engineering, and Zlob Modular to the approved local crawler list and remote store registry without UI, schema, RLS/policy/GRANT, deploy, Cron, push, or release changes.
-
2026-07-03T10:55+02:00 — Imported append-only snapshots/listings for the five stores after one-by-one crawls and dry-runs: RobotSpeak 142, Cicada Sound 293, Intellijel 85, Schlappi Engineering 10, and Zlob Modular 1. Active Price Hub coverage is now 13,391 active listings across 3,170 distinct modules and 27 active stores.
-
2026-07-03T10:55+02:00 — Database QA after the five-store run found zero active missing latest snapshots, zero active latest rows without price/currency, and zero active duplicate store/product URLs for the new stores. Zlob remains intentionally sparse because the importer rejects low/no-price direct-store rows unless they are clean price-bearing module observations.
-
2026-07-03T11:11+02:00 — User clarified that the next follow-up should be more centered in Europe. Mainland retailer probing found Soundium as a stable Shopify-backed Lithuania retailer, while Music All In rate-limited before stable output, Sonic Sales produced zero safe module matches, Modular Synth Lab produced zero importable rows from a parts-heavy WooCommerce catalog, and multiple larger mainland shops returned 403/404/empty/non-crawler-friendly responses.
-
2026-07-03T11:11+02:00 — Imported append-only snapshots/listings for three clean mainland-Europe feeds after dry-runs: Soundium 4, Nano Modules 14, and Dreadbox 9. Database QA found zero active missing latest snapshots, zero active latest rows without price/currency, and zero active duplicate product URLs for the three stores. Active Price Hub coverage is now 13,418 active listings across 3,170 distinct modules and 30 active stores.
-
2026-07-03T12:00+02:00 — Deep technical review of the Price Hub feature completed (docs-only, no code changes). Live Supabase inspection confirmed the three Price Hub tables have RLS disabled with anon+authenticated holding full DML — escalated as the P0 approval item. Correctness gaps identified: (a) CURRENCY_TO_EUR_RATE in module-price-listings-card.utils.ts and its duplicate in module-price-summary.utils.ts omit AUD/CAD/JPY, silently dropping ~2,700 listings from price sort and market-price rollup; (b) parseDecimalPriceMinor is not currency-aware, so JPY (and future KRW/HUF/ISK/VND/CLP) rows are stored 100× too large and only render correctly because the frontend also divides by 100; (c) no stale-threshold enforcement on read even though Trust rules document one; (d) products removed from a store's catalog stay active forever; (e) the snapshot-store-listings Edge Function is hardcoded to adapter_kind = 'woocommerce_store_api' so ~72% of current listings would never refresh if cron were enabled; (f) local importer sets next_check_at = now() for every row, which would produce a 13k-row backlog against the worker's 20/day cap; (g) cacheBust(['priceHub']) is wired for but never called; (h) catalog-crawler.ts (880 lines) is near the 1000-line layering error and import-local-snapshots.ts (745 lines) is already past the 500-line warning. Integrated as MVP hardening + extended Structural + prioritized Polish checklists, plus new Risks rows and the P0 approval-queue entry above. No code was touched.
-
2026-07-03T12:52+02:00 — Audited the remaining right-column retailer fallbacks after Store prices/search integration. Added Synthshop through Shopify products.json (NOK), and added a generic custom recursive sitemap/product-metadata crawler for Exploding Shed, Milk Audio Store, Machineroom, and Escape From Noise. The generic path reuses product metadata tags, adds JSON-LD offer parsing for Milk Audio, sends the crawler user-agent for metadata pages, and supports store-level URL include/exclude filters. Smoke crawls wrote normalized products for all five; match smoke found candidates for Synthshop, Escape From Noise, Milk Audio Store, and Exploding Shed. Machineroom produced normalized products in the 25-product smoke but no early candidates, so it needs broader QA before import. Perfect Circuit remains blocked by Cloudflare/bot responses, Thomann by robots/Cloudflare/429/no stable feed, and Patchwerks by expired TLS plus no accessible Shopify JSON endpoint.
-
2026-07-03T18:07+02:00 — User caught https://schneidersladen.de/en/tiptop-audio-vca showing available while the live Schneider page is preorder/ordered. Read-only Supabase inspection confirmed listing 7475 was a due shopware_metadata row with latest stale in_stock; live/parser diagnosis confirmed normalizeShopwareProductPage returns preorder. The local-only worker now includes Shopware metadata rows in scheduled eligibility and dispatches them through the shared Shopware parser. Added regression coverage for both worker adapter eligibility and the Schneider ordered/preorder page shape, then appended corrected snapshot 38487 (145.00 EUR, preorder, source = manual) for listing 7475. At this checkpoint Edge Function deploy/Cron were still unapproved; this was superseded by the 2026-07-07 approval split where deploy was approved after validation and Cron stayed manual-only.
-
2026-07-06T19:34+02:00 — User approved the P0 public-read/admin-write safety gate: anonymous and logged-in users should read Price Hub tables, RLS should protect writes, and crawler writes should use the usual administrator/service-role credential path.
-
2026-07-06T19:37+02:00 — User made fixing the crawler the new top priority and approved setting up whatever is needed to write clean Price Hub data to the database with local credentials.
-
2026-07-06T19:38+02:00 — Applied remote migration secure_price_hub_rls: RLS enabled on stores, module_store_listings, and module_price_snapshots; anon/authenticated reduced to SELECT; public select policies added; service_role write grants retained; tg_price_hub_set_updated_at now fixes search_path = public.
-
2026-07-06T19:39+02:00 — Local import/refresh scripts now default live write credentials to service-role key aliases only, reject anon/authenticated JWT keys before live writes, and support PRICE_HUB_ENV_FILE so the crawler can use an external local env file without copying secrets into the repo. Secret values were not printed or committed.
-
2026-07-06T19:40+02:00 — Verified the RLS-protected live write path with PRICE_HUB_ENV_FILE=/Users/polyterative/Code/Scraper/.env pnpm price-hub:refresh-local --store=synthshop --max-pages=1: 88 snapshots/listings imported and one conflicting URL was skipped. Shopify/WooCommerce --max-products caps now work.
-
2026-07-07T10:35+02:00 — Reconciled bounded partial refresh policy: explicit single-store bounded partial refreshes and dry-runs are allowed with a non-blocking warning for local pilot work, but broad live --store=all --max-products=... refreshes still fail closed with a BLOCKING warning to avoid accidental incomplete all-store imports.
-
2026-07-07T10:40+02:00 — Frontend Price Hub estimated FX handling now uses one shared helper for module detail price listing ordering and recent-market summaries. It covers existing EUR/USD/GBP/CHF plus seeded AUD/CAD/JPY/NOK stores and keeps labels approximate; no parser, backfill, migration, or storage semantics changed.
-
2026-07-07T10:46+02:00 — Module detail Store prices now enforces the Trust-rule freshness policy in UI utilities: rows older than 14 days by last_checked_at (or latest snapshot observed_at fallback for legacy rows) stay visible but show Last seen/Stale data, lose current-price/best-price treatment, and sort after current known prices. The importer did not get cacheBust(['priceHub']) because cacheBust is an Angular app-runtime cache invalidation operator; local Node import scripts run outside browser sessions and cannot invalidate already-open client caches without adding a remote notification/channel, which is out of scope for this no-schema/no-remote-change hardening pass.
-
2026-07-07T10:52+02:00 — Safe product-disappearance deactivation is local-importer only and schema-free: full-catalog evidence requires no --max-products, no explicit bounds, at least 25 crawled product URLs, and at least 5 accepted import rows. Eligible imports set missing active listings to active = false, verification_status = stale, and compact last_error = not_seen_since_full_catalog:YYYY-MM-DD; dry-run uses the same planner without writes. This intentionally skips small/partial crawls rather than risking a store wipe.
-
2026-07-07T11:06+02:00 — Reviewer follow-up tightened disappearance deactivation evidence: WooCommerce/Shopify crawls that stop on a full maxPages page are treated as incomplete even when the default cap was used, and sitemap/metadata crawls with skipped product pages block deactivation. These conditions still allow imports but skip stale-listing writes.
-
2026-07-07T11:31+02:00 — Local importer scheduling now avoids cron-backlog bursts: imported/upserted listings keep import-time last_checked_at and last_success_at, while next_check_at is deterministically staggered over the following seven days from stable store ID, module ID, and normalized product URL. The helper is exported and covered for determinism, bounds, and distribution; no remote data was written.
-
2026-07-07T11:38+02:00 — Store price row hero colors now use a deterministic muted HSL color derived from normalized store slug when no hand-tuned STORE_HERO_COLORS override exists. This avoids maintaining a 27+ store manual palette while keeping the original overrides stable.
Price Hub remains manual/on-demand; no Supabase Cron job is authorized. Any migration, backfill, retention, diagnostics, RLS, policy, or generated-type change must follow the documented approval, schema-change preflight, advisor, and before/after validation gates. No unrelated production-branch work, release, or push is authorized by this plan.
This issue preserves the complete migrated planning document. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Source: internaldocs/workflow/plans/done/price-hub-experimental-store-snapshot-pilot.md
Migration status: migrated from local documentation
Status and ownership
Problem and user intent
Patcher has module store search links, but users cannot see current store-by-store price or availability inside Patcher. This pilot should let users check module availability and prices store by store, with source links and freshness labels, while keeping unverified stores as search-link fallbacks.
Goals
Non-goals
Current system and context
The complete planning record below documents the applied additive Price Hub tables, approved public-read/service-role-write boundary, local store adapters and matching safeguards, module-detail Store prices UI, manual Edge Function deployment status, imported store coverage, and known deferred retailers.
Proposed solution
Complete the approved Price Hub experimental snapshot workflow through a capped manual worker, store/listing health diagnostics, currency-aware parsing and validated backfill, bounded snapshot retention/compaction, and explicit external listing links. Preserve the existing freshness rules, conservative matcher, disappearance safeguards, and local crawl/import paths.
Product and UX
Keep current and stale data visibly distinct, show source/shipping-origin labels and availability states, preserve search fallbacks, and keep wrong-match handling internal/operator-owned.
Technical approach
Use the existing local crawler/importer and Supabase-backed snapshot model. Apply schema or data changes only after the documented backend preflight, approval, advisor checks, and before/after validation.
Implementation layers
Surfaces and files
Expected surfaces are the Price Hub crawler/importer,
snapshot-store-listingsworker, Supabase Price Hub tables and migrations, generated backend types, module-detail Store prices card, and related tests and operational scripts. The complete file and implementation record is retained below.Dependencies and sequencing
internaldocs/patterns/BACKEND_METHODS.mdschema-change preflight before migration, backfill, retention, or diagnostics schema work.Acceptance criteria
raw_meta.Validation strategy
Run the existing Price Hub worker, crawler, importer, parser, currency, retention, backfill, and module-detail tests; perform read-only/advisor checks around approved data changes; smoke-test the Edge Function only after token configuration; and compare before/after counts, currency distributions, active-listing invariants, and retention/diagnostic placement.
Risks and mitigations
Decisions and open questions
Decision log
Open questions
Production and release gates
No Cron, unrelated RLS/policy change, production-branch work, release, or push is authorized. Migration, backfill, retention, diagnostics, and generated-type work must follow schema-change preflight, approved scope, advisor checks, and before/after validation. Edge Function invocation remains operator-controlled.
Related issues
Documentation impact
This issue preserves the complete migrated planning document below. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Complete planning record
MEDIUM: Price Hub — Experimental Store Snapshot Pilot
Status: MVP persistence/display checkpoint active. The additive schema has been applied remotely; corrected SchneidersLaden ADDAC812VU / Finaliser R-EQ / Nibbler snapshots plus Signal Sounds EU, SchneidersLaden, New Groove, Elevator Sound, Control, Found Sound, Rubadub, Detroit Modular, Nightlife Electronics, Clockface Modular, Moog Audio, Noisebug, Post Modular, Pusherman Productions, Thonk, After Later Audio, Patch Point, ALM / Busy Circuits, Instruo, WMD, Michigan Synth Works, RobotSpeak, Cicada Sound, Intellijel, Schlappi Engineering, Zlob Modular, Soundium, Nano Modules, Dreadbox, and Synthshop crawl imports are visible on module detail. Signal Sounds lazy stock detection, batched Randem inventory, shippable-location fallback, SchneidersLaden archived-page detection, Shopware structured-stock detection, Shopware panel-variant diagnostics, same-product match deduplication, memory-safe full-catalog matching, compact module-code matching, New Groove used/preorder/no-price cleanup, Elevator Sound B-stock/ex-demo/preorder/accessory filtering, Shopify used/consignment/preorder/accessory/status noise filtering, explicit Shopify discontinued-state handling, vendor-backed Shopify exact-title matching, direct-store brand hints, DIY/parts/apparel/pedal noise filtering, implausible price filtering, generic custom sitemap/product-metadata crawling, JSON-LD offer parsing, explicit shipping-origin labels, and module-detail status-dot/filter/order/search-fallback display have been fixed in the local crawler/importer/UI.
Search on > Other storesfallback chips now cover the imported Price Hub store batch where straightforward Shopify/WooCommerce site-search URLs are available. Control uses the official Brooklyn/US Shopify storefront. Elevator Sound uses the official UK.comWooCommerce store after the previous.eutarget failed DNS. Synthshop uses Shopifyproducts.jsonand has a service-role live import checkpoint. Exploding Shed, Milk Audio Store, Machineroom, and Escape From Noise have smoke-tested custom sitemap/product-metadata crawlers but no remote imports yet. Thomann, Perfect Circuit, and Patchwerks remain deferred because no stable crawler-friendly public feed was found, requests were blocked/rate-limited, TLS was invalid, or bot responses were returned. The P0 RLS/policy/GRANT/search_path gate is approved and applied remotely; Edge Function deploy is approved after validation, cron remains manual-only, user-facing wrong-product reporting is declined and should be removed, and push/release remain unapproved.Current checkpoint: local MVP preparation has been refined with Node-testable snapshot worker helper logic, a local-only no-database probe mode, WooCommerce/BigCommerce/Shopware/Shopify/custom local crawlers, a schema-only Supabase persistence layer, and a module-detail Store prices card. The scheduled worker now dispatches WooCommerce Store API, Shopify product JSON, BigCommerce metadata, Shopware metadata, and custom metadata listings locally; this remains undeployed. Local crawlers now enforce abortable fetch/body-read timeouts so a blocked store cannot hang a refresh indefinitely. The crawler can remain local-first long-term; Edge Function deploy/update is approved after local tests and configuration checks, while Cron remains declined for now.
Why: Patcher already has module store search links and one
modules.store_urlfield, but users cannot see current store-by-store price or availability inside Patcher. This pilot proves whether Patcher can collect daily price/stock snapshots with the existing Supabase-backed, backendless architecture before committing to a full Price Hub.Final objective: let users check module availability and prices store by store, with source links and freshness labels, while keeping unverified stores as search-link fallbacks.
Pilot hypothesis: local-first crawling can discover and review store/module matches safely before deciding whether any Supabase-backed daily snapshot worker is still needed. If translated later, Supabase Edge Functions plus Supabase Cron should be enough for a small daily store snapshot worker on the current Free organization, provided the worker is DB-queued, capped, and launched with one or two easy stores first.
Cost and feasibility gate
Current Supabase state observed 2026-07-01
MAIN/colourful-peach-caterpillarsozmatmywjpstwidzlsseu-central-1Supabase Free limits relevant to this pilot
Source: Supabase pricing/docs checked 2026-07-01.
Cost conclusion: the daily experimental pilot should fit the Free plan if it stays small: one scheduled Edge Function, one batch per day, no browser rendering, no high-frequency refreshes, and no broad all-store crawl.
Cost stop conditions:
Stores checked for parser ease
Easiest first
woocommerce_store_apiwww.elevatorsound.com, whose WooCommerce Store API works. Official contact page lists Elevator Sound at 74 Stokes Croft, Bristol, BS1 3QY; treat shipping origin asGB, currency asGBP. The earlier.eutarget was invalid and failed DNS.woocommerce_store_api0with emptyprice_html; normalize those as unknown/suspicious, not as0.00 EUR. Italian used/preorder listings (usato,occasione,prenotazione,preordine) must be excluded from full-catalog imports so second-hand/deposit pages do not masquerade as current new-stock listings.bigcommerce_metadataproduct:price:amount,product:price:currency,og:url,og:title, image metadata, and SKU. Availability metadata can be stale, so the crawler overrides it with Randem store-location inventory for theHQstore.bigcommerce_metadatasignalsounds.eustore with its own sitemap, EUR prices, and potentially different availability from the UK store. Official store copy says EU orders ship from the warehouse in Poland, so shipping origin isPLeven though the sales region is EU. Availability can lazy-update after page load; the crawler overrides staleog:availabilitywith Randem inventory for theSS Europestore.shopware_metadata.xml.gzURL set; product pages exposeproduct:price:amount,product:price:currency,og:url,og:title, product ID, image metadata, and stock text.shopify_product_jsonwww.ctrl-mod.com/products.jsonreturns Shopify product JSON with price, variant availability, vendor, handle, product type, tags, images, and SKU. Official about/shipping pages identify Control as a Brooklyn, New York brick-and-mortar/online store with New York pickup, so shipping origin isUS, currencyUSD. The catalog includes used/consignment, pre-order, accessory, case, and panel hazards that must remain strong noise signals.shopify_product_json/collections/eurorack/products.jsonreturns AUD price, variant availability, vendor, numeric handle, product type, tags, images, and SKU. Official shipping policy lists in-store pickup at 110 Albert Street, Brunswick East, Victoria 3057 and worldwide shipping, so shipping origin isAU. The broader all-products JSON is condition-heavy and rate-limited; the Eurorack collection is the safer source. Found Sound marks many brand-new Eurorack rows as preorder, so preorder is preserved as availability rather than treated as match noise for this store.shopify_product_json/collections/eurorack/products.jsonreturns GBP price, variant availability, vendor, handle, product type, tags, images, and SKU for the Glasgow/UK retailer. Condition/order/accessory hazards are filtered through shared Shopify noise rules.shopify_product_json/collections/eurorack-modules/products.jsonreturns USD prices from the Detroit/US retailer. The guessed/collections/eurorack/products.jsonhandle was empty;eurorack-modulesis verified. ExplicitDISCONTINUEDtitles normalize todiscontinuedinstead of generic out of stock.shopify_product_json/collections/modular/products.jsonreturns CAD prices from the Canadian retailer. The narrower/collections/eurorack/products.jsonwas valid but only yielded 67 products / 25 accepted rows, somodularis the better preview source.shopify_product_json/products.jsonreturns JPY prices from the Japan retailer. No simple Eurorack/module collection handle was exposed during quick collection probing, so the broad feed is filtered by the matcher. JPY values remain stored in display-compatible minor units because current UI formatting dividesprice_amount_minorby 100.shopify_product_json/collections/modules/products.jsonreturns CAD prices from the Canadian retailer. The guessed/collections/eurorack/products.jsonhandle was empty;modulesis verified. Open-box, special-order, and no-longer-available statuses remain filtered or normalized before import.shopify_product_json/products.jsonreturns USD prices and availability from the US retailer. Full-catalog import is viable after shared Shopify/current-stock noise filtering.woocommerce_store_apishopify_product_json/products.jsonreturns GBP prices, but the catalog is DIY/parts-heavy. Only built/assembled strong rows are imported after variant-title and parts/potentiometer/panel-set/PCB noise filtering.woocommerce_store_apishopify_product_json/products.jsonreturns USD direct-store prices. Product titles often omit the manufacturer because the whole store is first-party, so Shopifyvendormetadata is now trusted as manufacturer support for exact-title matching.shopify_product_json/products.jsonreturns EUR prices from the Berlin/Germany store. The broader catalog includes non-Eurorack and vintage items, but strong-match filtering keeps a small clean module subset.shopify_product_json/products.jsonreturns GBP direct-store prices. Titles use ALM product names and Shopify vendorALM, so the crawler adds a direct-storeALM Busy Circuitsbrand hint for matcher support. Apparel, slipmat, pedal, and placeholder-price hazards are filtered before import.woocommerce_store_apiInstruobrand hint for matcher support.shopify_product_json/products.jsonreturns USD direct-store prices. Product type/tags include modules plus apparel, accessories, and pedals; pedal/apparel/accessory hazards are excluded before import.shopify_product_json/products.jsonreturns USD direct-store prices for Michigan Synth Works and related module products. Strong-match filtering keeps a small price-bearing subset.shopify_product_json/products.jsonreturns USD prices from the San Francisco/US retailer. Broad-catalog matching stays behind shared used/accessory/order noise filtering and accepted 142 price-bearing rows.shopify_product_json/products.jsonreturns CAD prices from the Canadian retailer. Used/condition-heavy rows are filtered by shared Shopify noise rules; accepted import coverage is substantially stronger than the first-page used sample suggested.woocommerce_store_apiIntellijelbrand hint; B-stock/apparel/accessory rows remain filtered before import.shopify_product_json/products.jsonreturns USD direct-store prices. The direct-storeSchlappi Engineeringbrand hint supports exact-title matching while keeping the small catalog easy to QA.woocommerce_store_apiZlob Modularbrand hint supports matching, but strict no-price/implausible-price filtering means only one clean current module row was imported.shopify_product_json/products.jsonreturns EUR prices from the Lithuania retailer. The broad catalog is synth-focused rather than Eurorack-focused, so only four strict price-bearing module/semi-modular rows were imported.woocommerce_store_apiNano Modulesbrand hint and imports only strong price-bearing module rows.woocommerce_store_apiDreadboxbrand hint and imports only strong price-bearing module/semi-modular rows.shopify_product_jsonproducts.jsonworks again from the local crawler with NOK prices. Smoke crawl wrote 250 normalized products in one page and match smoke found strong module candidates.customsitemap/product metadata/befaco-bf-22/100133; product pages expose usable product metadata with EUR prices and availability. Smoke crawl wrote 5 normalized products from 1281 discovered URLs.customsitemap/product metadata + JSON-LD/it/shop/product URLs. Product pages rely on JSON-LD offers for price/currency, so the generic metadata parser now reads JSON-LD. Used URL variants are excluded. Smoke crawl wrote 5 normalized products from 4366 discovered URLs.customsitemap/product metadata/product/URLs; product pages exposeproduct:price:*metadata with EUR prices. Smoke crawl wrote 5 normalized products from 529 discovered URLs.customsitemap/product metadata/en/modular/product URLs; product pages exposeproduct:price:*, schema availability, and IDs. Generic metadata fetches require the crawler user-agent. Smoke crawl wrote 5 normalized products from 1134 discovered URLs.Defer initially
media/sitemap_en.xmlprobe returned a JPEG/bot response, not XML. Needs permission/API/affiliate route before automated checks.robots.txtdisallows/intl/, search/catalog requests hit Cloudflare/429, sitemap fetches were challenged/rate-limited, and no stable public price/availability feed was found without browser automation. Important store, but defer until an allowed feed/API is available.products.jsonis reachable but rate-limited during the full crawl before stable output could be written; defer until a slower/backoff-friendly crawl is implemented.patchwerks.com/www.patchwerks.comTLS certificate is expired from curl/Node's trust path,products.jsonreturns 404 when certificate verification is bypassed, andpatchwerks.myshopify.com/products.jsonreturns 401. Defer until TLS/API access is fixed.Proposed backend shape
Tables
Additive schema applied remotely 2026-07-02 under the schema-only approval gate. No RLS/policy/GRANT changes were included.
storesRegistry for known retailers/search targets.
idslugnamecountry_codebase_urlsearch_url_templateadapter_kind:woocommerce_store_api | shopify_product_json | bigcommerce_metadata | shopware_metadata | custom | nonecurrency_hintactiveprice_tracking_enabledrate_limit_per_daycreated_atupdated_atmodule_store_listingsCurated canonical product URL per module/store.
idmodule_idstore_idproduct_urlexternal_product_idexternal_handleactiveverification_status:candidate | verified | rejected | stalelast_checked_atlast_success_atnext_check_atfailure_countlast_errorcreated_atupdated_atmodule_price_snapshotsAppend-only observed price/availability history.
idlisting_idobserved_atprice_amount_minorcurrencyavailability:in_stock | out_of_stock | preorder | backorder | discontinued | unknownsource:scraper | api | manual | community | manufacturerraw_metacreated_atRetention policy for pilot
Scheduled worker design
Preferred environment
Use Supabase Scheduled Edge Functions:
Worker rules
limit = 20listings/day.activeandprice_tracking_enabledkill switches.Initial cadence
next_check_at: 24 hours after a successful check.verification_status = stale.Local no-database probe
Before deploy, the Edge Function can be served locally and called with
mode=probe.It still requires
PRICE_HUB_SNAPSHOT_TOKEN, but does not read Supabase URL/service-role env vars, create a Supabase client, or read/write tables.The probe fetch target is restricted to the approved WooCommerce pilot hosts:
elevatorsound.com,www.elevatorsound.com,newgroove.it, andwww.newgroove.it.Expected response shape:
{ "mode": "probe", "wroteToDatabase": false, "apiUrl": "...", "snapshot": { ... } }.Local catalog crawler and matcher
The local crawler is the current safe path for broad product discovery. It reads only the approved HTTPS store surfaces (WooCommerce Store API, BigCommerce product sitemaps plus product-page metadata, or Shopware sitemaps plus product-page metadata) and writes local files; it does not read/write Supabase, verify matches, deploy an Edge Function, create Cron, or add UI.
Outputs:
tmp/price-hub/<storeSlug>/products.json— normalized store products.tmp/price-hub/<storeSlug>/matches.json— optional conservative module/product candidates when--modulesis provided. Status is onlystrong_candidate,review_candidate, orignored; nothing is marked verified.Local import path
Curated local crawler matches can now be imported into the schema. The import keeps listing identity/current URL in
module_store_listingsand inserts a new append-onlymodule_price_snapshotsrow on every run.Live writes require
SUPABASE_SERVICE_ROLE_KEY. The importer accepts only strong candidates by default, chooses the highest-scoring product per module so the(module_id, store_id)listing constraint is not hit twice in one upsert, and then chooses one best module per product URL so the(store_id, product_url)listing constraint is not hit by duplicate ModularGrid/module matches. Dropped alternate modules are recorded in the selected snapshot metadata aspriceHubProductMatchAmbiguityandpriceHubAlternateMatchedModules.SchneidersLaden preview seed:
in_stock, 132out_of_stock, 62preorder, and 628discontinued.in_stock, 40out_of_stock, 22preorder, and 202discontinued, plus the previously corrected4524discontinued listing.WooCommerce zero-price rule:
price,regular_price, andsale_pricevalues of0are treated as missing/suspicious module prices, not valid free products.currencyand add compact metadata such aspriceWasZeroand, when the API includes empty price markup,priceHtmlEmpty, so the crawler can report the store quirk without showing impossible0.00 EURprices.Signal Sounds availability rule:
instockwhile the rendered page later shows a Randem Retail stock widget with "Sorry, this item is out of stock".https://api.randemretail.online/public/api/locationwith the Signal Sounds application id in SKU batches after product-page normalization. Per-product calls hit rate limits on full-catalog crawls.signal-sounds-eureadsSS Europe, whilesignal-sounds-ukreadsHQ.storeName: "SS Europe"withoutstoreExternalId, so the parser accepts either field for store targeting.SS Europequantity is zero but another Randem location is shippable with quantity. The crawler now treats any shippable positive Randem row asin_stock, while preserving all location quantities in raw metadata for diagnostics.unknowninstead of trusting staleog:availability.rawMeta.signalSoundsAvailabilitySource,rawMeta.signalSoundsStoreExternalId,rawMeta.signalSoundsInventoryQuantity, andrawMeta.signalSoundsInventoryLocations.Full-crawl correction:
signal-sounds-eu: 4461 normalized products, 2615 match candidates after memory-safe filtering, 1355 accepted import rows, 1339 appended snapshots, and 16 skipped normalized-URL conflicts already linked to other modules.schneidersladen: 5180 normalized products from 5464 discovered product URLs, 3245 match candidates after memory-safe filtering, 1851 accepted import rows, 1841 appended snapshots, and 10 skipped normalized-URL conflicts already linked to other modules.new-groove: 5887 normalized WooCommerce Store API products, 1692 match candidates after full-catalog filtering, 1580 strong candidates, 942 accepted import rows after deduplication, and no skipped conflicts in the corrected import. Three previously active New Groove listing URLs containing used/preorder markers were deactivated after the noise rule fix.elevator-sound: 2887 normalized WooCommerce Store API products from the officialwww.elevatorsound.comdomain over 29 pages, 1927 match candidates after full-catalog filtering, 1708 strong candidates, 1077 accepted import rows after deduplication, 1077 upserted active listings, and 1077 appended snapshots. Latest accepted availability distribution: 620in_stock, 348backorder, and 109out_of_stock.control: 3930 normalized Shopify products fromwww.ctrl-mod.com/products.jsonover 16 pages, 658 match candidates after full-catalog filtering, 630 strong candidates, 524 accepted import rows after deduplication, 524 upserted active listings, and 524 appended snapshots. Latest accepted availability distribution: 88in_stockand 436out_of_stock.found-sound: 2737 normalized Shopify products from the official Found Sound Eurorack collection over 11 pages, 1749 match candidates after full-catalog filtering, and 1100 accepted import rows after deduplication. A remotestoresrow was upserted, then the existing import-local path upserted 1100 active listings and appended 1100 snapshots. Latest accepted availability distribution: 39in_stock, 1026preorder, and 35out_of_stock.rubadub: 837 normalized Shopify Eurorack collection products over 4 pages, 638 match candidates, and 454 accepted import rows. The import upserted 454 active listings and appended 454 snapshots. Latest accepted distribution: 224in_stockand 230out_of_stock.detroit-modular: 2348 normalized Shopify Eurorack Modules collection products over 10 pages, 2048 match candidates, and 1337 accepted import rows. The import upserted 1337 active listings and appended 1337 snapshots. Latest accepted distribution: 820in_stock, 503out_of_stock, and 14discontinued.nightlife-electronics: 2115 normalized Shopify Modular collection products over 9 pages, 1770 match candidates, and 1141 accepted import rows. The import upserted 1141 active listings and appended 1141 snapshots. Latest accepted distribution: 1056in_stockand 85out_of_stock.clockface-modular: 1385 normalized Shopify products over 6 pages, 1135 match candidates, and 758 accepted import rows. The import upserted 758 active listings and appended 758 snapshots. Latest accepted distribution: 406in_stockand 352out_of_stock.moog-audio: 2428 normalized Shopify Modules collection products over 10 pages, 633 match candidates, and 421 accepted import rows. The import upserted 421 active listings and appended 421 snapshots. Latest accepted distribution: 420in_stockand 1out_of_stock.noisebug: 1297 normalized Shopify products over 6 pages, 784 match candidates after full-catalog filtering, and 504 accepted import rows. The import upserted 504 active listings and appended 504 snapshots. Latest accepted distribution: 362in_stock, 138out_of_stock, and 4discontinued.postmodular: 1012 normalized WooCommerce Store API products over 11 pages, 26 match candidates after full-catalog filtering, and 14 accepted import rows. The import upserted 14 active listings and appended 14 snapshots. Latest accepted distribution: 7in_stockand 7out_of_stock.pusherman-productions: 375 normalized Shopify products over 2 pages, 2 match candidates after DIY/parts filtering, and 2 accepted import rows. Latest accepted distribution: 2in_stock.thonk: 1365 normalized WooCommerce Store API products over 14 pages, 8 match candidates after DIY/parts filtering, and 3 accepted import rows. The first pass exposed a null-price assembled row; the importer now skips no-price products and the corrected latest Thonk rows all have GBP prices. Latest accepted distribution: 2in_stockand 1out_of_stock.after-later-audio: 124 normalized Shopify products over 1 page, 129 match candidates after vendor-backed exact-title matching, and 72 accepted import rows. Latest accepted distribution: 69in_stockand 3out_of_stock.patch-point: 241 normalized Shopify products over 1 page, 20 match candidates, and 15 accepted import rows. Latest accepted distribution: 8in_stockand 7out_of_stock.busy-circuits: 140 normalized Shopify products over 1 page, 57 match candidates after direct-store brand hints and noise filtering, and 37 accepted import rows. Latest accepted distribution: 36in_stockand 1out_of_stock.instruo: 40 normalized WooCommerce Store API products over 1 page, 41 match candidates after direct-store brand hints, and 35 accepted import rows. Latest accepted distribution: 31in_stockand 4out_of_stock.wmdevices: 89 normalized Shopify products over 1 page, 43 match candidates after apparel/accessory/pedal filtering, and 33 active accepted import rows after deactivating a WMD Geiger Counter pedal row. Latest accepted distribution: 27in_stockand 6out_of_stock.michigan-synth-works: 127 normalized Shopify products over 1 page, 36 match candidates, and 20 accepted import rows. Latest accepted distribution: 14in_stockand 6out_of_stock.pcb,kitbag,panel set,parts,potentiometer,used,pre-owned,open box,consignment,power adapter,stackcable,cover,manual, orguide.price_amount_minor = null; these were deactivated because Price Hub active listings should represent price-bearing data. Future local imports skip source rows lacking usable price/currency.4263) isin_stockat both Signal EU and SchneidersLaden.4524) remainsdiscontinuedat SchneidersLaden after compact module-code matching recovers theADDAC812VU->812Vproduct match.4831) isin_stockat both stores; Signal EU uses shippable RandemHQquantity while preserving EU/HQ location diagnostics.4106) no longer keeps the New Groove no-price row active; priced active rows remain from Clockface Modular, Control, Elevator Sound, Moog Audio, Nightlife Electronics, Rubadub, SchneidersLaden, Signal Sounds EU, and Signal Sounds UK.2810) has four active imported store listings: Signal Sounds EU, Signal Sounds UK, SchneidersLaden, and New Groove.4263) now has five visible store rows; Elevator Sound reports£380.00,in_stock, andUnited Kingdom./modules/details/2096(AA.1,129.00 USD, out of stock),/modules/details/2810(0-CTRL,399.00 USD, out of stock),/modules/details/4106(0-Coast,499.00 USD, out of stock),/modules/details/4831(Nibbler,240.00 USD, out of stock), and/modules/details/7372(ASQ-1,399.00 USD, in stock)./modules/details/1109(∑42,279.00 AUD, preorder),/modules/details/1788(100 Grit,499.00 AUD, preorder),/modules/details/3545(1983,379.00 AUD, preorder),/modules/details/3778([1]f,169.00 AUD, preorder),/modules/details/4831(Nibbler,439.00 AUD, preorder), and/modules/details/7372(ASQ-1,609.00 AUD, preorder)./modules/details/2810intentionally has no Found Sound row because the matching product was only a0-CTRL Power Adapter./modules/details/101,/modules/details/116,/modules/details/169,/modules/details/171,/modules/details/249,/modules/details/2810,/modules/details/4106,/modules/details/4263,/modules/details/4831, and/modules/details/8142.Shipping-origin rule:
EU/region labels as if they were a physical dispatch point.signal-sounds-eu.country_code = PL.elevator-soundatwww.elevatorsound.com, withcountry_code = GBandcurrency_hint = GBP.country_code = USandcurrency_hint = USD.country_code = AUandcurrency_hint = AUD.EU, the UI should surface that as data needing review rather than pretending it is a shipping origin.Shopware archived-page rule:
in stocktext before the actual buy widget marks a product as archived.Product is archived,Sorry folks, sold-out, unavailable, and out-of-stock messages are checked beforeIn stock.availability = discontinued; the snapshot keepsrawMeta.pageAvailabilityTextfor diagnostics.schema.org/InStock),delivery-information delivery-available, or an enabledAdd to cartbuy button wins over straypreorderwords inside descriptions or external product links. Nibbler exposed this case: both Black and Silver pages are in stock, but the description links to a manufacturer URL containingnibbler-preorder.Panel variant rule:
module_store_listingsstill has one current URL per module/store.rawMeta.panelVariantwhen obvious panel colour tokens are present.in_stockbefore preorder/backorder/out-of-stock/discontinued/unknown), then records ambiguity diagnostics in the selected snapshot:priceHubVariantAmbiguity,priceHubPanelVariants, andpriceHubAlternateMatchedProducts.unknownmust not win over a verified unavailable state because that would hide known store evidence.Full-catalog matching rule:
ignoredcandidates can exhaust Node heap.matchModulesToProducts(..., { includeIgnored: false })and--include-ignored-matches=falsekeep only strong/review candidates for full-catalog runs, while the default still includes ignored positive-score rows for smaller diagnostic crawls.ADDAC812VUcan appear as split store product slugs (addac-systems-812v-led-voltage-meter). The matcher now adds compact code aliases only when manufacturer evidence is already present, avoiding broad numeric false positives. Bare numeric suffixes are not aliases on their own, so TiptopBD909/CP909/HATS909do not match anSD909page solely through909; alphanumeric code aliases such as812vstill work.used,usato,occasione,prenotazione, andpreordine; otherwise rows such asMake Noise 0-Coast (prenotazione)can be incorrectly selected over the canonical product page.b-stock,ex-demo,pre-order,deposit,accessory, and related hyphen/space variants are treated as noise so B-stock, ex-demo, preorder, and accessory pages do not become verified current-new-stock imports.tagsandproductTypeinrawMeta, and the matcher includes those fields when applying noise terms such asused,consignment,pre-order,preorder,accessory,case, andpanel.preorderfrom match-noise text while preservingavailability = preorder; used/consignment rows, covers, stackcables, andpower adapteraccessories remain excluded from accepted imports.store-configs.tsas per-store match configuration defaults, so future source-specific tuning can happen beside the approved store entry instead of inmatcher.ts. Found Sound's preorder suppression remains tag-only: preorder in a product title or handle is still penalized as product-identity noise.rawMeta.vendor/brand/manufacturer metadata can provide manufacturer support; this keeps After Later Audio importable while still letting noise terms suppress panels, parts, and accessories.productBrandHint; the crawler stores that as compactrawMeta.brandso matcher support is explicit and source-specific rather than guessed globally.priceAmountMinororcurrency, has a non-positive/too-low accessory-like price, or has an absurd placeholder price; historical snapshots can remain append-only, but such listings should not stay active.UI shape for eventual pilot display
The first UI surface is implemented on module detail, directly under the primary module card on desktop so price/availability is visible without scrolling past the secondary rail.
Preferred v1 placement:
Buy newandSearch onsurfaces.Poland,Germany, etc.) when knownin_stockgreen,out_of_stockred, preorder/backorder amber,discontinuedgrey, unknown muted)Checked X agoShow: All, In stock, Available soon, Unavailable, Unknown.Order: Lowest price first by default, plus highest price, availability, store name, and most recently checked.Trust rules:
Last seenor hide current price.last_checked_atas stale; legacy rows withoutlast_checked_atuse the latest snapshotobserved_atas the UI freshness fallback.module_price_snapshots.limit=1) and leaves historical rows for future charts/history views.MVP → Structural → Polish layers
MVP — one daily worker, no public UI
stores,module_store_listings, andmodule_price_snapshots.module_store_listingsrows for known modules.snapshot-store-listingsEdge Function with WooCommerce Store API and Shopware metadata adapters.mode=probebehavior to fetch and normalize one WooCommerce product without Supabase env/client/database access.elevator-soundandnew-groove, writing totmp/price-hub.0.00 EURprices.Structural — reusable adapter spine
SupabaseServicefor current listing summaries.scripts/price-hub/README.md.snapshot-store-listingsfor the safe shared metadata adapters: WooCommerce Store API, BigCommerce metadata, and Shopware metadata now route through_shared/price-hub/*normalizers. BigCommerce completed 2026-07-07 by addingbigcommerce_metadatascheduled support without deploy/Cron changes. (added 2026-07-03)snapshot-store-listingsdispatch for Shopify product JSON in the local-only worker path after validating scheduled endpoint semantics per store; deploy/Cron/manual invocation remain gated, and local crawler pagination/rate-limit behavior is still not assumed safe for the Edge Function. (completed 2026-07-07; split from worker dispatch item 2026-07-07)snapshot-store-listingsdispatch forcustommetadata in the local-only worker path after proving every custom store row is compatible with the shared metadata fetch/normalization path; deploy/Cron/manual invocation remain gated. (completed 2026-07-07; split from worker dispatch item 2026-07-07)next_check_at = now()with the worker's daily/backoff schedule so turning cron on doesn't produce a 13k-row backlog against a 20/day cap. Completed 2026-07-07: local importer upserts now keeplast_checked_at/last_success_atat import time but compute a deterministic futurenext_check_atspread across seven days from store/module/product identity. (added 2026-07-03)priceHubAlternateMatchedModules,signalSoundsInventoryLocations, etc.) frommodule_price_snapshots.raw_metatomodule_store_listingsso snapshots stay skinny. (added 2026-07-03; approved 2026-07-07; policy clarified 2026-07-08)NOISE_TERMSand the magic 0.86 / 0.72 score thresholds inmatcher.tsto per-store configuration instore-configs.ts. Preorder is a legitimate availability state — penalise it only when it appears in title/handle, not when it appears as a tag. Completed 2026-07-07 with store match defaults and Found Sound tag-vs-title/handle coverage. (added 2026-07-03)scripts/price-hub/catalog-crawler.tsinto a stable facade plus smaller crawler modules for WooCommerce, Shopify, sitemap metadata orchestration, Signal Sounds fallback inventory, sitemap helpers, shared helpers, and output writes. Completed 2026-07-07 without changing public imports. (added 2026-07-03)import-local-snapshots.ts. Completed 2026-07-07 by moving row planning, dedupe, conflict filtering, module-id helpers, product URL extraction, and next-check staggering intoscripts/price-hub/import-local/row-planning.tswhile preserving theimport-local-snapshots.tsfacade used by refresh/tests. (added 2026-07-03)MVP hardening — post-review 2026-07-03
Additive to the pilot; do not require broader UX changes. Correctness and safety first, then structural gaps.
stores,module_store_listings, andmodule_price_snapshots; revoke write privileges fromanonandauthenticated; keep service-role crawler writes; add permissive publicselectpolicies so public read paths keep working. Pair withSET search_path = publicontg_price_hub_set_updated_atto clear the Price Hubfunction_search_path_mutableadvisor warning.AUD,CAD,JPYat minimum, plus any future store currency) in bothmodule-price-listings-card.utils.tsandmodule-price-summary.utils.ts; then consolidate the two duplicates into one shared module labelled as an estimate only. Completed 2026-07-07 with shared frontend estimated EUR helper covering AUD, CAD, CHF, EUR, GBP, JPY, NOK, and USD.parseDecimalPriceMinorcurrency-aware so zero-decimal currencies (JPY, KRW, HUF, ISK, VND, CLP) are stored as true minor units. This requires a coordinated approved backfill/migration for the 758 existing Clockface Modular JPY rows before the frontend/100cancellation can be removed.last_checked_atis older than a documented threshold (start with 14 days) and surface "Last seen" instead of a current price. Completed 2026-07-07: stale rows remain visible in All results but showLast seen/Stale data, are excluded from current price/best-price comparisons, and sort after current known prices.cacheBust(['priceHub'])remains app-runtime-only; local importer scripts cannot safely trigger an Angular in-memory/localStorage cache bust after remote writes.--full-catalogevidence. The compact reason is stored inmodule_store_listings.last_errorand the row is markedverification_status = stale.active = false(with a "not-seen-since" reason), gated by minimum pages/products so a partial crawl cannot wipe a store. Completed 2026-07-07 via local importer/refresh full-catalog evidence gates.Polish — user-visible Price Hub seed
Search onchips for unverified/untracked stores. Completed 2026-07-07: retailer chips are suppressed only for verified/current Price Hub listings; candidate, stale, and untracked stores remain visible as search fallbacks.Search on > Other storeschips for the imported Price Hub store batch with straightforward site-search URLs. Completed 2026-07-07: added Detroit Modular, Nightlife Electronics, Clockface Modular, Moog Audio, Noisebug, Pusherman Productions, Thonk, After Later Audio, Patch Point, ALM / Busy Circuits, Instruo, WMD, Michigan Synth Works, RobotSpeak, Cicada Sound, Intellijel, Schlappi Engineering, Zlob Modular, Soundium, Nano Modules, and Dreadbox; skipped stores where the URL was not part of this safe batch rather than adding a new UI surface.STORE_HERO_COLORSoverrides. Completed 2026-07-07.Risks and mitigations
0.00 EURas a real module price.og:availability; selectSS Europefor EU andHQfor UK.unknownwhen authoritative inventory is missing.discontinued.rawMetafor future UI/history work.(store_id, product_url)and choose the best module deterministically; preserve alternate matched modules inrawMetafor review.discontinued, and filter open-box/special-order/opening-soon hazards before broad imports unless a store-specific rule says otherwise.productBrandHintonly for known first-party sources; do not globally assume titles without brands are safe.public.*grants)secure_price_hub_rls: anon/authenticated now have onlySELECT, public read policies exist, and service_role write grants remain for local imports.snapshot-store-listingsEdge Function now dispatches WooCommerce Store API, Shopify product JSON, BigCommerce metadata, Shopware metadata, and custom metadata listings, so imported pilot rows are eligible if the worker is later deployed. This is not deployed; Supabase Cron remains declined/manual-only.price_amount_minor= major × 100, which happens to display correctly only because the frontend also divides by 100. Any downstream consumer (analytics, currency conversion, admin dashboard) will read JPY (and future KRW/HUF/ISK/VND/CLP) at 100× the real price. Fix by makingparseDecimalPriceMinorcurrency-aware and backfilling the 758 Clockface rows.module-price-estimated-fx.utils.tsand seeded AUD/CAD/JPY/NOK stores now participate in estimated EUR ordering and rollups. Currency-aware storage/backfill remains separate.Last seen/Stale data, do not expose a current price, and do not participate in best-price ranking. Backend stale status/retention remains separate.active = false,verification_status = stale, with compactlast_error = not_seen_since_full_catalog:YYYY-MM-DD. Partial/bounded/low-coverage crawls skip this path, and standalone import requires explicit--full-catalog.PRICE_HUB_ENV_FILEcan point to an external env file, so secrets do not need to be copied into the Patcher repository.--max-productsdid not cap Shopify/WooCommerce crawlsmaxProductsand reporthitMaxProducts; broad live--store=allpartial refreshes stayBLOCKING, while dry-runs or explicit single-store bounded refreshes can import with a warning.Validation strategy
Docs-only checkpoint:
node scripts/checks/check-docs.cjsgit diff --checkImplementation checkpoint, if approved later:
internaldocs/patterns/BACKEND_METHODS.mdschema-change preflight before touching migrations.pnpm updateBackendTypesafter approved schema changes.Approval gates
raw_meta.Approval queue
supabase/migrations/20260701115059_add_price_hub_pilot_tables.sqlto the remote Supabase project so the pilot tables and two store registry rows exist? This does not include RLS/policy/GRANT changes, Edge Function deployment, Cron creation, user-visible UI, or guessedmodule_store_listingsrows. Default if not approved: keep the pilot local-only.4524only.module_store_listing_flags, RLS, backend persistence, or user reporting UX by default.Decision log
2026-07-08T15:35+02:00 — Completed frontend-only cleanup after user declined wrong-product reporting: removed the
Report mismatchprototype/local acknowledgement state and restored each Store prices listing row as a single explicit external link. Validation passed with focused module-price listing specs,pnpm lint, docs check, andgit diff --check; no backend/schema/RLS/Cron/data/release/push work was performed.2026-07-08T15:20+02:00 — Staged as next coordinator-loop task after User Area Cool List Preview Alignment because it has an approved frontend-only cleanup slice: remove the declined report-wrong-product prototype and restore row-wide external listing navigation. Keep Supabase Cron manual-only, and do not perform retention/backfill/schema/data mutations without explicit schema-change preflight and approval.
2026-07-07T21:20+02:00 — Report-wrong-product UI prototype checkpoint completed locally. Store prices rows were restructured from a row-wide anchor into a non-interactive row with an explicit external listing link, avoiding nested interactive controls while adding a low-weight
Report mismatchbutton. The button only records local acknowledgement state and shows prototype-only/not-saved copy. Validation:pnpm test-headless --include="**/module-price-listings-card.component.spec.ts"passed. Scope stayed frontend-only: no backend calls, Supabase methods, migrations, RLS/policy changes, Edge Function deploy, Cron, release, push, or data writes.2026-07-08T14:13+02:00 — User chose Price Hub retention policy: keep detailed daily snapshots for 30 days, then compact older data indefinitely using a Home Assistant-style long-term statistics/checkpoint strategy rather than hard-deleting all older price history.
2026-07-08T14:13+02:00 — User declined Price Hub wrong-product reporting as a user-facing workflow. Future work should remove the
Report mismatchprototype, make the whole listing row open the external store listing again, and treat wrong matches as internal/operator-managed data quality.2026-07-07T21:12+02:00 — Sparse Price Hub summary/header history checkpoint completed locally. Added an explicit-column read path for recent active-listing snapshots, a typed sparse-history summary helper, module detail data-service stream, and existing title price-badge rendering (
~€399 · ↓5% 60d) with accessible tooltip caveats. Reviewer follow-up suppresses the badge when the recent snapshot read would be capped/truncated, rather than deriving a trend from an incomplete sample. Validation:pnpm test-headless --include="**/module-price-summary.utils.spec.ts",pnpm test-headless --include="**/module-minimal.component.spec.ts",pnpm test-headless --include="**/module-detail-data.service.spec.ts",node scripts/checks/check-docs.cjs, andpnpm lintpassed. Scope stayed read-only: no migrations, RLS/policy changes, Edge Function deploy, Cron, release, push, or data writes.2026-07-07T20:55+02:00 — Local crawler timeout hardening checkpoint: added
fetchTimeoutMs(default 30s), abortable fetch wrappers, response-body timeout guards, and Shopify curl timeout/signal handling across WooCommerce, Shopify, BigCommerce/Shopware/custom metadata, and Signal Sounds inventory override paths. Validation: implementation subagent, independent reviewer approval,pnpm test:functions:price-hub-local-crawler,git diff --check, andpnpm lintpassed. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed.2026-07-07T15:08+02:00 — Deployed manual/on-demand
snapshot-store-listingsEdge Function, then redeployed version 2 after the auth-status follow-up, to Supabase projectsozmatmywjpstwidzlsswithverify_jwt=falsebecause the function uses its ownPRICE_HUB_SNAPSHOT_TOKENauthorization. No Cron was created. Unauthenticated smoke check fails closed becausePRICE_HUB_SNAPSHOT_TOKENis not configured in the deployed function environment yet; configure that secret before manual production invocation. Bad/missing request tokens return HTTP 401 once the secret exists, while missing server config remains HTTP 500.2026-07-07T16:47+02:00 — SchneidersLaden stale-listing resolution completed. Root causes were stacked: skipped Shopware metadata pages blocked broad imports, active-listing refresh only read the first 500 DB rows, existing active listing URLs were not refreshed unless they were current strong matcher rows, duplicate crawled product URLs could duplicate active-refresh upserts, low-price observed rows stayed active with old snapshots, and 404 skipped metadata URLs were protected as if transient. Local refresh now warns on skipped metadata pages, paginates active listing reads at 500 rows, refreshes observed active listing URLs, dedupes refresh rows, marks observed unimportable active listings stale, and allows 404/410 skipped URLs to deactivate. Final targeted refresh imported 1910 fresh SchneidersLaden snapshots/listings and DB verification showed 1919/1919 active listings fresh, 0 active stale, and 0 active listings older than 30 days.
2026-07-07T14:42+02:00 — SchneidersLaden stale-listing diagnosis: the all-store refresh skipped import because
refresh-localtreated skipped Shopware metadata pages as a blocking partial-crawl condition. That was too strict for broken/non-product sitemap URLs and left previous snapshots visible. Skipped metadata pages are now warning-only for import while disappearance deactivation remains guarded; targeted live refresh imported 652 fresh SchneidersLaden snapshots/listings, skipped deactivation because 99 product pages lacked usable metadata, and left previous unmatched data intact.2026-07-07T15:02+02:00 — Zero-decimal local code checkpoint: Price Hub parsing and display now use currency fraction digits instead of assuming
/100everywhere. Shopify catalog JSON, product metadata pages, Shopify product.js scheduled refresh normalization, estimated EUR rollups, and Store prices formatting now treat JPY/other zero-decimal currencies as whole minor units. This is a code/test-only checkpoint: no Supabase migration, remote backfill, Edge Function deploy, Cron, RLS/policy, release, push, or data write was performed. Existing over-scaled stored rows still need a separate read-only data audit plus reviewed backfill plan before any remote mutation.2026-07-07T15:02+02:00 — Local refresh skipped-metadata policy hardened: crawls that skip individual metadata product pages now warn and may import matched rows, while disappearance deactivation remains guarded for those crawls. Truncation from max products/pages/sitemap files still blocks broad
--store=allimports. No data write, release, push, deploy, Cron, or RLS/policy change was performed.2026-07-07T14:42+02:00 — Local-only worker dispatch follow-up:
snapshot-store-listingsnow includes Shopify product JSON and custom metadata adapters alongside WooCommerce, BigCommerce, and Shopware. Shopify scheduled refreshes build one/products/{handle}.jsURL fromexternal_handleor product URL slug and use the storecurrency_hintwhen selected; custom refreshes reuse the shared metadata normalizer with store-slug context. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed; Supabase Cron remains manual/on-demand only.2026-07-07T14:33+02:00 — Follow-up from the daily local refresh: Whimsical Raps was recoverable as a local curl trust-store issue, not a bad public feed. The Shopify curl helper now retries TLS certificate failures with
--ca-native, and a targetedwhimsical-rapsrefresh imported 5 snapshots/listings with 0 deactivations. TechnoSynth still returns a Cloudflare 403 challenge from the WooCommerce Store API even with crawler headers; keep prior data intact and do not bypass bot protection.2026-07-07T12:40+02:00 — Zero-decimal storage/backfill remains approval-gated: do not treat
parseDecimalPriceMinoras a display-only cleanup. The safe default is to keep the current/100frontend workaround until the Clockface Modular JPY backfill/migration is explicitly approved.2026-07-07T12:40+02:00 — Snapshot retention/compaction and moving stable diagnostics out of
module_price_snapshots.raw_metaare schema/data-retention changes, not passive cleanup. Keep append-only snapshots and compact diagnostics inraw_metauntil approved.2026-07-07T12:40+02:00 —
module_store_listing_flagsand any report-wrong-product affordance need schema/RLS/UX placement approval. Default is no new table or row-level action until that approval exists.2026-07-07T12:40+02:00 — Sparse price history is a real user-visible addition; if approved later, the default placement is a tiny 30-day per-listing sparkline inside Store prices rows, with no new page or card.
2026-07-07T12:40+02:00 — Edge Function deploy and Supabase Cron remain separate approvals. Shopify/custom scheduled dispatch work can continue as code-only validation follow-ups, but they do not become Supabase approval items unless deploy/Cron is involved.
2026-07-07T14:07+02:00 — User approved a safe additive zero-decimal storage migration/backfill with validation queries/checks before and after. User also approved additive retention/diagnostics schema/backend work for snapshot retention state, scrape diagnostics, and failure metadata.
2026-07-07T14:07+02:00 — User approved report-wrong-product UI prototype work only; persistence/RLS/backend support remains deferred. Sparse price-history sparklines should first appear in the module Price Hub summary/header, not retailer rows.
2026-07-07T14:07+02:00 — User approved future Edge Function deploy/update after local tests and configuration checks pass. Supabase Cron is not approved; keep snapshots manual/on-demand for now.
2026-07-07T12:33+02:00 — Safe UI fallback checkpoint: extended the existing
Search on > Other storesretailer chips for imported Price Hub stores that had configs/data but no chip. The batch uses straightforward Shopify/search?type=product&q=or WooCommerce?s=&post_type=productsearch URLs and store-slug aliases so verified listings can still suppress matching chips while unverified/untracked stores remain searchable. No backend, migration, RLS, Edge Function, Cron, release, push, or new UI surface was changed.2026-07-07T12:29+02:00 — Partial worker dispatch checkpoint:
snapshot-store-listingsnow includesbigcommerce_metadatain scheduled adapter support and routes metadata product pages through the shared BigCommerce normalizer. This deliberately stops short of Shopify product JSON because scheduled endpoint semantics need separate validation, and stops short ofcustombecause compatible metadata behavior must be proven per custom store. No Edge Function deploy, Cron, migration, RLS/policy, release, push, or data write was performed.2026-07-07T12:28+02:00 — Store prices no longer hide the broader retailer search safety net unless a store has a verified current Price Hub listing. Candidate/stale/untracked store links remain under
Search on > Other stores, preserving the previous search-only fallback for stores not yet trusted as current listings.2026-07-07T12:14+02:00 — Behavior-preserving local importer split:
import-local-snapshots.tsremains the public CLI/Supabase write facade, while pure import row planning and validation helpers live underscripts/price-hub/import-local/row-planning.ts. Public imports from refresh/tests continue through the facade; validation kept the 23/23 import-local test coverage passing.2026-07-07T11:51+02:00 — Behavior-preserving local crawler split:
catalog-crawler.tsnow stays as the public facade used by local crawl/refresh/tests, while adapter and helper code lives underscripts/price-hub/crawlers/. Validation:pnpm test:functions:price-hub-local-crawlerpassed before and after the split.2026-07-07T11:23+02:00 — Moved Price Hub matcher review/strong thresholds and shared noise terms into
store-configs.tsmatch defaults, with matcher options accepting the approved store config. Local crawl/refresh now pass the store into matching;--min-scoreremains an explicit override. Found Sound preorder tags stay valid availability metadata viaignoredMatchNoiseTags, while preorder in titles/handles still triggers normal noise penalties.2026-07-07T11:19+02:00 — Added
scripts/price-hub/README.mdas the local store-adapter onboarding guide, covering approved config shape, adapter choice, crawl/match/dry-run/live import gates, service-role env handling, partial-crawl/deactivation safety, noise-term/productBrandHint guidance, shipping origin/currency review, and the no Edge/Cron/deploy boundary.2026-07-01T11:08+02:00 — User requested an experimental pilot plan for store-by-store module price/availability tracking, with daily Supabase Edge Function execution if cost/Free-plan feasibility checks out.
2026-07-01T11:12+02:00 — Supabase organization
MAINis on the Free plan and Patcher projectsozmatmywjpstwidzlssis active/healthy. Supabase Free includes 500,000 Edge Function invocations/month, 100 functions/project, 150s function wall-clock, 256 MB memory, 500 MB database, and 5 GB egress. A daily capped pilot should fit Free-plan limits.2026-07-01T11:12+02:00 — Pilot environment recommendation: Supabase Cron invoking a DB-queued Edge Function once daily. Do not add a separate server or browser automation for the pilot.
2026-07-01T11:12+02:00 — Parser priority: start with WooCommerce Store API stores (Elevator Sound, New Groove), then Shopify JSON stores (Control, Found Sound, Synthshop), then Signal Sounds EU BigCommerce metadata.
2026-07-01T11:50+02:00 — MVP local-preparation layer drafted: local additive migration, table constants, WooCommerce-only Edge Function worker shape, and parser fixtures/tests.
module_store_listingsseed rows remain blocked on real module IDs plus verified canonical product URLs; the next approval gate is remote migration application.2026-07-01T11:50+02:00 — Review findings addressed before handoff: WooCommerce fetches have a timeout, search result normalization fails closed instead of snapshotting a first-product fallback, and the worker fails closed if
PRICE_HUB_SNAPSHOT_TOKENis not configured.2026-07-01T12:03+02:00 — Created verified checkpoint commit
3918cf03for the local MVP preparation and stopped at the remote migration approval gate.2026-07-01T13:16+02:00 — Local-only refinement extracted Deno-free snapshot worker helpers and Node tests for limit parsing, fail-closed auth, WooCommerce URL construction/slug fallback, failure backoff/stale threshold, and error message clipping.
recordFailurenow checks update errors and surfaces them in the listing result instead of silently ignoring them. No migration, RLS/policy/GRANT, Edge Function deploy, Cron, UI, push, or release was performed.2026-07-01T13:16+02:00 — Created verified checkpoint commit
e5998b2dfor the local-only worker helper refinement. The next step remains blocked on remote migration approval.2026-07-01T14:27+02:00 — Added a local-only
mode=probePoC path for the snapshot Edge Function. It is intentionally DB-free, requires explicit probe JSON input, returns the WooCommerce API URL plus normalized snapshot, and keeps deploy/migration/RLS/Cron/UI/push gates closed.2026-07-01T14:27+02:00 — Review flagged arbitrary probe hosts as an SSRF risk; probe fetch targets are now restricted to HTTPS on the approved WooCommerce pilot hosts only.
2026-07-01T14:27+02:00 — Created verified checkpoint commit
2a04f353for the no-database probe mode. The next step remains blocked on explicit deploy/push approval; remote migrations, RLS/policy/GRANT changes, Cron, and UI remain unapproved.2026-07-02T09:38+02:00 — Added local-only crawler/matcher scripts for approved WooCommerce pilot stores. This checkpoint keeps Price Hub data discovery in local JSON files under
tmp/price-hub, treats Supabase translation as a later decision, and never marks a product/module match as verified.2026-07-02T09:38+02:00 — Review flagged zero-score generic module rows as a
matches.jsonexplosion risk; matcher now drops zero-score candidates and keeps ignored rows only when there was real positive signal.2026-07-02T09:38+02:00 — Created verified checkpoint commit
e7e3bd1bfor the local catalog crawler/matcher. The next step remains local analysis of crawler output or explicit approval for any remote/Supabase/UI work.2026-07-02T10:04+02:00 — Live New Groove inspection found real module products where the WooCommerce Store API returns
price,regular_price, andsale_priceas0with emptyprice_html. The normalizer now treats zero prices as unknown/suspicious (priceAmountMinor: null,rawMeta.priceWasZero: true) so Patcher never shows impossible0.00 EURmodule prices.2026-07-02T10:17+02:00 — Added Signal Sounds as two local-only stores:
signal-sounds-uk(signalsounds.com, GBP) andsignal-sounds-eu(signalsounds.eu, EUR). Both crawl product sitemap URLs and normalize BigCommerce product metadata, preserving separate price/availability observations per domain.2026-07-02T10:33+02:00 — Added SchneidersLaden as a local-only Shopware metadata store. The crawler follows the allowed sitemap index to its gzipped URL set, skips non-product/category pages without price metadata, reports skipped broken product URLs, and normalizes EUR price plus stock text from product pages.
2026-07-02T11:36+02:00 — Signal Sounds availability metadata can be stale: Melotus Versio EU reported
og:availability=instock, then the runtime widget rendered "Sorry, this item is out of stock". The local crawler now extracts SKU (NSEE70in the observed case), calls Randem Retail location inventory, and overrides availability using the storefront-specific warehouse quantity.2026-07-02T11:43+02:00 — Review approved the Signal Sounds availability fix with no findings. A real-page local crawl against Melotus Versio EU returned
availability: out_of_stock,signalSoundsStoreExternalId: SS Europe, andsignalSoundsInventoryQuantity: 0; writing corrected snapshots to Supabase remains an explicit approval gate.2026-07-02T11:40+02:00 — User approved and received one curated SchneidersLaden import for module
4524(ADDAC812VU): product URLhttps://schneidersladen.de/en/addac-systems-812v-led-voltage-meter,139.00 EUR, initially markedin_stock, sourcescraper. This was not a broad Schneiders import.2026-07-02T11:43+02:00 — User caught that the SchneidersLaden ADDAC812VU page is actually archived. The parser now scans availability by priority rather than first text position, so
Product is archived/Sorry folksbeats earlier genericin stocktext. A corrected latest snapshot was appended withavailability = discontinued.2026-07-02T14:13+02:00 — User flagged panel variants for AJH Synth Finaliser R-EQ (
4263): Silver is in stock while Black is preorder. The parser now records panel variants, the importer prefers the best available equal-score panel variant, and the latest4263snapshot records Silver as selected plus Black as an alternate inrawMeta.2026-07-02T14:30+02:00 — Broader SchneidersLaden preview seed imported 501 deduped listing/snapshot rows from a 1500-product local crawl. The import surfaced a real schema constraint interaction: the same product URL can strongly match multiple modules, so importer output now deduplicates by normalized product URL after per-module selection and records alternate module candidates in snapshot metadata.
2026-07-02T14:30+02:00 — Module detail Store prices moved from below the secondary
Search onarea to directly under the primary module card. Availability pills now include colored status dots so full-screen desktop browsing can scan current stock state quickly.2026-07-02T15:07+02:00 — User caught SchneidersLaden Nibbler (
4831) as a false preorder. The actual page hasschema.org/InStock, a green delivery block, and an enabled Add to cart button; the parser had matchedpreorderfrom a manufacturer link in the description. Shopware availability now prefers structured/buy-widget in-stock signals over descriptive preorder text, and a corrected latest snapshot marks Black in stock with Silver recorded as an in-stock alternate.2026-07-02T15:09+02:00 — User granted autonomy to fully crawl the currently approved Signal Sounds EU and SchneidersLaden sources and correct stale DB data. The crawler now applies Randem inventory in batches rather than per product, accepts
storeNameas fallback for EU rows missingstoreExternalId, treats shippable positive Randem rows as available, and marks missing authoritative Signal stock asunknown.2026-07-02T15:09+02:00 — Full imports appended 1339 Signal EU snapshots and 1841 SchneidersLaden snapshots after skipping normalized-URL conflicts already linked to other modules. Finaliser R-EQ (
4263) and Nibbler (4831) now show both stores in stock, while ADDAC812VU (4524) remains SchneidersLadendiscontinued.2026-07-02T18:25+02:00 — Elevator Sound official-domain crawl/import completed.
www.elevatorsound.comreturned 2887 normalized WooCommerce Store API products over 29 pages; after filtering with--include-ignored-matches=false, matcher output contained 1927 candidates, 1708 strong candidates, and 1077 accepted deduped import rows. The import upserted 1077 active Elevator Sound listings and appended 1077 snapshots without schema, RLS/policy/GRANT, Edge Function, Cron, push, or release changes.2026-07-02T18:25+02:00 — Elevator Sound crawl inspection found condition/order/accessory pages (
B-Stock,Ex-Demo,pre-order,accessory) that otherwise scored as strong exact matches, plus Tiptop 909-family false positives from shared numeric compact-code aliases. The matcher now detects hyphen/space noise terms and only uses bare compact-code aliases when the code contains letters, preservingADDAC812VU-style matching while blocking shared numeric suffixes.2026-07-02T18:29+02:00 — Runtime snapshot of
/modules/details/4263confirms Elevator Sound renders in Store prices with£380.00,Available now, andUnited Kingdom, alongside four other store rows for comparison.2026-07-02T18:53+02:00 — Control was selected as the next easiest store based on live probes.
www.ctrl-mod.com/products.jsonis a straightforward Shopify catalog; Found Sound also has Shopify JSON but its first sample and product taxonomy are heavily used/condition oriented; Synthshopproducts.jsonfailed TLS handshakes from this environment. Control official pages identify the store as Brooklyn, New York / US.2026-07-02T18:53+02:00 — Added
shopify_product_jsonlocal-crawler support and Control normalization. The normalizer records USD prices from decimal Shopify variant prices, product URL from handle, availability from variants unless pre-order tags/titles are present, image URL, vendor, product type, tags, selected variant ID/title/SKU, and available variant IDs.2026-07-02T18:53+02:00 — Control full crawl/import completed without schema/RLS/policy/GRANT/deploy/Cron/push/release changes.
products.jsonreturned 3930 normalized products over 16 pages; matcher output contained 658 candidates and 630 strong candidates; importer accepted 524 deduped rows, upserted 524 active listings, and appended 524 snapshots. Latest accepted distribution is 88in_stockand 436out_of_stock.2026-07-02T19:17+02:00 — Found Sound was selected after Control based on live probes.
https://foundsound.com.au/products.jsonworks but is broad, condition-heavy, and rate-limited; the officialhttps://foundsound.com.au/collections/eurorack/products.jsonsurface is narrower and returned 2737 normalized Eurorack products over 11 pages. Synthshop remained blocked by TLS/connection failures from this environment.2026-07-02T19:17+02:00 — Official Found Sound shipping policy says the store is a brick-and-mortar shop with pickup at 110 Albert Street, Brunswick East, Victoria 3057 and worldwide shipping, so the remote/local store row uses
country_code = AU,currency_hint = AUD, and adaptershopify_product_json.2026-07-02T19:17+02:00 — Found Sound full crawl/import completed without schema/RLS/policy/GRANT/deploy/Cron/push/release changes. The matcher output contained 1749 candidates; importer accepted 1100 deduped strong rows after excluding used/consignment/accessory hazards, upserted 1100 active listings, and appended 1100 snapshots through the existing import-local path. Latest accepted distribution is 39
in_stock, 1026preorder, and 35out_of_stock.2026-07-02T19:17+02:00 — Found Sound hazard handling differs from Control: Found Sound uses the configured
preordertag as match-noise suppression only, so valid upcoming-stock rows stay importable while preservingavailability = preorder. Used/consignment rows, covers, stackcables, andpower adapteraccessories still fall below importable status.2026-07-02T19:49+02:00 — Elevator Sound back-order correction: module
2100exposed that WooCommerce can reportis_in_stock: truewhilestock_availability.textsaysAvailable on back-order. The parser now treatsbackorder,back-order, andback orderin stock status/text/class asbackorderbefore considering in-stock flags.2026-07-02T19:49+02:00 — After the parser fix, the Elevator catalog recrawl normalized
https://www.elevatorsound.com/product/intellijel-multi-fx-1u/asbackorder; 539 latest Elevator snapshots with back-order evidence were corrected by appending newbackordersnapshots. Runtime/modules/details/2100now shows Elevator Sound asBackorder.2026-07-02T19:54+02:00 — Superseded during follow-up QA: availability classification preserves explicit Shopify preorder tags even when those tags are ignored for match-noise scoring. Do not convert Found Sound preorder catalog entries into available-now rows solely because a selected Shopify variant is available.
2026-07-02T21:38+02:00 — Added five more official Shopify-backed stores to the local crawler/import path and database registry without UI, schema, RLS/policy/GRANT, deploy, Cron, push, or release changes: Rubadub (
GB/GBP), Detroit Modular (US/USD), Nightlife Electronics (CA/CAD), Clockface Modular (JP/JPY), and Moog Audio (CA/CAD).2026-07-02T21:38+02:00 — Store discovery choices: Rubadub uses
/collections/eurorack/products.json; Detroit Modular uses/collections/eurorack-modules/products.jsonbecause/collections/eurorack/products.jsonwas empty; Nightlife Electronics uses/collections/modular/products.jsonbecause the narrower Eurorack collection only produced 25 accepted rows; Clockface Modular uses broad/products.jsonbecause no simple module collection handle was exposed; Moog Audio uses/collections/modules/products.jsonbecause the guessed Eurorack collection was empty.2026-07-02T21:38+02:00 — Imported append-only snapshots/listings for the five stores after dry-runs: Rubadub 454, Detroit Modular 1337, Nightlife Electronics 1141, Clockface Modular 758, and Moog Audio 421. Current active coverage is 12,378 active imported store listings across 3,058 distinct modules.
2026-07-02T21:38+02:00 — Shopify crawler reliability and correctness fixes from the five-store run: use curl for Shopify JSON fetches because urllib hit 429s where curl succeeded, make Shopify catalog path/currency/noise config store-specific, expand matcher noise for open-box/special-order/opening-soon/no-longer-available hazards, and normalize Shopify discontinued/no-longer-available text to explicit
discontinuedavailability.2026-07-03T10:55+02:00 — User asked for five more store crawlers/imports and explicitly prioritized correct database data over UI. Added RobotSpeak, Cicada Sound, Intellijel, Schlappi Engineering, and Zlob Modular to the approved local crawler list and remote store registry without UI, schema, RLS/policy/GRANT, deploy, Cron, push, or release changes.
2026-07-03T10:55+02:00 — Imported append-only snapshots/listings for the five stores after one-by-one crawls and dry-runs: RobotSpeak 142, Cicada Sound 293, Intellijel 85, Schlappi Engineering 10, and Zlob Modular 1. Active Price Hub coverage is now 13,391 active listings across 3,170 distinct modules and 27 active stores.
2026-07-03T10:55+02:00 — Database QA after the five-store run found zero active missing latest snapshots, zero active latest rows without price/currency, and zero active duplicate store/product URLs for the new stores. Zlob remains intentionally sparse because the importer rejects low/no-price direct-store rows unless they are clean price-bearing module observations.
2026-07-03T11:11+02:00 — User clarified that the next follow-up should be more centered in Europe. Mainland retailer probing found Soundium as a stable Shopify-backed Lithuania retailer, while Music All In rate-limited before stable output, Sonic Sales produced zero safe module matches, Modular Synth Lab produced zero importable rows from a parts-heavy WooCommerce catalog, and multiple larger mainland shops returned 403/404/empty/non-crawler-friendly responses.
2026-07-03T11:11+02:00 — Imported append-only snapshots/listings for three clean mainland-Europe feeds after dry-runs: Soundium 4, Nano Modules 14, and Dreadbox 9. Database QA found zero active missing latest snapshots, zero active latest rows without price/currency, and zero active duplicate product URLs for the three stores. Active Price Hub coverage is now 13,418 active listings across 3,170 distinct modules and 30 active stores.
2026-07-03T12:00+02:00 — Deep technical review of the Price Hub feature completed (docs-only, no code changes). Live Supabase inspection confirmed the three Price Hub tables have RLS disabled with
anon+authenticatedholding full DML — escalated as the P0 approval item. Correctness gaps identified: (a)CURRENCY_TO_EUR_RATEinmodule-price-listings-card.utils.tsand its duplicate inmodule-price-summary.utils.tsomit AUD/CAD/JPY, silently dropping ~2,700 listings from price sort and market-price rollup; (b)parseDecimalPriceMinoris not currency-aware, so JPY (and future KRW/HUF/ISK/VND/CLP) rows are stored 100× too large and only render correctly because the frontend also divides by 100; (c) no stale-threshold enforcement on read even though Trust rules document one; (d) products removed from a store's catalog stayactiveforever; (e) thesnapshot-store-listingsEdge Function is hardcoded toadapter_kind = 'woocommerce_store_api'so ~72% of current listings would never refresh if cron were enabled; (f) local importer setsnext_check_at = now()for every row, which would produce a 13k-row backlog against the worker's 20/day cap; (g)cacheBust(['priceHub'])is wired for but never called; (h)catalog-crawler.ts(880 lines) is near the 1000-line layering error andimport-local-snapshots.ts(745 lines) is already past the 500-line warning. Integrated as MVP hardening + extended Structural + prioritized Polish checklists, plus new Risks rows and the P0 approval-queue entry above. No code was touched.2026-07-03T12:52+02:00 — Audited the remaining right-column retailer fallbacks after Store prices/search integration. Added Synthshop through Shopify
products.json(NOK), and added a genericcustomrecursive sitemap/product-metadata crawler for Exploding Shed, Milk Audio Store, Machineroom, and Escape From Noise. The generic path reuses product metadata tags, adds JSON-LD offer parsing for Milk Audio, sends the crawler user-agent for metadata pages, and supports store-level URL include/exclude filters. Smoke crawls wrote normalized products for all five; match smoke found candidates for Synthshop, Escape From Noise, Milk Audio Store, and Exploding Shed. Machineroom produced normalized products in the 25-product smoke but no early candidates, so it needs broader QA before import. Perfect Circuit remains blocked by Cloudflare/bot responses, Thomann by robots/Cloudflare/429/no stable feed, and Patchwerks by expired TLS plus no accessible Shopify JSON endpoint.2026-07-03T18:07+02:00 — User caught
https://schneidersladen.de/en/tiptop-audio-vcashowing available while the live Schneider page is preorder/ordered. Read-only Supabase inspection confirmed listing7475was a dueshopware_metadatarow with latest stalein_stock; live/parser diagnosis confirmednormalizeShopwareProductPagereturnspreorder. The local-only worker now includes Shopware metadata rows in scheduled eligibility and dispatches them through the shared Shopware parser. Added regression coverage for both worker adapter eligibility and the Schneider ordered/preorder page shape, then appended corrected snapshot38487(145.00 EUR,preorder,source = manual) for listing7475. At this checkpoint Edge Function deploy/Cron were still unapproved; this was superseded by the 2026-07-07 approval split where deploy was approved after validation and Cron stayed manual-only.2026-07-06T19:34+02:00 — User approved the P0 public-read/admin-write safety gate: anonymous and logged-in users should read Price Hub tables, RLS should protect writes, and crawler writes should use the usual administrator/service-role credential path.
2026-07-06T19:37+02:00 — User made fixing the crawler the new top priority and approved setting up whatever is needed to write clean Price Hub data to the database with local credentials.
2026-07-06T19:38+02:00 — Applied remote migration
secure_price_hub_rls: RLS enabled onstores,module_store_listings, andmodule_price_snapshots; anon/authenticated reduced toSELECT; public select policies added; service_role write grants retained;tg_price_hub_set_updated_atnow fixessearch_path = public.2026-07-06T19:39+02:00 — Local import/refresh scripts now default live write credentials to service-role key aliases only, reject anon/authenticated JWT keys before live writes, and support
PRICE_HUB_ENV_FILEso the crawler can use an external local env file without copying secrets into the repo. Secret values were not printed or committed.2026-07-06T19:40+02:00 — Verified the RLS-protected live write path with
PRICE_HUB_ENV_FILE=/Users/polyterative/Code/Scraper/.env pnpm price-hub:refresh-local --store=synthshop --max-pages=1: 88 snapshots/listings imported and one conflicting URL was skipped. Shopify/WooCommerce--max-productscaps now work.2026-07-07T10:35+02:00 — Reconciled bounded partial refresh policy: explicit single-store bounded partial refreshes and dry-runs are allowed with a non-blocking warning for local pilot work, but broad live
--store=all --max-products=...refreshes still fail closed with aBLOCKINGwarning to avoid accidental incomplete all-store imports.2026-07-07T10:40+02:00 — Frontend Price Hub estimated FX handling now uses one shared helper for module detail price listing ordering and recent-market summaries. It covers existing EUR/USD/GBP/CHF plus seeded AUD/CAD/JPY/NOK stores and keeps labels approximate; no parser, backfill, migration, or storage semantics changed.
2026-07-07T10:46+02:00 — Module detail Store prices now enforces the Trust-rule freshness policy in UI utilities: rows older than 14 days by
last_checked_at(or latest snapshotobserved_atfallback for legacy rows) stay visible but showLast seen/Stale data, lose current-price/best-price treatment, and sort after current known prices. The importer did not getcacheBust(['priceHub'])becausecacheBustis an Angular app-runtime cache invalidation operator; local Node import scripts run outside browser sessions and cannot invalidate already-open client caches without adding a remote notification/channel, which is out of scope for this no-schema/no-remote-change hardening pass.2026-07-07T10:52+02:00 — Safe product-disappearance deactivation is local-importer only and schema-free: full-catalog evidence requires no
--max-products, no explicit bounds, at least 25 crawled product URLs, and at least 5 accepted import rows. Eligible imports set missing active listings toactive = false,verification_status = stale, and compactlast_error = not_seen_since_full_catalog:YYYY-MM-DD; dry-run uses the same planner without writes. This intentionally skips small/partial crawls rather than risking a store wipe.2026-07-07T11:06+02:00 — Reviewer follow-up tightened disappearance deactivation evidence: WooCommerce/Shopify crawls that stop on a full
maxPagespage are treated as incomplete even when the default cap was used, and sitemap/metadata crawls with skipped product pages block deactivation. These conditions still allow imports but skip stale-listing writes.2026-07-07T11:31+02:00 — Local importer scheduling now avoids cron-backlog bursts: imported/upserted listings keep import-time
last_checked_atandlast_success_at, whilenext_check_atis deterministically staggered over the following seven days from stable store ID, module ID, and normalized product URL. The helper is exported and covered for determinism, bounds, and distribution; no remote data was written.2026-07-07T11:38+02:00 — Store price row hero colors now use a deterministic muted HSL color derived from normalized store slug when no hand-tuned
STORE_HERO_COLORSoverride exists. This avoids maintaining a 27+ store manual palette while keeping the original overrides stable.Production and release gates
Price Hub remains manual/on-demand; no Supabase Cron job is authorized. Any migration, backfill, retention, diagnostics, RLS, policy, or generated-type change must follow the documented approval, schema-change preflight, advisor, and before/after validation gates. No unrelated production-branch work, release, or push is authorized by this plan.
Related issues
Documentation impact
This issue preserves the complete migrated planning document. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Source and migration metadata
Source: internaldocs/workflow/plans/done/price-hub-experimental-store-snapshot-pilot.md
Migration status: migrated from local documentation