MILWEB®
MILWEB®
MW/023ECOASTUDIO

ECOA

ECOA — home screen
STUDIO
MILWEB
TYPE
DIGITAL PRODUCT
STATUS
LIVE

02 / THE IDEA

Anonymous social network · full product with AI

People want to vent and be heard without showing their face, and traditional networks punish vulnerability.

03 / EXPERIENCE
  1. 01 / 01

    revoke select plus an explicit column-list grant: not even the API can link account to post.

    ECOA — 01
  2. 02 / 02

    Always-on deterministic regex plus optional Claude Haiku. AI downtime never blocks a post from publishing.

    ECOA — 02
  3. 03 / 03

    A 6-word bcrypt-hashed passphrase reassigns the account via RPC, with zero personal data required.

    ECOA — 03
  4. 04 / 04

    No free hashtags, no DMs and 13 curated topics: a structure built against coordinated campaigns.

    ECOA — 04
ECOA onboarding:"Here, only words"

04 / UNDER THE HOOD

  • 01The production stack is Next.js 16 (App Router, Turbopack) on Supabase Postgres in the sa-east-1 region, chosen for in-Brazil data residency…+

    The production stack is Next.js 16 (App Router, Turbopack) on Supabase Postgres in the sa-east-1 region, chosen for in-Brazil data residency with LGPD in mind. It's deliberately simpler than the target architecture documented in the project's blueprint (NestJS, Redis, NATS JetStream, Meilisearch, React Native/Expo): the MVP swapped all of that for a faster validation shortcut. Since the product is text-only with no social graph (no following, no followers), the most expensive problem in a social network, timeline fan-out, simply doesn't exist. Each post weighs a few KB and the feed query is a Postgres view with keyset pagination, with no queue or dedicated cache needed.

  • 02Anonymity here isn't 'no signup', it's a database-level guarantee. Each person enters via Supabase's signInAnonymously() and gets a public_n…+

    Anonymity here isn't 'no signup', it's a database-level guarantee. Each person enters via Supabase's signInAnonymously() and gets a public_number generated by a Postgres function (random draw with a uniqueness check), shown as "User #48291". The internal UUID (profiles.id) never leaves the database: a revoke select strips the default table privilege on posts, comments and profiles from the authenticated role, then a grant select restores access only to an explicit column list, excluding user_id. In practice, even a malicious client-side query can't join account to content, because the privilege to do so doesn't exist at the database-role level. It isn't an application rule that could be worked around. There's no browsable profile page either: tapping a "#48291" opens no history, a deliberate anti-stalking design choice.

  • 03Moderation runs in two stages, both in lib/moderation.ts. Stage 0 is deterministic and instant: Unicode NFKC normalization (against homoglyp…+

    Moderation runs in two stages, both in lib/moderation.ts. Stage 0 is deterministic and instant: Unicode NFKC normalization (against homoglyph bypass tricks), link blocking, a PII regex (email and phone) that only flags, and a crisis-signal regex ("I want to die", "hurt myself") that never blocks publishing, because it only exists to trigger support. Stage 2 is optional: a call to Claude Haiku with structured JSON output, whose system prompt explicitly instructs "when in doubt, don't flag", because a false positive silencing someone in distress is treated as worse than a false negative. The design is fail-open by principle: if the API goes down or hits its 8-second timeout, content passes through. Today, with no API budget provisioned in production, only stage 0 is actually active. Community reports auto-remove content via a Postgres trigger once 3 distinct reporters flag the same item, with no dedicated moderation queue app. The raw Supabase dashboard fills that role in the MVP.

  • 04A few decisions make the kind of product this is clear. There will never be private messaging, treated in the project as "a harassment and g…+

    A few decisions make the kind of product this is clear. There will never be private messaging, treated in the project as "a harassment and grooming vector". There will never be free-form hashtags: just a curated taxonomy of 13 topics, specifically to prevent coordinated campaigns. And the feed is chronological, with no infinite scroll, with a "breathing milestone" every ~50 posts, a structural anti-addiction decision rather than just a UX nicety. Email-free account recovery uses a 6-word passphrase (a 240-word list, ~47 bits of entropy) hashed with bcrypt, reassigned via an RPC with ON UPDATE CASCADE propagating through posts, comments and reactions. The current limits are documented honestly: the recovery-attempt counter gets wiped out by the same transaction rollback that produces the auth error. It's a known, accepted gap, since the passphrase's entropy and bcrypt's cost already cover most of the risk. A 19-scenario e2e suite runs against the real database, including a check that column-level privacy actually prevents user_id from leaking.

BUILT WITHNext.js · TypeScript · Supabase · PostgreSQL · Claude API

06 / RESULT

Anonymity by architecture · AI moderation

MVP LIVE

[ I WANT SOMETHING LIKE THIS ]

07 / SELECTED SCREENS
ECOA onboarding:"Here, only words"
SCREEN 01ECOA onboarding:"Here, only words"
ECOA onboarding:community rules
SCREEN 02ECOA onboarding:community rules
ECOA onboarding:"You are a number"
SCREEN 03ECOA onboarding:"You are a number"
NEXT EXPERIENCE17 / LOJA DE IPHONE

Loja de iPhone

Loja de iPhone

ALL WORK

ALL WORK