The whole flow, and the three forks I need you to settle
This walks the entire path a person takes — from the first paint of /start through gratitude, the fork, free browsing, account creation, the sharing preference, and buying a tier. Every screen is clickable. Where a real decision exists, both options are mocked side by side rather than chosen for you.
community_records.What I checked before drawing anything
- Prod is serving gratitude now —
GET lab.joyos.global/api/community-gratitudereturns200with{"entries":[…1 entry…],"cardCount":12}. The old "returns[]" note in the goal file is stale. But one real entry against a twelve-card layer means the sparse state is the real state on production today. - The API returns no author at all — every entry is
{ text }. There is no name, no initial, no identity in the payload. Attribution is not a styling decision; it needs a new field end to end. - Anonymous gate submissions have no author to recover. The endpoint's own comment: "actor_identity is set reducer-side to ctx.sender (the SPACETIME_TOKEN service identity)." Every anonymous entry is stamped with the service account, not a person. Those rows can never be attributed retroactively.
- An opt-IN already ships —
src/components/onboarding/CommunityGratitudeOptInPrompt.vue, default OFF, persisted throughset_community_gratitude_opt_in. Its own comment reads "OFF by default (silence is not consent)". That is the opposite default from "unless they've opted out." - Staging's feed is full of test junk — the tunnel serves entries like
"Wall UI check — e2e-wall-ui-1785114011693". If attribution ships, that junk becomes visibly attributed junk.
Sources: live curl against prod and the staging tunnel; functions/api/community-gratitude.js; the opt-in component and its spec; project memory on the gate, the fork, auth boundaries, and the three-step checkout.
/start, at first paint
Gratitude is on screen the instant the page renders — no hover, no scroll, no interaction. Use the switches to see the attribution treatments against the three data states that actually occur.
What are you grateful for today?
First name only
"— Marcy" under the line. Warm, unmistakably a person, low exposure. Gratitude is written in a soft, reflective register; a surname on it reads like a directory entry, and it is the version a member will regret. First name is the smallest treatment that still satisfies "show who submitted it."
Initial mark
A small gold-ringed monogram plus first name. Reads more designed and gives the floating layer a second visual rhythm, but it adds a graphic element to a background whose whole job is to stay quiet. Offered because it is the one that most looks like a product; A is the one that most looks like a community.
Attribution cannot be rendered today for any existing entry. The API returns { text } with no author, and anonymous gate rows carry the service identity rather than a person's. Shipping attribution means: add an author field to the record write, expose it on GET, decide what an anonymous entry displays, and accept that every row already in the table is permanently unattributable and will render as it does today. Whatever you pick below applies going forward only.
The sparse state is the real state
Prod is serving one entry against a twelve-card layer right now. "Visible from the start" is therefore a data problem before it is a design problem. Three ways to handle it, and this needs an answer too — flip the switch above to Sparse and Empty to see the difference:
- Render only what exists — one card floating alone. Honest, and looks broken.
- Seed the wall with real gratitude from Kristin and early members, clearly attributed to them. Genuine, and needs her to write them.
- Reduce the card count to the number of real entries and let density grow with the community, with a quiet line at the base until it fills. This is what the mockup shows for the sparse and empty states, and it is my recommendation.
Also worth naming: staging's feed is currently test strings. Whatever ships needs the junk cleared, or the wall will attribute e2e fixtures to real names.
They submit — with no account
This is the moment that breaks the tidy story. The person has just written something personal, and there is nobody to attach it to.
Received.
Sign it "— a guest"
Keeps every card structurally identical, so the wall doesn't look half-finished, and it quietly tells a visitor that named cards belong to members. No data is collected, nothing changes about the anonymous path, and it composes cleanly with the free-browsing rule.
Ask for a first name at the gate
One optional field beside the gratitude box. Gets attribution on anonymous entries immediately and populates the wall fastest — but it adds friction to the single most important door in the product, and an unverified free-text name is a moderation surface. Offered because it is the only option that makes anonymous entries genuinely attributed.
The fork, still with no account
Gratitude and the fork travel together — the standing rule is that wherever gratitude is required, the lane choice is required too. Learner opens freely. Creator is visible but locked, per the Catalyst-only rule.
Learning Lab
Creator Studio
Unchanged from the locked fork-door design and the Catalyst hard rule: the Creator wedge is never hidden, it reveals an upgrade path instead of an enter button. Nothing here is new; it is drawn so the composition with onboarding is visible.
They browse the whole learning side, still anonymous
The hard rule is that learning-side browsing needs no login. So there is no account, and therefore nowhere to store a sharing preference. This is the structural fork you have to settle.
Onboarding begins at account creation. Guests are never onboarded.
An anonymous visitor gets no onboarding step and no preference — their entries render under the anonymous treatment chosen on screen 2. The sharing question is asked once, at the first moment a real person exists to attach it to. This preserves free browsing exactly as specified and keeps the preference honest: it is a member setting, not a guest cookie.
Store a guest preference locally, migrate it at signup
Ask at the gate, hold the answer in local storage, adopt it when an account appears. Gives guests real control — but the preference is device-bound, silently lost on another browser, and creates a second write path for the same flag, which is exactly the kind of parallel path that drifts. Offered because it is the only option where a guest's choice is respected before signup.
Option C, for completeness: require an account to submit gratitude at all. It resolves everything cleanly and directly violates the free-browsing rule, so it is listed and not recommended.
An account appears — one of two ways
Both triggers land in the same place, so onboarding has one entrance rather than two.
They buy something
The account is created inside checkout, step two of the three-step flow. A free Joy Seeker account is created alongside the purchase so the dashboard, profile and community are available the moment they return from Stripe.
They want the Creator side
The locked wedge sends them to plans; buying Catalyst is the same checkout. Creator access is a tier consequence, not a separate signup.
In both cases the person lands back in the app authenticated for the first time, and the onboarding beats run — which is the next screen, and where the sharing question lives.
The sharing screen, both defaults, side by side
You said "unless they've opted out," which makes public attribution the default. What already ships is an opt-IN, default off, whose own comment reads "silence is not consent." Both are drawn below with the exact wording each would carry. I have deliberately not chosen.
The member who reads nothing gets published
Someone who clicks Continue without reading has their gratitude, and their first name, on a public wall that unauthenticated strangers see. Gratitude is written in a reflective, often vulnerable register. It also reverses a shipped default and a stated position that silence is not consent, so anyone who accepted the current OFF default would need their state preserved rather than flipped underneath them.
The wall fills fast. That is the real argument for it — the whole feature only works with volume.
The member who reads nothing stays private
Nothing is published without a deliberate act. Matches the existing component, the existing reducer, and the existing spec, so no migration and no reversal. The cost is real and immediate: a mostly-empty wall, which is the exact problem you are trying to solve, and it needs the seeding answer from screen 1 to compensate.
Default the text to shared and the name to private. Everyone's gratitude joins the wall immediately; the attribution line is what requires a deliberate opt-in. The wall is full on day one, nobody is named without choosing it, and "visible from the start" is satisfied without a privacy reversal. This is the option I would take if you want one recommendation from me.
Whichever you pick, the wording above is the wording that would ship — the copy changes materially between the two, so approving the default is also approving the sentence.
Buying a subscription
Three steps, per your standing preference: the listing, an account-creation modal, a handoff to Stripe's hosted page, and a receipt on return. Card details are never inline.
Open question worth naming: buying Catalyst is what unlocks the Creator side, so the upgrade path from the locked wedge and the plans page are the same purchase. If you want a distinct "become a creator" narrative around that purchase, say so — the mockup currently treats it as one flow.
Day two, and every day after
The gate is global — it fires on every page, and the fork travels with it. Onboarding does not.
Welcome back, Marcy.
What repeats daily
- The gratitude gate, on whatever page they were heading to.
- The lane choice, satisfied from their stored preference and re-promptable, not re-asked from scratch.
What runs exactly once
- The welcome beat.
- The sharing preference.
- The first-step coach.
Each beat is dismissed per member and persisted, matching how the existing onboarding components already gate themselves. A returning member sees the gate, not the onboarding.
One caveat to flag: today's beats self-gate partly on browser local storage. Clearing storage or switching devices can re-show a beat. If a member re-seeing the sharing question would be a problem — and with a privacy preference it might be — the dismissal needs to live on the user record, not the browser.
Eight decisions. Reply "1 A, 2 yes, …" and I'll build it.
Same list as the companion doc. Recommendations are marked.
Attribution needs an author field written at record time and exposed on GET /api/community-gratitude, which returns { text } today. Every existing row is permanently unattributable — anonymous gate entries are stamped with the service identity, not a person. Attribution therefore applies to new entries only, and the wall will be a mix of named and unnamed cards for as long as the old rows are in it.