Answer
Cards are made from Plays, the read, listening, ideas, stories, watchers and statics, healed as they are read, and ranked per person by one score with fixed first-screen seats; this page settles how a card is made, how the feed is ordered and which knobs tune it.
Today
Where cards come from
| Source | Maker | Source |
|---|---|---|
| Plays | cards-gen.js: 1 or 2 cards per Play; the title is the point, the body two sentences (25 to 35 words), every number in the fact sheet |
brew/cards-gen.js:1-3 |
| The read | instant cards at read time | brew/build.js:601 |
| Listening and ideas | social-feed.js heardCards, ideaCards; threads must pass community-gate.js |
brew/social-feed.js memory brew-community-seat |
| Stories | research.js (ads, trends, news, how-to, viral) |
brew/research.js:1-5 |
| Watchers | catalog every 3 hours, AI answers weekly (live-watch.js) |
brew/live-watch.js:1-5 |
| Statics | Play 21's static ads (statics.js) |
brew/statics.js:1-4 |
| Connected accounts | live-* cards, members only |
brew/connectors/index.js:324 |
Rules every card passes
- Fact gate: every money amount and percent must be in the fact sheet; strict mode also gates multipliers and large counts. brew/fact-gate.js:1-5
- Claims: a card says only what the brand itself says (
claims.js). brew/cards-gen.js:6-7 - Today's counts: a count row naming one fact takes today's value, never a rival's, never to or from 0, never a jump over 5×. memory brew-card-fit-and-counts
- The right button:
fitCtaandfitMediumheal the button and format as the card is read (a Reddit post never gets an Instagram format). brew/feed-service.js:9 memory brew-read-stream-backfill - One take: no card promises "3 ads"; one version, then Redo it. memory brew-one-version
- Plain words: one background pass rewrites title, body, reasons and steps in everyday words, numbers checked in code, cached in
plain.json(PLAIN_V3);PLAIN_OFF=1stops it. brew/plain.js:1-5, 18 brew/SCOPE.md - Proof: a shield with a count opens "Why this, and the proof", built with no AI from facts, accounts, quotes, rival ads and the pages read; a Play is never evidence; the proof must prove the claim (
proof-logic.js). brew/evidence.js:1-6 memory brew-proof-plain-make - One card per ask: six Plays saying "fix the pack prices" show once (
same-ask.js). brew/feed-service.js:27
The score
For each card and viewer, score() adds: brew/feed-service.js:539-568
| Part | What it is |
|---|---|
| value | the Play's grade (impact 1 to 5, confidence, effort) from value.js, one fast call per 20 Plays brew/value.js:1-6 |
| seat | fit to the viewer's role, lane and stated focus words |
| affinity | hearts and clicks by agent, campaign, format, product; minus "Not for me", with near titles sinking by 4 |
| fresh | streaming cards decay over 24 hours, others over 72 |
| fatigue | days seen, the agent's share of today's screen, quiet jams |
| feedback | "Fewer" minus 2 per step |
| tune | the member's own weights (less −4, more +4 per agent) and topics (+2 each, two at most) brew/docs/handover/engineering/12-ui2-service-api.md |
| told and lift | a Play rewritten on the team's learnings +6; team cards +10, learned up +8 |
| contra | the team said it does not work: −40 until "bring back" |
| extra | urgency (sold out, days left), a first draft to look at (+3), statics and ads (+2), live account cards |
| behind | its Play is being rewritten: −8 |
| chan, pace | channels the viewer runs and their ad pace |
The layout
- Pins from the "new cards" pill sit on top for 30 minutes. brew/feed-service.js:700
- Two picture cards for every text card, unless the other kind is more than 2 points stronger. brew/feed-service.js:703-706
- Social seats: position 3 the best community reply or post, 7 the best picture post, 9 another text card. brew/feed-service.js:708-715
- Make seats: positions 2 and 6 hold an ad or post the brand can make, from different groups, never a rival's ad; a finished static takes one. brew/feed-service.js:716-722
- No two cards of the same sort in a row. brew/feed-service.js:723
- Storefront cards are hidden in v1 (
?storefront=1shows them). brew/SCOPE.md
Stories
Five rings always show: Brand today, Trends, Popular ads, Industry news, How to. Each story lives 24 hours from first seen; an expired one can still rank as a card. A ring whose stories expire reads its source again, at most every 2 hours. Story score is 0.4 freshness plus 0.6 strength. memory brew-stories-and-cards brew/feed-service.js:1312
Tomorrow
| Item | Version | Tag |
|---|---|---|
| One score, fixed seats, plain words and proof on every card | v1 | Stays |
| Ranking weights checked on fixtures from several brands | v2 | New |
| Cards in "we" voice across every writer | v1 | Changes (open from QA 25 Oct) memory brew-qa-round2-2026-10-24 |
| Storefront cards back in the feed | v2 | Changes |
Ranking tests for score, rank and feed |
v2 | New |
How we do it
- When "X cards never show", check where the ranker puts them before blaming the source. memory brew-community-seat
- Before shipping a card-rule change, compare old and new code on every saved card:
node tools/audit-card-fit.mjs DIR --before
memory brew-card-fit-and-counts
- Heal wherever text is read (pack load,
pub, jam brief), not only at load; shared jams and workspace copies outlive pack heals. memory brew-one-version - New brand routes must be added to the three path regexes in
feed-service.js, or they return "not found". memory brew-drop-learnings
Numbers
| Metric | Definition | Where measured |
|---|---|---|
| Cards served per brand | Visible cards in a member's feed | GET /api/tenants/:t/feed |
| Wrong-medium and empty-asset cards | Cards failing the fit audit | tools/audit-card-fit.mjs (0 of 9,540 on 22 Oct) memory brew-card-fit-and-counts |
| Proof coverage | Share of cards whose numbers all match a source | evidence.js counts (Proposed to aggregate) |
| Feed load time | Server time for the feed answer | Server-Timing, [slow] log over 1.5 s |
Open
- Are the ranking weights right? Proposal: build fixtures from the five partners and freeze the top 20 per brand as a test, then tune.
- Should Storefront come back for v2? Proposal: yes for Shopify brands with a connected store only.