Context / current state
Marketplace — Structured Inquiry and Offers (Tier 2, priority HIGH; requires stable public profiles / community trust layer). Replaces a loose email-only inquiry with a structured transaction seed: every serious buyer contact creates or updates a marketplace_transaction row, keeping v1 lighter than full realtime chat while avoiding a future migration from unstructured messages to transaction lifecycle records.
Safe local helper checkpoints complete (pure TypeScript — no schema/RLS/remote apply, backend methods, UI/routes, notifications, or deploy work):
validateAndNormalizeMarketplaceInquiryDraft — buyer inquiry draft: required nonblank listingId/buyerProfileId, trimmed message capped at 1000 chars, optional proposed price accepted only as a valid paired price/currency through the marketplace money util, whitelisted normalized output with buyerDestinationSummary only (never raw address snapshots).
- Transaction state-transition and snapshot-summary helpers with specs (2026-07-06).
buildMarketplaceLatestOfferSummary — latest-offer display: agreed price for accepted/paid/shipped/received/closed, else latest counter, else original proposal; malformed latest offer surfaces as invalid_price instead of silently falling back; whitelisted descriptor output.
Remaining work
- MVP: propose/approve
marketplace_transactions + transaction_status_events schema/RLS; buyer inquiry form (proposed price, message, optional address picker); seller accept/decline/counter actions; snapshot agreed price + address at accept with private shipping revealed only after explicit user action; notification hooks for state changes.
- Structural: counter-offer loop with latest-offer summary; one-active-serious-transaction constraint per listing once accepted; structured seller info card for payment/shipping instructions without free-form chat; admin-readable event history for dispute investigation.
- Polish: inline "Send offer" / "Send info" cards in the future message thread UI; templates for common seller responses. Stale inquiry auto-cancel stays deferred — MVP uses manual status changes only.
Key decisions (preserved)
- 2026-06-18 — Inquiry stores first contact as a transaction seed so lifecycle and chat can attach later.
- 2026-07-08 — Owner decisions: private shipping/contact data must never be exposed automatically — add explicit reveal controls for shipping details with the ability to hide/revoke later if the transaction goes wrong; no automatic stale-inquiry cancellation in the MVP.
- 2026-07-08 — Reviewer found the first latest-offer pass could fall back from a malformed counter offer to an older proposal; fixed so malformed latest offer data surfaces as
invalid_price.
- Non-goals: no generic chat thread in MVP; no payment processing; no file attachments; no feedback/reputation creation; no automatic collection mutation after accepted/sold state.
- Open question: decide whether completed-sale price datapoints require opt-in at this stage.
Gates (owner / remote)
Acceptance criteria
- A buyer can send a structured inquiry from an active listing.
- A seller can accept, decline, or counter without opening a generic chat.
- Accepted transactions snapshot agreed price and address details, but private shipping details remain hidden until explicitly revealed and can be hidden again.
- Only buyer and seller can read the transaction.
- Every state change creates an event row.
- Checkpoint behavior holds: inquiry drafts return
{ valid: true, inquiry } or { valid: false, errors }, never throw on malformed input, and exclude unknown/private fields.
Validation strategy
- Unit tests for state transition helper and buyer inquiry draft helper (happy path, optional price, invalid price/currency pair, malformed values, unknown/private field exclusion, message max length).
- Supabase service tests for buyer/seller permission-shape and cache busting.
- Component tests for inquiry form and seller response actions; E2E smoke for inquiry → accept happy path once routes exist.
pnpm lint, node scripts/checks/check-docs.cjs, and git diff --check.
Related issues
Documentation impact
internaldocs/workflow/plans/marketplace-structured-inquiry-and-offers.md is removed; the internaldocs/workflow/TODO.md Tier-2 line is removed.
- Two companion Marketplace plans (transaction lifecycle, realtime messaging) mention this plan by path in prose; their references will point at this issue when each plan is migrated.
- No README map row or CURRENT_FEATURE reference exists for this plan.
Preserved source plan (restored)
The earlier migration body condensed this source. The complete local source is restored verbatim below.
Marketplace — Structured Inquiry and Offers
Status
Safe local helper checkpoints complete. Priority: HIGH. Product area: marketplace / buyer-seller workflow.
Latest checkpoint scope: pure TypeScript latest-offer/counter-offer summary only. No schema/RLS/policy/migration/data,
backend methods, UI/routes, notifications, deploy/release/push, or production-branch work.
User intent
Users should be able to click a button that sends the important sale information as structured data: proposed price,
message, shipping destination/address choice, seller response, and next steps.
Product / roadmap fit
This replaces a loose email-only inquiry with a structured transaction seed. It keeps v1 lighter than full realtime chat
while avoiding a future migration from unstructured messages to transaction lifecycle records.
Current system analysis
- Roadmap now allows structured inquiry records while keeping generic chat/payment out of v1.
- Address book is planned as private reusable templates.
- Listing entities will expose active public listings and seller-only management.
Future strategy
Make every serious buyer contact create or update a marketplace_transaction row. The first message can be structured
without a full chat thread. Later realtime messaging and status lifecycle can attach to the same transaction.
Goals
- Add structured inquiry / offer creation from listing detail.
- Support seller actions: accept, decline, counter.
- Lock agreed price and selected address snapshots at accept time.
- Reveal sensitive contact/shipping details only after acceptance.
- Seed transaction status events from the first inquiry.
Non-goals
- No generic chat thread in MVP.
- No payment processing.
- No file attachments.
- No feedback/reputation creation.
- No automatic collection mutation after accepted/sold state.
Assumptions
- Buyers may inquire with a message and optional proposed price.
- Buyers can choose a saved shipping address, but sellers should initially see only broad destination summary until accept.
- Sellers can use structured response actions rather than composing every detail manually.
- Private shipping/contact details are never exposed automatically; the owning user must explicitly reveal them and must be able to hide/revoke them later if the transaction goes wrong.
Dependencies and sequencing
Depends on listing core. Depends on address book for full one-click buyer shipping payload, but can start with a destination
summary if address book lands later.
MVP layer
Structural layer
Polish layer
Proposed data model
marketplace_transactions
id
listing_id
seller_profileid
buyer_profileid
proposed_price_amount_minor
proposed_price_currency
agreed_price_amount_minor
agreed_price_currency
buyer_shipping_address_snapshot
seller_shipping_origin_snapshot
status
created_at
updated_at
closed_at
transaction_status_events
id
transaction_id
from_status
to_status
actor_profileid
note
created_at
Initial transaction statuses: proposed, negotiating, accepted, cancelled_by_buyer, cancelled_by_seller,
cancelled_mutual.
Proposed RLS (requires user approval)
- Buyer/seller-only read for transaction rows.
- Buyer can create inquiry for active listing if not seller.
- Seller can accept/decline/counter for own listing.
- Both participants can read transaction events; writes happen only through controlled backend methods / RPC if state
validation requires it.
File / surface map
src/app/features/marketplace/marketplace-transaction.utils.ts
src/app/features/marketplace/marketplace-transaction.utils.spec.ts
- Listing detail route from
marketplace-browse-detail-and-cockpits.md
- Address picker from
marketplace-shipping-address-book.md
src/app/features/backend/DatabaseStrings.ts
src/app/features/backend/supabase-add.ts
src/app/features/backend/supabase-get.ts
src/app/features/backend/supabase-update.ts
src/backend/database.types.ts
Acceptance criteria
- Current safe checkpoint: buyer inquiry drafts return
{ valid: true, inquiry } or { valid: false, errors }, never throw on malformed input, and exclude unknown/private fields.
- Current safe checkpoint: optional proposed price uses the existing marketplace money util and is accepted only with a valid paired currency.
- Current safe checkpoint: destination data is limited to a non-sensitive summary string until schema/RLS/address snapshot approvals exist.
- A buyer can send a structured inquiry from an active listing.
- A seller can accept, decline, or counter without opening a generic chat.
- Accepted transactions snapshot agreed price and address details, but private shipping details remain hidden until explicitly revealed and can be hidden again.
- Only buyer and seller can read the transaction.
- Every state change creates an event row.
Validation strategy
- Unit tests for state transition helper and local buyer inquiry draft helper.
pnpm test-headless --include="**/marketplace-transaction.utils.spec.ts" for happy path, optional price, invalid price/currency pair, malformed values, unknown/private field exclusion, and message max length.
node scripts/checks/check-docs.cjs after workflow doc updates.
git diff --check before delivery.
pnpm lint because TypeScript production code was added, if feasible.
- Supabase service tests for buyer/seller permission-shape and cache busting.
- Component tests for inquiry form and seller response actions.
- E2E smoke test for inquiry -> accept happy path once routes exist.
Safe local helper checkpoint — 2026-07-07
- Added
MarketplaceInquiryDraft and validateAndNormalizeMarketplaceInquiryDraft as local-only transaction utils.
- The helper normalizes only whitelisted primitive/id fields:
listingId, buyerProfileId, message, optional proposed price/currency, and optional buyerDestinationSummary.
- Proposed price is optional; when either price or currency is provided, both must be valid and parse through the existing marketplace money util.
- Message is trimmed, required, and capped at 1000 characters.
- Arbitrary address snapshots, private notes, live-row timestamps, and other unknown fields are deliberately excluded from normalized output.
Remaining gates: schema/RLS/policy/migration/data changes, backend methods, UI/routes, notifications, deploy/release/push, and production-branch work all require explicit future approval.
Safe latest-offer summary checkpoint — 2026-07-08
- Added
buildMarketplaceLatestOfferSummary as a pure local helper for future counter-offer UI and transaction cards.
- The helper chooses agreed price for accepted/paid/shipped/received/closed states, otherwise the latest counter price when present, otherwise the original proposed price.
- Malformed latest counter/agreed price fields return
invalid_price instead of silently falling back to an older offer.
- Output is a whitelisted descriptor only: availability, normalized minor amount/currency, source, label, optional awaiting actor, and whether the current actor can respond.
- Unknown/private fields such as address snapshots, internal notes, and raw message copy are not echoed.
- No schema/RLS/backend/UI/notification/deploy/release/push work was done.
Risks and open questions
- Accepted state must not automatically reveal email/phone/shipping details. Add explicit reveal and later hide/revoke controls for private shipping details.
- No automatic stale inquiry timeout in MVP; use manual status changes only.
- Decide whether completed-sale price datapoints require opt-in at this stage.
Coordinator-loop handoff
Implement after listing core MVP or bundle with it if the user wants a usable marketplace launch slice. Keep realtime chat
out until this structured transaction seed is stable.
Decision log
- 2026-06-18T11:26+02:00 — Inquiry plan stores first contact as a transaction seed so lifecycle and chat can attach later.
- 2026-07-06T18:02+02:00 — Added safe local transaction state-transition and snapshot-summary helpers with specs; no schema/RLS/remote apply/UI/deploy was done.
- 2026-07-07T13:10+02:00 — Added a safe local buyer-inquiry draft normalization checkpoint; no schema/RLS/backend/UI/notification/deploy/release/push work was done.
- 2026-07-07T13:10+02:00 — Destination data stays as optional
buyerDestinationSummary only; arbitrary private address snapshot objects are excluded until an approved snapshot contract exists.
- 2026-07-08T12:30+02:00 — Added the safe latest-offer summary helper and focused specs. Reviewer found the first pass could fall back from a malformed counter offer to an older proposal; fixed it so malformed latest offer data surfaces as
invalid_price.
- 2026-07-08T14:13+02:00 — User decided private shipping/contact data must never be exposed automatically. Add explicit reveal controls for shipping details and allow the owner to hide/revoke visibility later if the transaction goes wrong.
- 2026-07-08T14:13+02:00 — User decided Marketplace MVP should not auto-cancel stale inquiries. Keep status changes manual unless a later workflow explicitly adds automation.
Source and migration metadata
Source: internaldocs/workflow/plans/marketplace-structured-inquiry-and-offers.md
Migration status: migrated from local documentation
Context / current state
Marketplace — Structured Inquiry and Offers (Tier 2, priority HIGH; requires stable public profiles / community trust layer). Replaces a loose email-only inquiry with a structured transaction seed: every serious buyer contact creates or updates a
marketplace_transactionrow, keeping v1 lighter than full realtime chat while avoiding a future migration from unstructured messages to transaction lifecycle records.Safe local helper checkpoints complete (pure TypeScript — no schema/RLS/remote apply, backend methods, UI/routes, notifications, or deploy work):
validateAndNormalizeMarketplaceInquiryDraft— buyer inquiry draft: required nonblanklistingId/buyerProfileId, trimmed message capped at 1000 chars, optional proposed price accepted only as a valid paired price/currency through the marketplace money util, whitelisted normalized output withbuyerDestinationSummaryonly (never raw address snapshots).buildMarketplaceLatestOfferSummary— latest-offer display: agreed price for accepted/paid/shipped/received/closed, else latest counter, else original proposal; malformed latest offer surfaces asinvalid_priceinstead of silently falling back; whitelisted descriptor output.Remaining work
marketplace_transactions+transaction_status_eventsschema/RLS; buyer inquiry form (proposed price, message, optional address picker); seller accept/decline/counter actions; snapshot agreed price + address at accept with private shipping revealed only after explicit user action; notification hooks for state changes.Key decisions (preserved)
invalid_price.Gates (owner / remote)
Acceptance criteria
{ valid: true, inquiry }or{ valid: false, errors }, never throw on malformed input, and exclude unknown/private fields.Validation strategy
pnpm lint,node scripts/checks/check-docs.cjs, andgit diff --check.Related issues
marketplace_transactionsschema and status events defined here.Documentation impact
internaldocs/workflow/plans/marketplace-structured-inquiry-and-offers.mdis removed; theinternaldocs/workflow/TODO.mdTier-2 line is removed.Preserved source plan (restored)
The earlier migration body condensed this source. The complete local source is restored verbatim below.
Marketplace — Structured Inquiry and Offers
Status
Safe local helper checkpoints complete. Priority: HIGH. Product area: marketplace / buyer-seller workflow.
Latest checkpoint scope: pure TypeScript latest-offer/counter-offer summary only. No schema/RLS/policy/migration/data,
backend methods, UI/routes, notifications, deploy/release/push, or production-branch work.
User intent
Users should be able to click a button that sends the important sale information as structured data: proposed price,
message, shipping destination/address choice, seller response, and next steps.
Product / roadmap fit
This replaces a loose email-only inquiry with a structured transaction seed. It keeps v1 lighter than full realtime chat
while avoiding a future migration from unstructured messages to transaction lifecycle records.
Current system analysis
Future strategy
Make every serious buyer contact create or update a
marketplace_transactionrow. The first message can be structuredwithout a full chat thread. Later realtime messaging and status lifecycle can attach to the same transaction.
Goals
Non-goals
Assumptions
Dependencies and sequencing
Depends on listing core. Depends on address book for full one-click buyer shipping payload, but can start with a destination
summary if address book lands later.
MVP layer
listingId,buyerProfileId, and trimmed message capped at 1000 characters.buyerDestinationSummary, not arbitrary private address snapshots.marketplace_transactionsandtransaction_status_eventsschema / RLS.Structural layer
Polish layer
Proposed data model
marketplace_transactionsidlisting_idseller_profileidbuyer_profileidproposed_price_amount_minorproposed_price_currencyagreed_price_amount_minoragreed_price_currencybuyer_shipping_address_snapshotseller_shipping_origin_snapshotstatuscreated_atupdated_atclosed_attransaction_status_eventsidtransaction_idfrom_statusto_statusactor_profileidnotecreated_atInitial transaction statuses:
proposed,negotiating,accepted,cancelled_by_buyer,cancelled_by_seller,cancelled_mutual.Proposed RLS (requires user approval)
validation requires it.
File / surface map
src/app/features/marketplace/marketplace-transaction.utils.tssrc/app/features/marketplace/marketplace-transaction.utils.spec.tsmarketplace-browse-detail-and-cockpits.mdmarketplace-shipping-address-book.mdsrc/app/features/backend/DatabaseStrings.tssrc/app/features/backend/supabase-add.tssrc/app/features/backend/supabase-get.tssrc/app/features/backend/supabase-update.tssrc/backend/database.types.tsAcceptance criteria
{ valid: true, inquiry }or{ valid: false, errors }, never throw on malformed input, and exclude unknown/private fields.Validation strategy
pnpm test-headless --include="**/marketplace-transaction.utils.spec.ts"for happy path, optional price, invalid price/currency pair, malformed values, unknown/private field exclusion, and message max length.node scripts/checks/check-docs.cjsafter workflow doc updates.git diff --checkbefore delivery.pnpm lintbecause TypeScript production code was added, if feasible.Safe local helper checkpoint — 2026-07-07
MarketplaceInquiryDraftandvalidateAndNormalizeMarketplaceInquiryDraftas local-only transaction utils.listingId,buyerProfileId,message, optional proposed price/currency, and optionalbuyerDestinationSummary.Remaining gates: schema/RLS/policy/migration/data changes, backend methods, UI/routes, notifications, deploy/release/push, and production-branch work all require explicit future approval.
Safe latest-offer summary checkpoint — 2026-07-08
buildMarketplaceLatestOfferSummaryas a pure local helper for future counter-offer UI and transaction cards.invalid_priceinstead of silently falling back to an older offer.Risks and open questions
Coordinator-loop handoff
Implement after listing core MVP or bundle with it if the user wants a usable marketplace launch slice. Keep realtime chat
out until this structured transaction seed is stable.
Decision log
buyerDestinationSummaryonly; arbitrary private address snapshot objects are excluded until an approved snapshot contract exists.invalid_price.Source and migration metadata
Source: internaldocs/workflow/plans/marketplace-structured-inquiry-and-offers.md
Migration status: migrated from local documentation