Skip to content
MoriaCrewPublic

About

Pinneds: your stuff, pinned together. Friends' photos become cut-out pins on a shared board; anyone remixes everyone's pins into a new scene that credits whose things are in it. Meta Model API: Muse Spark, SAM 3.1, Muse Image, Muse Voice Transcribe. Meta Global AI Developer Hackathon 2026, Indie track, Build for Your World.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pinneds: your stuff, pinned together

Friends drop photos onto one shared board. Meta's models, called through the Meta Model API, cut out the things in them, and anyone can mix everyone's pins into new scenes that credit whose things are in them.

  • Live app: https://pinneds.com (live from Nov 1)
  • Dev, live models: https://dev.pinneds.com
  • Video: 3-minute demo on YouTube
  • Hackathon: Meta Global AI Developer Hackathon, Indie track, theme Build for Your World. Solo developer, Oct 26 – Nov 4, 2026.
  • Built during the event. All application code was written between the Oct 26, 2026 kickoff and the Nov 4 deadline. The five commits before the 12:00 ET kickoff hold only the licence, planning docs and the secret-scanner setup; the app scaffold landed at 15:09 ET on Oct 26. Every merged PR with its merge time and the coding agent that built it: docs/build-log.md.

A ginger cat in a green beanie and red heart sunglasses rides a patterned skateboard down a sunny street

A scene from the landing page, made from three made-up friends' pins: a cat, a beanie with heart sunglasses, a skateboard. Muse Image made their photos, Muse Spark named the things, SAM 3.1 cut them out and Muse Image composed the scene (docs/17).

What it does in 30 seconds

  1. Share. Start a board and send its link to the group chat. No app, no sign-up: the link is the invitation.
  2. Pin. Anyone adds a photo. Muse Spark names the things in it, SAM 3.1 cuts each one out, and they land on the board as stickers with the owner's name. Missed something? Tap it in the photo.
  3. Make a scene. Pick 1–4 pins from anyone, then type an idea, tap a suggested one or say it out loud. Muse Image composes a new picture, "Made from" the people whose things are in it.
  4. Remix. Any scene can be remixed with new pins and a new idea; the credits carry everyone who contributed.

Also: owners can rename or delete their pins (deleting one hides the scenes that used it); people are allowed in photos, and a face gets a one-line consent note, never a block; optional Google sign-in adds My boards and My pins on every device but never gates a board; the UI speaks 7 languages (English, French, Spanish, Portuguese (Brazil), German, Italian, Indonesian), chosen per visitor from one shared link.

How Meta's models power it

Five Meta models. Four run inside the app behind six capability ports in src/gateway/; the fifth ran at build time. The app walks through one real example at pinneds.com/models. Prompts, Policy keys, fallbacks, costs and evidence for each: docs/19-meta-models.md.

Model (ID) What it does in Pinneds Port
Muse Spark (muse-spark-1.3) Names 1–6 things per photo as short noun phrases SAM 3.1 can cut, and flags faces (elements.v1). Names a cut-out you tapped, from its crop (tap-name.v1). Suggests scene ideas for the picked pins in the viewer's UI language, cached 24 h per language (chips.v2). ElementNamer, TapNamer, ChipSuggester
SAM 3.1 (sam-3.1) Cuts out each named thing (one concept per call, four calls at a time) and whatever you tap (point prompt), keeping the most confident usable mask. Segmenter
Muse Image (muse-image-1.0) Composes 1–4 pins into one new scene through /v1/responses (scene.v1, store: false); a remix sends the parent scene first. It also made the source photos of the Meta-made landing sets. SceneComposer
Muse Voice Transcribe (muse-voice-transcribe-1.0) Turns a spoken scene idea (up to 30 s) into text. The audio is never stored. SpeechTranscriber
Muse Spark Open (meta-models/muse-spark-open-1026, on Baseten) Build time only: translated the UI into six languages, with a second reviewer pass (translate-ui.v1, review-ui.v1). Never called by the running app. scripts/i18n-translate.mjs

Every runtime call goes through one decorator chain (src/gateway/decorators.ts): timeout → retry once on 429/5xx → reserve budget, fail closed → call → log the estimated cost → a typed ready / failed / refused outcome. A refusal is a friendly line, never a 500, and a scene that runs out of AI budget becomes a free collage of the cut-outs.

