Commit Graph

28 Commits

Author SHA1 Message Date
type-two
5551963ef4 feat(3d): procedural cyclorama / infinity cove (new — not in WowPlatter)
The hip-hop room's north section is a real cyclorama (white curved infinity wall). Added a data-driven cove:
virtual_space gains cyclorama (comma list of north/south/east/west) + cyclorama_radius + cyclorama_color.
buildCyclorama() sweeps a profile per wall — [flat wall down to R] → [quarter-circle fillet radius R] →
[white floor lead-in] — so the floor curves up into the wall with no hard seam (the seamless studio look).
Cyclorama walls skip their flat wall (the cove replaces it). Set on hiphop-room (space 3): cyclorama=north,
R=0.9. v1 = single-wall cove (back curve); 3-sided + rounded vertical corners + partial-wall extent for the
L-shape = next iteration, all tunable via the columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:47:11 +10:00
type-two
d489e8807d feat(buylist): wishlist scarcity ranking via DealGod /api/supply
DealGod shipped POST /api/supply (store_count + discogs_seller_count per release). RecordGod half:
PRICEGOD scrapes the RareWaves wishlist (www.rarewaves.com/pages/wishlist) → /products/<ean>- barcodes +
colour-from-slug → POST /admin/wishlist/scarcity → resolve barcodes → batch DealGod /api/supply → upsert
buylist (store_count/au_copies/lowest_au/discogs_seller_count/discogs_lowest + already_stocked). New 🎯 Buy
list admin view ranks rarest-first (store_count asc, sellers asc), flags colour vinyl + already-stocked.
Endpoints /admin/{wishlist/scarcity, buylist, buylist/clear}; _dealgod_supply batches ≤200. Verified e2e on
6 real wishlist items: ranked 0-store rarest first, AU prices live, sellers backfill per DealGod's pin sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:07:48 +10:00
type-two
926468a8b5 feat(distro): RareWaves order ingest → cost-tracked NEW stock + approval landing
End-to-end: PRICEGOD scrapes a RareWaves order (account.rarewaves.com/orders/<id>) → POST /admin/intake/distro
→ resolve barcode→release_id (normalised: DB barcodes stored inconsistently, functional index on digits-only +
EAN/UPC leading-zero variants; Discogs barcode-search fallback) → stage one NEW copy per qty with cost_price +
cost_source, in_stock=false. Idempotent per order. New '/admin/newstock' approval queue: staff confirm/re-pick the
release (trgm search), set retail, flip new/used → publish (staged=f, in_stock=t). Bulk publish-all-matched-&-priced.
Backend verified e2e (ingest→queue→approve→live, unapproved stay staged). PRICEGOD: rarewaves.js content script +
account.rarewaves.com host perm + wowplatter.distro + bg rwDistro handler (needs extension reload to test live).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:13:48 +10:00
type-two
4c462861f5 feat(inventory): cost_price + cost_source + new/used split (distro-ingest foundations)
Buying stock from distros (RareWaves discount sales) → need per-COPY cost + a new/used flag.
- inventory.cost_price (per copy — costs vary per copy of the same release) + cost_source ('rarewaves #592619')
- inventory.condition_type 'new'|'used' (default used); backfilled 451 sealed → new
RareWaves analysed = Shopify; order pages give barcode (in /products/<ean>- handle) + title + qty + unit cost
($X.XX/ea). Wishlist = Klevu app (harder). Full plan in RAREWAVES_INGEST_PLAN.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:49:51 +10:00
type-two
9a66f59bfe feat(auth): staff email+password login + password reset (login stays token-based under the hood)
Staff were stuck pasting a raw bearer token with no reset. Now: a proper /login page (email+password)
that exchanges to the staff member's existing token (SPA unchanged downstream). PBKDF2 hashing (stdlib,
no bcrypt dep). New: POST /auth/login, POST /auth/change-password (self), POST /admin/staff/{id}/password
(admin reset). staff gains email(unique)/phone/pay_rate/password_hash. Staff admin UI: add/edit details,
set-password, and timecards now show pay (hours × rate). admin.html redirects to /login when unauthed +
bounces expired tokens; nav gets Change-password + Sign-out. Owner master token still works (break-glass on
the login page). Verified e2e: login, wrong-pw 401, token auth, admin reset invalidates old pw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:24:34 +10:00
type-two
f9ab9527f3 perf(search): pg_trgm fuzzy staff search — typo-tolerant, multi-word, ~100ms (was ILIKE seq scan)
The Postgres answer to WowPlatter's sluggish rigid search. Was ILIKE '%..%' → Seq Scan (3038 cost),
zero typo tolerance, and multi-word phrases ('bob marley legend 2022') returned NOTHING (needs a
contiguous substring). Now: pg_trgm GIN index on disc_release.search_text (already populated) +
word_similarity operators — :q <% search_text (filter, GIN-accelerated) ranked by :q <<-> search_text.
Threshold pinned at 0.45 on the DB (recall vs speed sweet spot ~140ms; 0.3 too loose/slow, 0.6 too strict).
Added the missing disc_release_format(release_id) index (was seq-scanning per row) + inventory.title trgm
for stock-finder. All DDL idempotent in _STARTUP_DDL. Live: 'bob marly survivl' (2 typos) -> Survival in 117ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 16:10:36 +10:00
type-two
88172924eb feat(intake): three-source intake — internal lookup + Discogs collection + Google Sheet
Replaces the bare release-id box with a tabbed Intake that all feeds one _stage() core:
- Find & stage: search the local Discogs mirror (instant) with a Discogs-API fallback for new
  titles; pick a release → condition/price → stage. Cards show cover/artist/year/label/format.
