Skip to content

fix(scout,listing,dashboard): JSON fence parsing, inventory quantity coverage, per-listing publish + Etsy image panel#45

Merged
brac merged 2 commits into
mainfrom
fix/scout-json-listing-publish-images
May 30, 2026
Merged

fix(scout,listing,dashboard): JSON fence parsing, inventory quantity coverage, per-listing publish + Etsy image panel#45
brac merged 2 commits into
mainfrom
fix/scout-json-listing-publish-images

Conversation

@brac

@brac brac commented May 30, 2026

Copy link
Copy Markdown
Owner

Fixes from a bug-hunting session covering Scout, Listing, and the dashboard.

Scout — fenced JSON no longer fails parsing

  • _strip_code_fences is now regex-based: handles a prose preamble before the ```json fence and requires the closing fence on its own line, so a literal ``` inside a JSON value can't truncate the payload.
  • Added a "raw JSON only, no fences" instruction to SYSTEM_PROMPT and a log.warning when the fence fallback fires (observability on how often Claude disobeys).
  • The status='error' row persistence is intentional (observability + 7-day dedup backoff) and is unchanged.
  • Tests: preamble, inline-backtick-in-value, and bare-JSON passthrough.

Listing — Etsy 400: Missing input parameter: [quantity]

  • Root cause was a stale @presswork/shared dist (source already sends quantity: 999); fixed operationally by rebuilding the shared dist.
  • Added regression coverage so it can't silently break: an MSW handler that 400s on a missing/zero offering quantity, plus a unit test asserting POD_VARIANT_QUANTITY (999) and an EtsyInventoryInputSchema round-trip.

Per-listing, cost-aware publish

  • resumePublish is now exported from @presswork/listing and consumed by the dashboard (workspace dep + transpilePackages). Moving it into @presswork/shared was rejected because its cone pulls in the Anthropic SDK + compliance/copywriter domain logic.
  • New publishListingNow server action (owner-auth + pending_publish stale-tab guard).
  • Detail page gains a confirmed "Publish to Etsy ($0.20 listing fee)" button, disabled while the listing agent is running; "Approve & publish" → "Approve" (it only queues). Compliance gates still run on publish (defense-in-depth preserved).

Etsy image management panel

  • getListingImages / deleteListingImage added to shared (+ mock fixtures, 204 No Content handling); offering quantity schema tightened to .positive().
  • New dashboard API routes (GET/POST/DELETE under app/api/listings/[id]/etsy-images/) and EtsyImagePanel to view images live on the Etsy listing and add/delete from the on-file pool (design PNG + mockups). v1 is add/delete only (no reorder).
  • SSRF guard: the image-upload POST allowlists https on Supabase / Printify / Dynamic-Mockups hosts (the URL is fetched server-side).

Verification

  • scout: 27 passed, 5 skipped · listing: 133 passed, 5 skipped · shared: 72 passed
  • typecheck clean (shared, listing, ledger, dashboard) · ruff clean

Known follow-ups (non-blocking)

  • @presswork/listing exports point at src/*.ts vs shared's dist/*.js — works today (only the dashboard consumes it via transpile) but should align before any plain-Node consumer imports it.
  • Minor polish: catch PublisherError in publishListingNow for a friendlier message; surface retry_count on the publish card when >0; debounce the panel's post-action refetch; mock DELETE fixture should return null.
  • Pre-existing lint: unused dynamicMockupsTemplate in listings.ts.

🤖 Generated with Claude Code

brac added 2 commits May 30, 2026 18:20
…coverage, per-listing publish + Etsy image panel

Scout:
- Replace startswith-only code-fence stripper with a regex extractor that
  handles a prose preamble before the ```json fence and requires the closing
  fence on its own line (so a literal ``` inside a JSON value can't truncate
  the payload). Add a "raw JSON only, no fences" line to SYSTEM_PROMPT and a
  log.warning when the fallback fires. Add preamble + inline-backtick +
  bare-JSON unit tests. Error-row persistence behavior is unchanged.

Listing (quantity regression coverage; source already correct):
- Add an MSW handler for the Etsy inventory PUT that 400s when any offering
  lacks a numeric quantity > 0, and a unit test asserting every offering
  carries POD_VARIANT_QUANTITY (999) and round-trips EtsyInventoryInputSchema.

Per-listing publish (cost-aware):
- Export resumePublish from @presswork/listing and add it as a dashboard
  workspace dep (transpilePackages). New publishListingNow server action
  (owner-auth + pending_publish stale guard). Detail page gains a confirmed
  "Publish to Etsy ($0.20 listing fee)" button, locked while the agent runs;
  "Approve & publish" relabeled to "Approve". Compliance gates still run.

Etsy image management:
- Add getListingImages/deleteListingImage to shared (+ mock fixtures, 204
  handling) and tighten offering quantity schema to .positive(). New dashboard
  API routes (GET/POST/DELETE) and EtsyImagePanel to view live Etsy images and
  add/delete from the on-file pool (design PNG + mockups). POST allowlists
  https on Supabase/Printify/Dynamic-Mockups hosts to prevent SSRF.
…can bundle resumePublish

The per-listing publish button imported `@presswork/listing`, whose exports map
pointed at raw TypeScript source. Next's webpack resolver then failed on the
package's NodeNext ".js" import specifiers ("Can't resolve './poller.js'"), and
a global extensionAlias workaround broke Next's own server chunk loading
("Cannot find module './901.js'").

Fix mirrors the proven @presswork/shared pattern:
- @presswork/listing exports now resolve `import` to compiled ./dist/*.js (types
  still from ./src), so ".js" specifiers map to real emitted files — no bundler
  resolution hacks.
- Add a dedicated ./publish subpath exporting only resumePublish, so the dashboard
  doesn't bundle the agent poll-loop (poller.js); dashboard imports
  @presswork/listing/publish.
- Dashboard gains predev/prebuild that run `tsc -b ../shared ../listing`, so dist
  is always present before next dev/build (fresh clones, CI, Vercel).
- Revert the extensionAlias next.config change.

Regression tests (packages/listing/src/publish.test.ts): resumePublish stays a
callable export, the ./publish subpath export points at an existing file, and
publish.ts stays decoupled from the poller barrel.

Verified: `next build` green (/listings + /api/listings/[id]/etsy-images build),
listing 136 + dashboard 155 tests pass, typecheck + lint clean.
@brac
brac merged commit f1c0bfe into main May 30, 2026
2 of 5 checks passed
@brac
brac deleted the fix/scout-json-listing-publish-images branch May 30, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant