Reported by a member on X on 2026-08-03, the morning the Tuesday digest went out:
I need help, as I see a mail stating 2 sites you could trade with 1. Marketing · DR 0 · 0 given / 0 received & 2. SEO · DR 22 · 0 given / 1 received
What can be done here, in dashboard, Its hard to navigate.
He is right, and it is not a navigation problem.
What he was told to do
The digest rendered propose_trade partnerId="<uuid>" under every candidate row, and again
as the primary call to action. propose_trade has never existed. It appeared in exactly
two places in the repo — src/emails/digest.tsx and a doc comment in
src/lib/services/mask.ts — and is registered nowhere in src/lib/mcp/tools.ts, absent
from /docs/mcp, and backed by no service function and no server action.
Matching is entirely server-initiated. The only writer of exchange_matches is
upsertMatch, whose only caller is autoPair, whose only caller is setSiteStatus on
admin approval. A member cannot start a trade with a chosen partner in either interface.
It compounds. The digest cron skips anyone with an open match
(src/app/api/cron/digest/route.ts:94-102), so every digest recipient is guaranteed to
land on the "No open matches" empty state. The email offers partners and the dashboard is
empty by construction. That is the whole of his experience.
For the record, the data was fine — only the instruction was not. He is techfusiongear.com
(SEO, DR 21); his two candidates were veldo.app (Marketing, DR 0) and contextbolt.com
(SEO, DR 22), both real, both correctly masked.
What has been done
The advertisement is gone: the digest no longer prints a tool name or a bare uuid, and it
now states plainly that pairing is automatic and that a match arrives as its own email. The
/app empty state was corrected too — it claimed the Tuesday sweep pairs people, when the
sweep only mails. That was honesty work. The capability is still missing, which is what
this issue tracks.
What a real fix has to cover
proposeTrade(...) in src/lib/services/matches.ts, reusing upsertMatch and
orderPair so the sorted-pair uniqueness invariant keeps meaning something.
- A
propose_trade MCP tool in src/lib/mcp/tools.ts with its own budget in
src/lib/limits.ts. Budgets are keyed by tool name and shared across both interfaces on
purpose, so the browser cannot become the cheap way in.
- A browser surface on
/app. The member who complained was in a browser, and /app has
no browse or search section at all today. searchPartners exists but is MCP-only.
exchange_matches.proposedById already exists and is always null. This is what that
column is for. No schema change should be needed.
Open questions, all product rather than technical
- Must a proposal respect
candidateCategories / MATCH_GROUPS, or may a member propose
across any category? The auto-matcher bands deliberately; a human picking on purpose may
not want to be banded.
- What stops one member proposing to the entire member base? A rate limit is abuse control,
not a cap on how many open proposals a single site may hold.
- What happens when the partner already has an open match? The digest currently offers them
regardless — contextbolt.com was offered to him while its own match with clksy.co sat
in agreed. That was left as-is on purpose, but a propose flow forces the question.
- When the proposer owns several sites, do they pick which one is on their end?
Adjacent, found while investigating
DigestProps.standingNote is supported by the template and by notifyDigest, but the
cron never passes it (route.ts:157-162), so the standing footer has never rendered in
production.
- The cron uses only
sites[0].category (route.ts:104), so a member with sites in two
categories only ever receives digests for one of them.
Reported by a member on X on 2026-08-03, the morning the Tuesday digest went out:
He is right, and it is not a navigation problem.
What he was told to do
The digest rendered
propose_trade partnerId="<uuid>"under every candidate row, and againas the primary call to action.
propose_tradehas never existed. It appeared in exactlytwo places in the repo —
src/emails/digest.tsxand a doc comment insrc/lib/services/mask.ts— and is registered nowhere insrc/lib/mcp/tools.ts, absentfrom
/docs/mcp, and backed by no service function and no server action.Matching is entirely server-initiated. The only writer of
exchange_matchesisupsertMatch, whose only caller isautoPair, whose only caller issetSiteStatusonadmin approval. A member cannot start a trade with a chosen partner in either interface.
It compounds. The digest cron skips anyone with an open match
(
src/app/api/cron/digest/route.ts:94-102), so every digest recipient is guaranteed toland on the "No open matches" empty state. The email offers partners and the dashboard is
empty by construction. That is the whole of his experience.
For the record, the data was fine — only the instruction was not. He is
techfusiongear.com(SEO, DR 21); his two candidates were
veldo.app(Marketing, DR 0) andcontextbolt.com(SEO, DR 22), both real, both correctly masked.
What has been done
The advertisement is gone: the digest no longer prints a tool name or a bare uuid, and it
now states plainly that pairing is automatic and that a match arrives as its own email. The
/appempty state was corrected too — it claimed the Tuesday sweep pairs people, when thesweep only mails. That was honesty work. The capability is still missing, which is what
this issue tracks.
What a real fix has to cover
proposeTrade(...)insrc/lib/services/matches.ts, reusingupsertMatchandorderPairso the sorted-pair uniqueness invariant keeps meaning something.propose_tradeMCP tool insrc/lib/mcp/tools.tswith its own budget insrc/lib/limits.ts. Budgets are keyed by tool name and shared across both interfaces onpurpose, so the browser cannot become the cheap way in.
/app. The member who complained was in a browser, and/apphasno browse or search section at all today.
searchPartnersexists but is MCP-only.exchange_matches.proposedByIdalready exists and is alwaysnull. This is what thatcolumn is for. No schema change should be needed.
Open questions, all product rather than technical
candidateCategories/MATCH_GROUPS, or may a member proposeacross any category? The auto-matcher bands deliberately; a human picking on purpose may
not want to be banded.
not a cap on how many open proposals a single site may hold.
regardless —
contextbolt.comwas offered to him while its own match withclksy.cosatin
agreed. That was left as-is on purpose, but a propose flow forces the question.Adjacent, found while investigating
DigestProps.standingNoteis supported by the template and bynotifyDigest, but thecron never passes it (
route.ts:157-162), so the standing footer has never rendered inproduction.
sites[0].category(route.ts:104), so a member with sites in twocategories only ever receives digests for one of them.