- Discogs collection: lists the OAuth token's own folders (mrp3, 152 with stock) → page through
  releases, reads media/sleeve/price from the collection's custom fields, stage-all-on-page.
- Google Sheet: paste the share URL → public CSV export (no Google creds) → auto-detect columns
  (release id/media/sleeve/price/comment/timestamp) → preview → bulk import. SKU from the
  Google-Form timestamp (YYYYMMDDHHMMSS), matching migrated stock.

Staging enriches title/artist/cover on demand: a disc_cache miss fetches the release from the
Discogs API and grows the local mirror (disc_cache + disc_release + artist + label), so internal
search + storefront get richer with every intake. Verified end-to-end on the VPS (incl. enrich of
a not-in-cache release) + sheet-parser self-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:05:46 +10:00
type-two
988e6437cb feat(shop): per-track Apple previews (lazy iTunes) + jog mini-player + dead-YouTube check
/shop/audio-tracks/{id}: resolves each track's preview from its Apple song id via iTunes lookup
(au), cached in disc_release_track.apple_preview. /shop/audio validates YouTube via oembed (200=live),
caches dead in disc_release_video.dead, returns one live id. release.html + kiosk.html get a per-track
▶ on each tracklist row feeding a mini-player with a jog/seek bar (falls back to a single Listen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 23:44:17 +10:00
type-two
bab0826fea feat(shop): in-store kiosk page — touch browse, locate-in-store, scan-to-buy QR
site/kiosk.html: full-screen themed kiosk (idle attract cover-wall → tap → browse with
big search + genre chips + record grid → detail with tracklist, in-store locator (crate ·
rack) and a QR to buy on the customer's phone). 75s idle returns to attract. /shop/release
copies enriched with crate/rack location (public-safe). Route /kiosk. Better than WowPlatter's
kiosk: locate + phone-QR front and centre. Audio previews deferred (no internal audio yet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 23:01:54 +10:00
type-two
5351cfb9c2 feat(pos): rebuild Sales — 3 tabs, smooth checkout + tender/change, print+email receipts
7 tabs → Register / History / Settings. Register: search/scan + cart + checkout flow
(cash tender→change, split tenders, layby deposit, notes) → completes → receipt auto-pops.
Receipts now server-rendered (app/receipts.py, one template) with per-item discounts, GST
breakdown, split lines, tendered/change, store header/footer; Print opens a clean window
(no visibility hack); Email via app/mailer.py (stdlib SMTP, creds in vault, admin-only) with
a Settings 'send test' button. History merges past sales + laybys (reprint/collect). Settings
holds currency/GST/discount + receipt header/footer + promotions. sales gains amount_tendered
+ notes; SaleIn carries tendered/notes; /sales/{id}/receipt + /{id}/email + /email-test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:29:42 +10:00
type-two
b46930d468 feat(staff): time clock — clock on/off = shift = timecard
staff_shift table (one open shift per staff via partial unique index). /admin/clock
(status+today total), /clock/in (idempotent), /clock/out; /admin/timecards (admin)
= per-staff hours + recent shifts over a window. search.html top bar gets a live
clock widget (🟢 On 2h14m · Clock off · Log out; Clock on when off) ticking every 30s;
Log out warns if still clocked on. admin Staff tab gains a Timecards report.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:40:02 +10:00
type-two
5e2a9e7ab1 feat(auth): staff accounts + roles — operators run the store, admins keep the keys
Token resolver now checks the env owner-token (admin) then a staff table (name/token/
role). New require_admin gates /settings/* (API keys/connections) + staff CRUD; everything
operational (search/scan/inventory/labels/intake) stays on require_token so staff can do it.
admin.html: /admin/me drives ROLE; data-admin nav (Staff + Connections) hidden for staff +
go() blocks the views; new Staff tab to add operators, set role, reset/show token once,
deactivate. Tokens shown once on create.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:33:19 +10:00
type-two
48f8579bff feat(shop): P4 wantlist — public 'request a record' page + admin queue
Storefront /wantlist form (themed, email-keyed, guest-friendly) posts to
/shop/wantlist → wantlist table (lazy DDL). Release pages deep-link to it
(prefilled; 'not in stock — request this record' when no copies). Admin gets
a Wantlist tab: pending/found/fulfilled queue with one-click status actions.
Default storefront menu → Records + Wanted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:29:00 +10:00
type-two
28a46615a5 feat(shop): entity browse pages — artist/label/genre/style storefront views
records.html reads /artist|label|genre|style/{val} from the path, sets the
filter + an entity header (artist/label name via /shop/{kind}/{id}). /shop/browse
gains artist/label EXISTS filters + primary artist_id; release page links
artist→/artist/{id} and genre/style pills→/{kind}/{name}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:18:15 +10:00
type-two
fcc3d7b2ff feat(storefront): P0 — public Browse (faceted release catalog) + Release detail pages + /shop/{browse,facets,release} API; Woo-transacted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:37:04 +10:00
type-two
d6e75667bf feat(customers): CRM — list with spend/orders/last + record page (overview stats, editable info, mailing opt-in, sales history)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:17:11 +10:00
type-two
ddaa6ecedf feat(images): RecordGod WebP cover cache (/img/r/{release_id}) — same-VPS, configurable DISC_IMAGE_DIR, no DealGod dep
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:45:31 +10:00
type-two
879a58992f feat(collections): Collections CRUD + fallback locator + Reorganize scan/apply/compress API + Collections UI
- collections_routes.py: /nav/collections CRUD over virtual_collection + /nav/locate (exact-then-rules)
- navigator_routes.py: /nav/reorg/{scan,apply,compress} (atomic re-file)
- disc_cache gets year/genre_ids/style_ids cols for rule matching (backfill TODO)
- /search: collections list + locator fallback shows the should-be-here zone for unfiled hits

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:56:34 +10:00
type-two
53b1a64a48 feat(nav): light/dark switch every page + store→rack→crate navigator
- nav.js owns a light/dark toggle (persisted, default light) across all owner pages
- /nav API: spaces, store-layout, rack/{id}, crate/{id} over migrated virtual_* geometry
- /search rebuilt as Store→Rack→Crate drill-down (true rack rotation, per-level slot grid,
  crate facing arrows + BACK/FRONT/L/R, search→locate)
- facing convention (directionYaw, slot-grid, rotatedArrows, world composition) in RECORDGOD_NAVIGATOR_PLAN.md
- no-cache HTML routes so deploys show instantly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:33:24 +10:00
type-two
eb502efaf4 feat(pos): full Sales console + migrate customers/discounts/settings 2026-06-22 02:40:35 +10:00
type-two
584fd140ad feat: Search/locate workstation + Search & Sales in nav (cache-bust nav.js) 2026-06-22 01:57:54 +10:00
type-two
80ff669cac feat(pos): Sales tab — ring-up, discounts, payment plans, holds, locate 2026-06-22 01:43:14 +10:00
type-two
19e3cb131f feat: clean stub URLs (/builder /shop /admin /dash) + store geometry + storefront 2026-06-21 23:16:57 +10:00
type-two
a9076af19d feat(builder): storefront customizer 2026-06-21 21:29:55 +10:00
1f388f241c feat(admin): MEGA admin dash + /admin endpoints (stats/inventory/orders) 2026-06-21 19:19:56 +10:00
e1fcf94f93 feat: standalone RecordGod web UI — landing, control-room dash, encrypted secrets vault
- app/vault.py: Fernet-encrypted credential store (app_secret table); key lives only in
  RECORDGOD_SECRET_KEY (.env, gitignored) — refuses to store anything if the key is unset.
- app/settings_routes.py: admin-gated GET/POST /settings/secrets; values encrypted at rest,
  NEVER returned. Known fields: Woo / Discogs / Cloudflare / DealGod credentials.
- site/index.html: recordgod.com landing. site/dash.html: control room (token gate +
  paste-your-credentials form). main.py loads .env, mounts site at /, store at /store.
- Proven: 401 on bad token; DB holds ciphertext (gAAAA…), not plaintext.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:40:07 +10:00
8b48010b50 feat(virtual): lift the 3D store out of WordPress — scene API + raw Three.js renderer
- migrate_virtual.py: all 15 wp_rmp_disc_virtual_* tables → Postgres (~700 rows).
- app/virtual.py (public): GET /virtual/scene (geometry + slim front covers enriched
  from discogs_full), /virtual/crate/{id}/records (digging), /virtual/locate (locate-stock).
  Replaces payload.php — no WordPress bootstrap.
- webstore/index.html: walkable raw Three.js store (room/racks/crates from the data,
  pointer-lock FPS, click-to-dig hook), served same-origin at /store.
- Proven: 51 racks, 346 crates, 277 covers; locate release→crate works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:59:52 +10:00
d75f27a691 feat: RecordGod founding — service scaffold + MariaDB→Postgres migration
- FastAPI twin of DealGod (async SQLAlchemy/asyncpg). wowplatter/v1: ping,
  inventory/lookup, inventory/intake (enriches from discogs_full by release_id).
- schema v2: unified inventory (vinyl+general), slim crate, sales/sale_items.
  Discogs catalog NOT copied — joined from discogs_full at read time.
- migrate.py: 34,543 shop rows moved (vs ~3.2M in the WowPlatter clone).
- Smoke test green; plan/readme carry the architecture + UI boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:18:50 +10:00