flowchart LR
  photo["Photo"] --> strip["EXIF stripped<br/>working copy"]
  strip --> name["Muse Spark<br/>names the things"]
  name --> cut["SAM 3.1<br/>cuts each one out"]
  cut --> pins["Pins on the board"]
  strip -->|tap| point["SAM 3.1<br/>point cut"]
  point --> tapname["Muse Spark<br/>names the tapped pin"]
  tapname --> pins
  pins --> chips["Muse Spark<br/>idea chips"]
  voice["Spoken idea"] --> asr["Muse Voice Transcribe<br/>transcribes"]
  chips --> idea["Scene idea"]
  asr --> idea
  pins --> compose["Muse Image<br/>composes"]
  idea --> compose
  compose --> scene["Credited scene"]
  scene -->|remix| compose
  entext["English UI text"] -.->|build time| sparkopen["Muse Spark Open<br/>translates + reviews"]
  sparkopen -.-> langs["6 more languages"]
Loading

Architecture

Spec: docs/11-studio.md §5. Decisions: ADR-003.

  • Policy as data. One typed Zod Policy (src/policy/policy.ts) holds every count, size, cap, timeout and TTL: defaults in code, then env POLICY_OVERRIDES_JSON, then per-board overrides. Prompts, validators and the UI read the same object. src/config.ts is the only file that reads process.env.
  • Six ports and decorators. ElementNamer, TapNamer, ChipSuggester, Segmenter, SceneComposer and SpeechTranscriber each have a Meta adapter (src/gateway/meta-ports.ts over src/gateway/meta-gateway.ts, the one file that calls the Meta Model API: plain fetch, no provider SDK), a mock adapter (the default, AI_MOCK=1) and a recorded-fixture adapter. Product code only gets the decorated ports (src/server/ai-ports.ts). Spend is reserved before a call on a per-day counter, with per-person and per-board caps.
  • Jobs, request and poll. A DecomposeJob (photo → pins) and a SceneJob (pins → scene) move pending → running → ready / failed / refused, idempotent by id with claim-before-call, so a repeated request never pays twice. The request does the work; the board page polls (TanStack Query). No queue.
  • Data model (src/domain/board.ts). An Element is an owned cut-out, a Pin puts it on a Board, and a Scene credits its elementIds and records its promptVersion and costUsd. Firestore when hosted, in memory locally. Board links are 128-bit capability slugs, validated before any data path is built.
  • Privacy.
    • Original photos are never stored. An upload is checked, stripped of EXIF and re-encoded into a working copy; only cut-outs and scenes persist, served through routes that check the board link. Client Firestore and Storage rules deny everything.
    • The working copy is deleted when the uploader confirms their pins. If they never do, the next photo added to that board sweeps any copy older than Policy workingCopyTtlHours (24 h), and the bucket lifecycle rule in storage.lifecycle.json is the backstop.
    • Calls to Meta run in Standard mode: not used for training, which is not the same as not stored. The Muse Image compose call sends store: false. We make no "zero data retention" claim.
flowchart LR
  up["Upload<br/>(original never written)"] --> chk["Checked, EXIF stripped,<br/>re-encoded"]
  chk --> wc["Working copy<br/>tmp/"]
  wc --> spark["Muse Spark<br/>names"]
  wc --> sam["SAM 3.1<br/>cuts out"]
  sam --> el["Cut-outs<br/>elements/"]
  el --> sc["Scenes<br/>scenes/"]
  wc -->|"pins confirmed"| del["Working copy deleted"]
  wc -.->|"older than 24 h: swept"| del
  wc -.->|"1-day lifecycle rule"| del
Loading

What persists: cut-outs and scenes. The original is never written; the working copy is temporary.

Run it locally

Offline, with mock models (no key)

Needs Node 20.9+ and npm. The defaults in .env.example (AI_MOCK=1, DATA_BACKEND=memory) run the whole loop with no key and no paid calls. Mock pins are always the same few things, and a mock scene is a collage of the cut-outs.

git clone https://github.com/MoriaCrew/Pinned.git
cd Pinned
npm install
cp .env.example .env.local   # PowerShell: Copy-Item .env.example .env.local
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# paste that value after SESSION_SECRET= in .env.local (joining a board needs it)
npm run dev

