The owner token is per-browser localStorage, so a sign-in here doesn't carry across domains;
the banner hands you the token to paste into the new panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each Find & Stage result card's condition dropdown now fetches a price suggestion and fills the
price input (unless the operator typed one). New GET /admin/intake/suggest?release_id=&condition=
maps the Discogs grade to a band of DealGod's composed value (M/NM→high, VG+→typ, VG→mid, G+→low).
Sourced from /api/value (AU listings) now; per-condition discogs_full.release_market suggestions
slot into the one dealgod.value() call when the fleet scrape pipeline is live — UI shape unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(1) Inventory edit modal gains Cost + New/Used + a live Margin readout (price−cost, %); inv_edit accepts
cost_price + condition_type; inventory list returns them.
(2) Inertia/Warner SOH list = an xlsx that doubles as the order form (fill ORDER QTY, upload). New
/admin/intake/distro-sheet[/preview]: openpyxl parses header-mapped columns (ORDER QTY/UPC/ARTIST/TITLE/
FORMAT/PPD), takes rows with QTY>0 → shared _ingest_distro core (refactored out of the RareWaves path) →
staged NEW stock with cost_price=PPD, kind from FORMAT (cd/vinyl), catno kept. Upload+preview UI in New stock.
DistroItem gains kind+catno; RareWaves path now passes kind too. Verified on the real 3,368-row Warner sheet:
3 ordered titles → 4 copies, all resolved, kind=cd, cost from PPD. Added openpyxl to requirements.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After redirecting from /login with the token in localStorage, signin() read the (empty) #tok gate
input and overwrote TOKEN with '', so /me 401'd → removeItem + bounce back to /login → loop. Now keeps
the stored token when the box is empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Intake 'Find & stage' renders it.year (an int) through escapeH → (1977).replace is not a function →
the whole results render threw. escapeH now coerces via String() so numbers/null are safe everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- settings vault gains openrouter_api_key + openrouter_model (generic Connections page renders them);
/settings/test/openrouter shows OpenRouter usage/limit.
- RECORDGOD_AI_AGENT_PLAN.md: honest assessment + phased plan for a grounded, tool-calling AI assistant.
Core stance: tools not free SQL; read-only + propose-never-commit; pgvector lives in DealGod (reached via
its API), not recordgod-db; moat = grounded data, not the model.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WowPlatter self-heal idea, freed from its 3-hour import: one button scans in-stock items for
gaps (title/weight/cover) and backfills. Phase 1 = local backfill from the disc_cache mirror
(instant, unbounded UPDATE…FROM); Phase 2 = bounded Discogs re-fetch for release_ids missing from
the mirror or without cover art (rate-limited, re-run for the rest). Refactored _enrich → _fetch_release
so Heal can force-refetch (not just cache-miss). New /admin/intake/heal/{scan,run} + a 🩹 Heal admin view.
Live first run: 30,202 rows had no title/weight (migration never denormalized them → broke shipping
quotes); 30,193 healed instantly from the mirror, 239 needed Discogs (60/batch). Real win.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The intake sheet is read straight from the stored Google service account (discotag@…
inventory-sheets, migrated out of WowPlatter's wp_rmp_disc_google_service into the vault) — a
signed JWT (cryptography, no new dep) → Sheets API → private sheet, no public-share needed. Uses
the saved column_mappings (timestamp/release_id/media/sleeve/price/comment, 1-indexed). SKU from
the sheet's locale timestamp '2025-01-30, 14:01:02' → 20250130140102 (matches migrated stock).
Bulk import (both connected + paste-URL paths) now SKIPS SKUs already in inventory, so importing
the 34,752-row form log only stages the genuinely-new rows (live: 2,006 new / 32,746 skipped).
Preview shows new-of-total. Endpoints: GET/POST /admin/intake/gsheet/{preview,import}.
Also: .dockerignore now excludes disc_images/ (volume-mounted, was 916MB) — build context
918MB→1MB, deploy 13min(hung)→24s. The COPY . . was baking the image store into every image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Admin-only /admin/system; no maintenance buttons (PG autovacuums, infra owns backups).
The useful 10% of WowPlatter's System page; the WP cache/transients/WP-CLI/debug rest dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard: fast KPI tiles (instant /stats) + lazy 30-day revenue line + stock-by-format
donut. Reports rebuilt into a parameterized workbench — date presets (7d/30d/90d/12mo/all
+ custom from/to) × dimension (trend/genre/style/format/condition/payment/weekday/hour +
top releases/artists/labels/customers) × metric (revenue/count), each chart fetched ON
DEMAND. New /admin/report/{kpis,series,breakdown,top,stock} (whitelisted dims, bound dates).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>