Desktop/wide layout: section nav is visible in the header (How it works, Why, FAQ, About, Stats, Handbook, Log in). No hamburger.
Screens
Table of contents in three levels (chapter, screen, variant). Small preview cards at the selected Desktop/Mobile and Light/Dark combo — click a preview for full size, Left/Right arrows step through every screen, with a written description and a copyable link.
Captured at desktop and mobile. On mobile the header shows the Menu button; open it to reveal the same links stacked (tapping a link closes the menu). On desktop this is the landing without the hamburger.
Open the language switcher in the marketing header. Custom listbox (rounded panel, endonym rows with a check on the current locale) — not OS chrome.
The only state: imprint plus privacy, marketing chrome.
The only state: three convictions, verse, and forum CTA, marketing chrome.
Logged out. Heading Log in with your device, one Log in button.
Transient after a login click, before the ceremony finishes: spinner and Preparing your login….
Login begin or finish failed. Copy Something went wrong. Please try again. (login.error) and Try again.
GET /me 403 with the api wrong-account copy, or passkey finish with that same string. Alert You signed in with the wrong account. Please try again with the correct account. (login.wrongAccount) and Try again. The leftover session is cleared so the visitor is not left signed in. Try again starts authenticate-first login.
After Log in, the browser reports NotAllowedError (no discoverable passkey, or the visitor dismissed the picker). Heading Do you already have an account? with labeled Log in with existing account and Open a new account. No account is created until the visitor clicks Open a new account and completes the create ceremony.
Telegram or another in-app WebView detected. Heading Open this page in your browser; no Log in button; Open in browser and Copy link instead.
Open the light language switcher top-right. Custom listbox with endonym rows (English / Deutsch / Español / Filipino) — not a native OS select.
Heading Send help, explainer lead, Open the forum.
Signed in, no name yet. Your name and the name field at the top, Continue and labeled Skip pinned at the bottom of the screen. One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.
Signed in with a display name (or a skipped name) and no username. Your 21.gifts name, the username field, and Continue. One Menu top-right.
Signed in with a name (or a skipped name) and no address. Your Wallet of Satoshi address and the address field at the top, Continue and labeled Skip pinned at the bottom of the screen. One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.
Signed in with a name and address and rulesAgreedAt still null. First chapter (lead paragraph plus the accent-bordered The test callout) and Continue visible.
After one Continue: rule card with kicker Rule 1, heading Only free donations, body, and The test callout. Icon-only back is visible.
Rule card Rule 2 / Donors come first with body and The test callout.
Rule card Rule 3 / Contact stays in the app with body (no test callout).
Heading Welcome, muted lead, and the welcome list (app-fg check glyphs, not accent).
Heading Allowed, muted lead, and the allowed list (muted check glyphs).
Heading Better not, muted lead, and the better-not list (minus glyphs).
Heading Forbidden, muted lead, and the three forbidden groups (red cross glyphs).
Last chapter: muted Our house block (body plus emphasised closing paragraph) and I agree to these rules. That click POSTs agreement.
Last-chapter POST failed. Alert Could not save your agreement.
Last-chapter POST in flight. Agree disabled with a spinner; Our house still visible.
Gift icon with an integrated Bitcoin symbol, Welcome, Ada, with the dismissible laws hint box and rules/contact links, Active selected. Paid notes newest-first (Ada ₿5 then Carol ₿21); Bob's unpaid note is not visible. Composer with attach + Send icons. React (forum.react) on every top-level note. Posts do not show Send Bitcoin; Gift appears on a payable reply after Show reactions. Founder / Moderator / Verified pills beside the name when role is one of those three; basis has no pill (Carol is verified, Ada is moderator; Bob is basis and hidden on Active). One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.
A moderator sees an icon-only Delete post control in the note footer icon row with copy; confirming wraps to the next line. Other roles do not see it. The server independently checks the live role.
Delete post opens an inline confirmation: Delete this post and its reactions from 21.gifts? Confirm deletion (check) and Cancel deletion (X) are icon-only controls. Cancel sends no request.
While DELETE is pending, confirmation and cancellation are disabled and a spinner replaces the check. Successful deletion removes the post; stale refresh payloads cannot restore it in this session.
A failed deletion keeps the post and confirmation visible with an error and retry. Already missing posts (404) are removed from the local view.
A moderator who expands a note sees an icon-only Delete reaction control on each nested reply. Ordinary members do not. Parent still has Delete post.
Delete reaction opens inline confirmation: Delete this reaction from 21.gifts? Confirm deletion (check) and Cancel deletion (X) are icon-only. Cancel sends no request. The parent post stays.
While DELETE of the reply is pending, confirmation and cancellation on that reply are disabled and a spinner replaces the check.
A failed reply deletion keeps the reply and confirmation visible with Could not delete the reaction. Please try again. and retry.
Click All — Bob's unpaid note (Does anyone have spare sats this week?) is visible with Ada and Carol; list order newest-first (Ada, Carol, Bob).
Click No gifts yet (German: Noch ohne Geschenk) — only loaded notes with exactly zero sats appear. Bob is visible; paid Ada and Carol are hidden. This includes notes without a receiving wallet. The four filters use a two-column grid for readable labels on mobile. Active remains the default.
Active selected; last-visit stamp older than Bob's unpaid note; No gifts yet shows the count chip 1.
No zero-sat notes remain in the loaded list. Every loaded message has already received Bitcoin. appears; the filters and composer remain available. An entirely empty forum still uses the general empty state.
Click Most popular — paid notes ordered by sats (Carol ₿21, then Ada ₿5). Unpaid Bob is hidden.
Copy No message has received Bitcoin yet. Active selected, unpaid notes hidden, composer visible.
Empty copy No messages yet — be the first to write one. plus composer (attach + textarea + Post).
Loading copy Loading… while the messages fetch is in flight.
Load error Could not load messages. Please try again. plus Try again.
Click Post with an empty composer and no photo or video → Enter a message or add a photo or video. The composer caps at 500 characters (same as POST /forum/messages); over-length drafts show Keep it to 500 characters and are not sent.
On All (unpaid photo-only notes are hidden on Active): photo-only forum row from Ada with inline image (Photo from Ada) and the attach control visible in the composer.
On All: photo-only forum row from Ada with two stills (Photo from Ada twice, photoCount: 2) in ForumPhotoGallery (first still in view, next still peeks, 1/2 chip, dots) and the attach control visible in the composer.
After a successful post of caption Hello with this photo. plus a JPEG: the row shows Photo from Ada, then that text below the photo; the composer is empty again (attach + textarea + Post).
On All: forum row from Ada with two stills (Photo from Ada) in ForumPhotoGallery (first still in view, next still peeks, 1/2 chip, dots) and caption Hello with these photos. below the photos; the composer is empty (attach + textarea + Post).
Typed caption Caption before attaching a photo. in the composer; no preview yet; attach + Post idle.
Signed-in /welcome with the composer focused while visualViewport.height is 60% of innerHeight and offsetTop is 15% (iPhone Safari software-keyboard geometry). The rounded AppShell frame still fills innerHeight; it does not shrink to the short visual viewport.
JPEG preview (Selected photo) and Remove photo; textarea empty.
Two JPEG previews (Selected photo) and per-index Remove photo; textarea empty.
Preview plus caption Caption with selected photo., ready to Post.
Two JPEG previews plus caption Caption with selected photos., ready to Post.
MP4 preview in the composer and Remove video; textarea empty.
Video preview plus caption Caption with selected video., ready to Post.
After Remove photo, caption Caption kept after removing photo. remains; preview gone.
Attach in flight (Post disabled + spinner, no preview yet). Native file picker is OS chrome and is not a variant.
Same spinner, caption Caption while the photo is preparing. already in the textarea.
Post in flight: spinner on Post, composer disabled, preview and caption Caption while the post is in flight. still shown.
Forum row for Ada with caption Caption waiting for the photo to load. and hasPhoto, image bytes not yet loaded so no <img>. A failed photo fetch looks the same (text-only row) — not a separate variant.
Attach a GIF → Use a JPEG, PNG, or WebP photo, or an MP4, WebM, or MOV video.
Same alert with caption Caption with an unsupported photo. still in the composer.
Encoded JPEG over 1 MB → Keep photos under 1 MB and videos under 32 MB.
Eleven files → You can add up to 10 photos.
Same alert with caption Caption with a photo that is too large. still in the composer.
Same 11-file tooMany alert with caption Caption with too many photos. still in the composer.
POST fails after caption+JPEG → Could not post your message; preview and caption remain.
Open Menu top-right → Menu includes Home first (Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install, Log out, then a quiet Version {version} line (app.version)). Profile is one line (User + Profile; ₿ totals on the right only when a side is non-zero). Notifications shows an unread count on the right only when unreadCount > 0 (Ada’s default shot is 0, so no count). Messages shows a count on the right only when inbox unread > 0; Ada’s default shots are 0 so no number. Ada’s default welcome-menu shot has zeros, so no ₿ totals on the right. Living room rules and Contact each have an icon, optional Install app when an install offer exists, Log out, then a quiet Version {version} line (app.version). Language, theme, and number format live on /profile, not in this Menu. With both totals zero, the Profile link’s accessible name is Profile; otherwise it includes only the visible non-zero indicator labels. Other accessible names are unchanged. No English / Deutsch / Español / Filipino option rows. No native language select.
Open Menu with unreadCount 3 stubbed on GET /forum/notifications → Notifications shows 3 on the right (nav.notificationsUnread, accessible name Notifications, 3 unread). Messages shows a count on the right only when inbox unread > 0; Ada’s default inbox unread is 0 so no number. Other Menu rows match menu-open (Ada’s totals still zero). The installed PWA home-screen badge is the sum of notification unread, inbox unread, and staff-room unread (0 or 1). The Menu still splits the counts (Notifications vs Messages vs Moderation).
Open Menu with two unread inbox rows stubbed on GET /conversations → Messages shows 2 on the right (nav.inboxUnread, accessible name Messages, 2 unread). Notifications stay at count 0. Other Menu rows match menu-open.
Staff (moderator) Open Menu with GET /conversations/moderator-group stubbed unread true → Moderation shows 1 on the right (nav.moderateUnread, accessible name Moderation, 1 unread). Notifications and Messages stay at count 0. Other Menu rows match menu-open except Moderation is present because Ada is seeded as moderator.
Payable reply after Show reactions, Gift opened, amount filled, not submitted. Amount CTA is Pay (forum.payNow) on iPhone/iPod and Continue (forum.payContinue) on desktop. Live equivalent in the preferred fiat (no picker). No error, no payment QR, no wallet Pay button yet. The post itself does not show Send Bitcoin.
Payable reply, Gift amount submitted. Captured at desktop and mobile. On desktop the invoice card shows the Bitcoin payment QR, a top-left back control, and a Pay button with the Wallet of Satoshi icon. On a smartphone the amount form stays: disabled minted amount, the same Pay button, Waiting for payment… under it, back control, no QR. The preferred-fiat line is on pay-amount (rate stub); these invoice-step shots use the empty stats mock and stay ₿-only.
Same pay sheet captured at desktop and mobile. On a smartphone user-agent: the amount form stays after mint (disabled minted amount, Pay button with the Wallet of Satoshi icon, Waiting for payment… under it, back control, no QR). The preferred-fiat line is on pay-amount (rate stub); these invoice-step shots use the empty stats mock and stay ₿-only. On desktop this scenario shows the QR invoice card.
Payable reply, Gift amount submitted, but the author's wallet cannot mint a zap invoice. The pay sheet stays on the amount form and shows The author's wallet cannot receive this Bitcoin payment. Amount CTA is Pay (forum.payNow) on iPhone/iPod and Continue (forum.payContinue) otherwise. No payment QR and no invoice-step Pay with Wallet of Satoshi button.
Carol's Verified tag clicked; the explanation under that card header is visible (A moderator has met this person in real life and confirmed they are real.). Bob stays without a pill; Ada still shows Moderator.
Named member with living-room rules agreed and no Wallet of Satoshi address. Composer filled, Post clicked. RequirementsOverlay dialog Add your Wallet of Satoshi address with the profile Lightning Address field (LightningAddressForm variant=profile). No Skip. Close (X) is present.
Named member with living-room rules agreed and no username. Composer filled, Post clicked. RequirementsOverlay dialog Add your 21.gifts name with UsernameForm variant=overlay. No Skip. Close (X) is present.
Named member with living-room rules agreed, a Wallet of Satoshi address, and hasPosted false. After login on /welcome, IntroduceYourselfOverlay dialog Introduce yourself with body copy and Write an introduction. Close (X) is icon-only. - Actions: Close dismisses this mount only. Write an introduction (Button type="button" size="lg") dismisses the overlay, focuses the welcome composer (requestForumCompose / FORUM_COMPOSE_EVENT), and router.push('/welcome') only when the path is not already /welcome.
Named member with living-room rules dismissed and a paid forum note whose body is New: plus https://example.com/phish. Clicking that URL opens ExternalLinkWarning dialog Open external link? with the catalog body, the destination URL as break-all text, labeled Open link, and icon-only Close. Internal 21.gifts URLs on the same board do not open this overlay. - Actions: Close dismisses without opening. Open link (Button type="button" size="lg") confirms and hands the https URL to openInSystemBrowser.
On All, click Show reactions on a note — the note's ₿ amount and the reaction-count text also expand — card expands (aria-expanded), replies list loads via fetchReplies, and the in-card reply composer shows Write a reaction plus an Amount sats field. Gift-only replies render as send ₿… plus the same optional preferred-fiat · as notes (₿-only when conversion is null); a reply with text and a gift shows both. Reply authors show the same Founder / Moderator / Verified pills as notes (basis has none); clicking a pill toggles the same short explanation. Empty reply text and an empty amount invoices 21 sats (pay-sheet default) and opens the pay sheet; a reply with text and an empty amount is unpaid for the parent author or a verified member, otherwise 1 sat; an amount of 0 is billed as 1 sat.
On All, expand Ada's note. The thread shows a gift-only reply (send ₿21) and a text reply with the gift amount under the body. The in-card composer still has Write a reaction and Amount.
On All, expand Ada's note. The thread shows two replies from Robin, who has no 21.gifts account: a gift-only reply (send ₿69) and a text reply containing https://example.com/hello. Each author line shows an External button next to the name (same slot as a role pill); clicking it opens a short hint that the person wrote from another app, not from a 21.gifts account, and is shown because they sent bitcoin to a post. The URL is visible as plain text — not a clickable link, no autolink, no quoted-note embed.
Signed-in founder Cyrill, living-room laws dismissed, Active. Only Riana Rosello's paid 21-sat verified note is in the list; the card is expanded. Cyrill's reply shows just for information: and a nested technical-note post (photo, caption starting A Quick Technical Note, Founder pill, ₿43). The raw https://21.gifts/messages/d8cd22dd-d5c4-46a8-82ed-38b4d2f551ec URL is not visible.
Click Copy link to this note — control sets data-copied after writing origin/messages/<uuid> to the clipboard.
Signed-in /welcome on the default filter; expand Ada's note to show its reply. - Trigger: Click Copy link to this reply on a nested reply. - Result: The control sets data-copied on that reply's own button after writing the reply's own origin/messages/<replyId> permalink to the clipboard — the note's own copy button is unaffected. - Scope: Every reply row has this control regardless of whether Gift or moderator delete are also visible on that row.
Signed-in /welcome with one paid German note. Translate is visible under the body (not in the footer icon row). English notes on other fixtures still hide it.
Same German note after clicking Translate while POST /translate hangs. The control is busy (aria-busy) with a spinner.
Same German note after a successful translation. Translated body plus Show original.
Same German note after POST /translate fails. Alert Could not translate this note. Please try again. and the Translate control remains.
Signed-in /welcome with one paid note whose body is longer than 280 characters. Collapsed preview, ellipsis, and Show more are visible; the distinctive tail is hidden.
Visitor is scrolled down the forum list. A silent refresh found a newer note id. Labeled New posts pill is visible; the new note text is not yet in the list.
Signed-in member with an unread moderator_appointed notification. Labeled You are a moderator pill is visible under the frame header. Idle screenshots omit the pill. When New posts is also shown, this pill stays at top-2 and New posts moves to top-14 (not a separate variant).
Ada's paid note includes #21GiftsShop. The card shows a #Shop pill linking to /shops and the visible body hides the raw hashtag. Other welcome chrome matches default.
Full rules body with rule card Only free donations visible.
Hydrated Ada session: icon-only back + wordmark → /welcome, Menu top-right (Home first). Rule card Only free donations still visible.
Idle composer with lead and rules link.
Click Send with an empty composer → Enter a message.
After a successful send the app navigates to /messages?c= and shows the official 21.gifts thread (the message body, not a dead-end thank-you sentence).
Heading Profile, then inside the single max-w-sm identity card: no chart FiatPicker. When the series is empty, profile.chartEmpty (role="status", No gifts yet.) with no axis/SVG / no ₿|fiat scale; otherwise a compact Given/Received chart (legend left, ₿ | selected fiat SegmentedControl tone="gift" right; no chart title heading); About me with empty prompt Tell others who you are. and Write your About me when aboutMe is null (not a forum post); icon-only Copy link to this profile; name, location (Location / Ort, unset shows Not set), and Wallet of Satoshi address fields with icon actions to the right (pencil / check / X / trash), then FundingStatusCard (verification / 21 gifts grant) under the address form, then a Notifications section with a three-stage All / Active / Mentions SegmentedControl tone="neutral" and, when Push APIs are ready, a second This device On / Off SegmentedControl tone="neutral" (selected fill bg-app-btn; On / Off visible text), then a Language settings row (uppercase kicker and one-row SegmentedControl tone="neutral" same as Theme, English / Deutsch / Español / Filipino), then a Theme settings row (uppercase kicker and SegmentedControl tone="neutral" System / Light / Dark), then a Fiat currency settings row (FiatPreferenceSwitcher, the only FiatPicker on the card, same pill chrome as Theme, not the compact orange gift picker; CHF|EUR|USD|PHP), then a Number format settings row (uppercase kicker and SegmentedControl tone="neutral" samples 10'000.23 / 10,000.23 / 23.000,33); no View key heading and no visible URL/key text. No second panel below the card. Icon-only back and wordmark in the page-frame header (returns to the forum); one Menu in that same header row (Home first; log out, then a quiet Version {version} line (app.version); given/received totals only when that side is non-zero). Chart never swaps to Loading….
Viewport after scrolling the identity-card Fiat currency row into view (CHF | EUR | USD | PHP). Default capture is full-page on the AppShell inner scroller; this variant is viewport-only after scrollIntoViewIfNeeded.
Filtered receive series with three UTC days (including a zero-gap day) and received total ₿1'500. Chart shows day ticks such as 2026-06-01; Given stays flat at zero with a visible legend.
Same receive stub as receive, with the fiat scale selected and USD pressed (Given and received in USD).
One receive day (₿21 on 2026-06-01). Chart draws a horizontal single-point line.
Two-day series with cumulative USD 1425.00, scale switched to USD so the axis shows $1'425 (Swiss default grouping).
Both series non-zero: received ₿1,500 over three UTC days and given ₿2,100 on 2026-06-02. Chart shows Given and Received together. Needle state-profile-given-received.
Owner card with a real bio not equal to the display name. Seed GET /me with name: 'Ada', aboutMe: 'I build on Bitcoin', setup complete. Shows filled About me text plus the icon-only pencil (Edit About me), not the empty CTA (Tell others who you are. / Write your About me).
Owner card with bio and photo. Seed GET /me with aboutMe: 'I build on Bitcoin', aboutMeHasPhoto: true. Stub GET /me/about/photo 200 JPEG. Shows the stored image (About me photo), the bio text, and the icon-only pencil (Edit About me), not the empty CTA.
Owner in the About me textarea editor. From the empty CTA, click Write your About me (empty→Write is enough). Needle: getByRole('textbox', { name: 'About me' }) / Save About me icon button. Save/cancel are icon-only IconButtons (getByRole + catalog text is not visible). textarea uses text-base.
Owner editor with role="alert" save error after stubbing PUT /me/about to 500, opening the editor from empty, and clicking Save. Copy Could not save. Please try again.
Notifications section with role="alert" save error after stubbing POST /me/notification-level to 500 and clicking Active. Copy Could not save notification level.
Notifications section with role="alert" after clicking On on the This device pill when Web Push is present but enable fails. Copy Notifications are not available in this browser.
Basis owner. Grant section after the address form. Copy You are not verified yet. plus how in-person verification works. No apply button.
Verified owner with funding.status none. Copy You are not admitted to daily 21.gifts grant payouts. Grace copy that daily gifts continue until 25 September 2026. Button Apply for the 21 gifts grant. Three conviction titles and an About link.
Verified owner with funding.status none after Apply is in flight. Apply button disabled with a spinner.
Verified owner with funding.status none after Apply fails (POST /funding/apply 500). role="alert" copy Could not submit your application. Please try again. Apply button remains.
Verified owner with funding.status pending. Copy Your application is open. A moderator will review your posts.
Verified owner with funding.status trial. Copy You are on a one-day trial. Review repeats tomorrow.
Verified owner with funding.status admitted. Copy You are admitted to daily 21.gifts grant payouts. plus Reviewed by a moderator on {date}.
Member identity card with About me inside the card when aboutMe is set; read-only location; Message on the card when another member has a profileMessage. Not a forum post.
Identity card with counts; posts button pressed; post card 'Second post from Carol.' in the feed.
Identity card; posts pressed; profile note hidden; the listed post has hasPhoto and shows the inline photo (Photo from Carol) above the text, same ForumBoard paint as /welcome photo.
Identity card; posts pressed; profile note hidden; the listed post has hasPhoto and photoCount: 2 and shows two stills (Photo from Carol) in ForumPhotoGallery (next still peeks, 1/2 chip, dots) above the text, same ForumBoard paint as /welcome photos.
Identity card; replies pressed; no pinned profile-note card; reply card 'A reply from Carol.'
Identity card; posts count pressed; feed shows Loading…; no pinned profile-note card.
Identity card; replies count pressed; feed shows Loading…; no pinned profile-note card.
Identity card; posts count pressed; feed error Could not load messages. Please try again. and Try again; no pinned profile-note card.
Identity card; replies count pressed; feed error and Try again; no pinned profile-note card.
Identity card; posts count 3 pressed; one listed post; muted Showing the latest 1 of 3.; no pinned profile-note card.
Identity card; replies count 3 pressed; one listed reply; muted Showing the latest 1 of 3.; no pinned profile-note card.
Member identity card only (profileMessage: null, aboutMe null); copy-profile-link still on the card; no About me heading; no forum card.
Malformed or unknown id → This profile could not be found.
Failed fetch → error copy and Try again.
Signed-in visitor viewing their own /members/:id card.
Named visitor with living-room rules agreed and no Wallet of Satoshi address. Posts feed open, listed note expanded, reply filled with the Amount field visible, Post clicked. RequirementsOverlay dialog Add your Wallet of Satoshi address with the profile Lightning Address field. No Skip. Close (X) is present.
Named visitor with living-room rules agreed and no username. Posts feed open, listed note expanded, reply filled with the Amount field visible, Post clicked. RequirementsOverlay dialog Add your 21.gifts name with UsernameForm variant=overlay. No Skip. Close (X) is present.
Signed-in /members/:id with a German post in the posts feed. Translate is visible under the body (not in the footer icon row).
Same German post after clicking Translate while POST /translate hangs. The control is busy (aria-busy) with a spinner.
Same German post after a successful translation. Translated body plus Show original.
Same German post after POST /translate fails. Alert Could not translate this note. Please try again. and the Translate control remains.
Signed-in moderator viewing another member who is basis. Staff card with Verify (data-testid="state-members-staff-verify").
Member identity card with a Verified role pill and a second tag Reviewed by a moderator on {date} from fundingReviewedAt.
Same member card after tapping the reviewed tag. role="status" copy Reviewed by a moderator.
Valid known key. Heading Profile, FiatPicker only while unsigned; empty series shows FiatPicker + profile.chartEmpty when unsigned (No gifts yet., no legend/SVG / no ₿|fiat scale; never Loading… on the chart) and profile.chartEmpty alone when signed in, About me inside the card when aboutMe is a string (not a forum post), icon-only Copy link to this profile, name, location, and Wallet of Satoshi address field labels, yellow Action required, the account must be activated / Activate banner under the card when unclaimed (even if signed in), no visible view-key URL/text, no back arrow.
Valid known key with a filled About me (aboutMe is a real bio, not a name-copy). Same read-only card as default plus the About me heading and body text. Copy-profile-link remains. No edit.
Valid known key with About me text and photo (aboutMe: 'I build on Bitcoin', aboutMeHasPhoto: true). Same read-only card as default plus the About me heading, bio, and photo (About me photo). Copy-profile-link remains. No edit.
Unknown or malformed key. Copy This profile could not be found.
Waiting on the profile fetch. Copy Loading…
Profile fetch failed. Copy Could not load this profile. Please try again. and Try again.
Valid known key whose profile already has a passkey (hasPasskey: true). Same read-only card as default, no yellow activation banner, no Activate button.
Telegram or another in-app WebView detected on an unclaimed profile. Escape card under the profile (Open this page in your browser, Open in browser, Copy link); no yellow Activate banner.
Loaded stats with one cumulative over-time chart visible. Scale defaults to ₿.
Inverted ranking fixture (June tall in ₿ / short in USD, July the reverse). Scale switched to USD on Total spend over time, By person, and By month.
Zero gifts. KPI zeros and No gifts recorded yet.
Waiting on GET /gifts/stats. Copy Loading…
Fetch failed. Copy Could not load gift stats. Please try again. and Try again.
Loaded day with at least one gift row (recipient alice).
No gifts that UTC day. Copy No gifts recorded on this day.
Waiting on GET /gifts. Copy Loading…
Fetch failed. Button Try again.
Founder seed Cyrill only. Lead tells the visitor to click a person to load everyone linked to them.
Click Cyrill, then Severin. The chain grows left to right: Cyrill appointed Severin, who verified Ada and Bob.
Zero nodes. Copy No one is on the Trust Chain yet.
Waiting on GET /trust/graph. Copy Loading…
Fetch failed. Copy Could not load the Trust Chain. Please try again. and Try again.
Founder seed is on screen. Clicking that person fails the hop fetch. The diagram stays; the same error copy and Try again sit above it. Retry re-fetches that hop without re-fetching founder seeds; the banner stays gone only if the hop succeeds.
Member list. No chooser. All inbound origins: Bob, official 21.gifts, and npub1abc…xyz.
Member list with an unread Direct row Bob (unread: true, unreadMessageCount: 2; inbox.threadUnread, accessible name Bob, 2 unread): semibold name, visible 2 with tabular-nums lining-nums, and text-app-fg last text. No visible word Unread. Other rows remain read/muted.
Staff (moderator). Contact selected. List shows official 21.gifts. Chooser present. No pinned Staff room / Moderators row.
Staff (moderator). Damus selected. List shows npub1abc…xyz. Chooser present. No pinned Staff room / Moderators row.
Member. No chooser. Loaded list whose last text is the viewer's own send (Bob, Hello team). Preview You: Hello team as a compact filled chip on the right of the muted row, not muted body text.
Member. No threads. Copy No private messages yet. Chooser absent.
Waiting on GET /conversations. Copy Loading… Chooser absent.
List fetch failed. Button Try again. Chooser absent.
Open official thread. Heading 21.gifts (a profile control when the api sent accountId), origin Contact under the heading, inbound Hello team as a full-width muted note card and a sent filled app-btn bubble on the right labelled You, composer visible with the labeled Amount field next to it. Chooser absent.
Member list. One conversation (Bob), gift-only last preview ₿21 (empty lastText, lastFromMe, lastSats 21). Chooser absent.
Open official thread. fromMe gift-only bubble send ₿21. Labeled Amount field still visible next to the composer. Chooser absent.
Open thread. Inbound Hi with amount ₿21 under the body. Composer and labeled Amount field visible.
Open thread, Amount 21 submitted. Pay sheet open with Pay with Wallet of Satoshi. Captured at desktop and mobile (same variant, four combos). Desktop shows the Bitcoin payment QR plus the wallet Pay button; smartphone has the wallet Pay button and no QR. Waiting for payment… is acceptable while the pay poll hangs.
Open Direct thread. Incoming bubble text includes a public forum note URL; the nested quoted-note card shows A Quick Technical Note and hides the raw https://21.gifts/messages/<uuid> URL.
Valid known UUID. Thread may be parent-only when replies are empty. Card with author name, timestamp, text (Hello from Ada), sats via formatBitcoin plus optional preferred-fiat · formatFiatDisplay when the conversion is non-null (otherwise ₿-only, no · —), optional photo or clip-aspect <video>. Auth CTA below the card.
Unsigned permalink. Ada note with photoCount 2 and empty text. ForumPhotoGallery shows earlier stills at 88% so the next photo peeks, the last still full width, a 1/2 chip, and dots.
Hydrated Ada session: icon-only back + wordmark → /welcome, Menu top-right (Home first). Thread card Hello from Ada, React, copy link, and Write a reaction (auto-expanded). Posts do not show Gift or an envelope.
Unknown or malformed id. Copy This profile could not be found.
Waiting on the public message fetch. Copy Loading…
Public message fetch failed. Copy Could not load this profile. Please try again. and Try again.
Public German note. Body is the German fixture; Translate is visible.
After clicking Translate while POST /translate hangs. The control is busy.
After successful translation: Show original.
After POST /translate 502: Could not translate this note. Please try again.
Parent Ada “Hello from Ada” plus gift reply Pater Severin (empty text, sats 3000) showing formatBitcoin (₿3'000). Opened on the parent UUID.
Unsigned permalink card (PublicThreadCard). Parent Ada “Hello from Ada” plus two replies from Robin, who has no 21.gifts account: a gift-only reply (₿69) and a text reply containing https://example.com/hello. Each author line shows a non-interactive External span next to the name (same slot as a role pill; not a button, no hint). The URL is visible as plain text — not a clickable link, no autolink, no quoted-note embed.
Public permalink of Riana Rosello's note. Cyrill's reply shows just for information: plus the same nested technical-note post (photo, caption, Founder, ₿43). Raw URL not visible.
Same thread opened on the reply UUID. Parent + gift; permalink target ring (data-permalink-target="true", ring-1 ring-app-fg) on the gift reply.
Loaded list with at least one unread forum reply (actor Bob, copy Bob replied).
No notifications. Copy No notifications yet.
Waiting on GET /forum/notifications. Copy Loading…
List fetch failed. Button Try again. Copy Could not load notifications. Please try again.
Staff (moderator) hub with heading Moderation, collapsed payout-goal widget, labeled Hidden notes control → /moderate/hidden, labeled Open proposals control → /moderate/proposals, labeled Open applications control → /moderate/applications with grant-review lead, Moderators chat group control → /moderate/group, and Handbook control → /moderate/handbook.
Staff hub with an unread Moderators chat group. Collapsed payout-goal widget unchanged. Moderators chat group control shows 1 and accessible name Moderators chat group, 1 unread (moderate.groupUnread). Hidden notes, Open proposals, Open applications, and Handbook unchanged.
Signed-in basis account. Copy This page is for moderators. No tools list.
Staff (moderator) hub with the payout-goal widget expanded: explanation copy and the 30-UTC-day official-payout count chart (goal line at 100).
Staff (moderator) hub with the payout-goal widget showing Loading… while GET /gifts/stats is in flight. Tools list still visible.
Staff (moderator) hub with the payout-goal widget showing Could not load payouts. Please try again. and labeled Try again. Tools list still visible.
Staff (moderator) loaded queue with at least one open proposal (subject Rose, Proposed by Bob, Confirm as moderator).
Signed-in basis account. Copy This page is for moderators. No list.
Staff (moderator) loaded list with zero open proposals. Copy No open proposals.
Staff (moderator) waiting on GET /trust/proposals. Copy Loading…
Staff (moderator) list fetch failed. Copy Could not load open proposals. Please try again. Button Try again.
Staff (moderator) row they proposed themselves. Copy Waiting for another moderator to confirm. No Confirm button.
Staff (moderator) Confirm as moderator failed. Copy Could not update this member. Please try again.
Staff (moderator) Confirm as moderator POST in flight. Confirm disabled with a spinner; proposal row still visible.
Staff (founder) loaded queue with at least one open application (subject Rose).
Signed-in basis account. Copy This page is for moderators. No list.
Staff (founder) loaded list with zero open applications. Copy No open applications.
Staff (founder) waiting on GET /funding/applications. Copy Loading…
Staff (founder) list fetch failed. Copy Could not load open applications. Please try again. Button Try again.
Staff (founder) loaded application for Rose with a living-room post and Trial / Admit / Reject.
Signed-in basis account. Copy This page is for moderators. No fetch.
Staff (founder) loaded application for Rose with zero living-room posts. Copy No living-room posts. Trial / Admit / Reject still visible.
Staff (founder) waiting on GET /funding/applications/:accountId. Copy Loading…
Staff (founder) detail fetch failed. Copy Could not load this application. Please try again. Button Try again.
Staff (founder) Trial POST failed. Copy Could not update this member. Please try again.
Staff (founder) Trial POST in flight. Trial disabled with a spinner; application still visible.
Staff (founder) loaded application whose grant.status is trial. Admit and Reject visible; Trial absent.
Moderator. Loaded group thread with message Hello mods. Composer visible. No origin filter.
Moderator. Loaded group thread with Rose Otero's message Great work today, moderators! and the paid stipend as a footer line inside that bubble (not a separate bubble): 21.gifts, the ₿ amount plus preferred-fiat suffix, and the payment time. Composer visible.
Signed-in non-staff visitor (verified or basis). Heading Moderators chat group. Copy This room is for moderators. No thread fetch.
Moderator. Group exists, zero messages. Composer Your message visible.
Moderator waiting on GET /conversations/moderator-group. Copy Loading…
Moderator fetch failed. Button Try again.
Moderator. Empty thread. One JPEG selected in the composer; Remove photo visible; textarea empty.
Moderator. Empty thread. Two JPEGs selected in the composer; two Selected photo thumbs.
Moderator. Loaded group thread whose body is a public forum note URL. Nested quoted-note card shows A Quick Technical Note; the raw URL is hidden.
Moderator. Loaded group thread with one attached still and no text. Image alt Photo from Ada.
Moderator. Empty thread. JPEG attach in flight; Send disabled; no Selected photo yet.
Moderator. Empty thread. Attach a GIF → Use a JPEG, PNG, or WebP photo. No Selected photo.
Moderator. Empty thread. Encoded JPEG over 1 MB → Keep photos under 1 MB.
Moderator. Empty thread. Eleven files → You can add up to 10 photos.
Staff (moderator) handbook with heading Handbook, TOC Chapters, and chapters 21.gifts login, Verified, Official funding program, each with a copy-link control.
Signed-in basis account. Copy This page is for moderators. No chapters.
The only state.
Heading Shops, lead, composer, one shop note Cafe Luna with a #Shop pill. Laws hint absent. Raw #21GiftsShop is not visible.
Empty copy No shops yet — add the first one. Composer still present.
Loading…
Could not load messages. Please try again. and Try again.