Open http://localhost:3000 and start a board. Health check: http://localhost:3000/api/health.

With live models

Set AI_MOCK=0 and your own META_API_KEY in .env.local (never commit it; see SECURITY.md). META_MODEL_SPARK, META_MODEL_SAM, META_MODEL_IMAGE and META_MODEL_VOICE override the model IDs. AI_DAILY_BUDGET_USD (default 5; 0 turns AI off) and AI_DAILY_CALL_LIMIT (default 500) cap the day's spend. Every call logs an estimated cost (docs/16-ai-usage.md). Hosting is Firebase App Hosting: the dev branch builds the dev backend and main builds prod (apphosting*.yaml, docs/DEPLOY.md).

Tests and CI

npm run typecheck
npm test
npm run build

On dev at d958f29 (Nov 4, 2026), npm test reports 2,091 tests passed, 3 skipped, in 181 test files (one of them skipped). CI (.github/workflows/ci.yml) runs all three with AI_MOCK=1, so it never makes a paid model call. Gitleaks scans the full history on every PR (.github/workflows/secret-scan.yml) and runs as a pre-commit hook, and Cursor Bugbot reviews every PR: as of Nov 4, each of the 240 merged PRs passed CI and gitleaks and got a Bugbot review (docs/build-log.md). Domain and Policy logic have unit tests; the gateway has contract tests that replay recorded spike fixtures (eval/fixtures/studio/). Live-model runs happen by hand, with cost logged; results are in eval/studio-findings.md.

Billing: Board Plus

PLUS_MODE is off (the default: nothing is sold), mock (a no-money demo) or stripe. With stripe the app sells one pack, Board Plus (€2.99, Stripe): once per board, it unlocks every board look and foil stickers and adds 20 creations. Any member can buy it for the board; guests never have to pay. Creations are a Policy switch (credits.enabled: off in the code defaults, on in both hosted backends through POLICY_OVERRIDES_JSON). When on, a maker's first 3 boards a week start with 5 free creations (prod gives 20 during judging week; later boards start at 0), each AI scene uses one (photos and cut-outs never do), and a collage is free. A board without Plus cuts up to 15 free photos; Plus removes that limit. Purchases are granted by Stripe webhook only, and keys come from Secret Manager, never git. Design and runbook: docs/15-board-plus.md.

Docs

Known limits

  • Cuts can miss. Sometimes SAM 3.1 finds no usable mask for a named thing, and it does not become a pin; tapping it in the photo is the workaround. Muse Spark names the same photo a little differently from run to run.
  • Costs are estimates from Meta list prices (src/gateway/costs.ts). The provider dashboard is authoritative.
  • Not every picture is from Meta. The classic landing set, the /new look-picker samples (public/start/), the board theme art (public/themes/) and the eval fixture photos are Higgsfield images (SAM 3.1 cut out the stickers). The try-on and cat landing sets are Meta end to end.
  • AI-written text is English in places. Pin names and some scene text stay English whatever the UI language. Idea chips, generated and fixed, follow the UI language.
  • Machine translations. Muse Spark Open wrote and reviewed them; no native speaker has checked them yet. Latin-script languages only.

Built with

Next.js 15 (App Router) and TypeScript, Firebase App Hosting, Firestore and Cloud Storage, the Meta Model API, Stripe Checkout, sharp, Zod, TanStack Query and Vitest; built by one developer with AI coding agents, one issue per PR (docs/03-ai-sdlc.md). Which agent built what, with PR numbers and dates: docs/build-log.md. PIN-n in code and commits is the Linear issue ID; history is in git log.

License

Copyright (C) 2026 Moria Crew. AGPL-3.0, see LICENSE. In short: you may use, study and modify this code, but if you offer a modified version to others over a network you must release your source under the same licence. Hackathon note: the organisers separately receive a broad licence to submitted materials under the event rules; the AGPL governs reuse by everyone else.

About

Pinneds: your stuff, pinned together. Friends' photos become cut-out pins on a shared board; anyone remixes everyone's pins into a new scene that credits whose things are in it. Meta Model API: Muse Spark, SAM 3.1, Muse Image, Muse Voice Transcribe. Meta Global AI Developer Hackathon 2026, Indie track, Build for Your World.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages