A 16×16 pixel-art editor with an anonymous public gallery and merch checkout. Live at https://pixel.drawbang.com.
The stack is static-first: a browser editor builds the GIF locally, a small ingest Lambda accepts proof-of-work-gated submissions, and a daily batch job regenerates the gallery as static HTML on S3. Merch flows through a separate Lambda that talks to Stripe + Printify.
editor + gallery + GIFs → S3 (us-east-1) behind CloudFront
POST /ingest → Lambda + API Gateway
POST /merch/checkout, /merch/... → Lambda + Stripe + Printify
daily gallery rebuild → GitHub Actions cron
| Tool | Version | Why |
|---|---|---|
| Node.js | 22.x | Vite, the toolchain, and CI all run on 22 |
| npm | bundled with Node | dependency install + scripts |
| Git | any | obvious |
| AWS CLI | v2 (optional) | poke at S3 / Lambda / DynamoDB during ops |
| SAM CLI | latest (only if deploying) | npm run lambda:deploy calls sam deploy |
You only need AWS / SAM if you're deploying. The editor, tests, and local dev servers run with just Node + npm.
# homebrew (simplest)
brew install node@22
# or with nvm if you juggle versions
brew install nvm
nvm install 22
nvm use 22# Debian / Ubuntu — NodeSource is the most reliable source for current Node
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
# Arch
sudo pacman -S nodejs npm
# Fedora
sudo dnf install nodejs:22
# or with nvm anywhere
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
nvm install 22
nvm use 22Confirm with node --version — should print v22.x.
# macOS
brew install awscli aws-sam-cli
# Linux (awscli)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscli.zip
unzip awscli.zip && sudo ./aws/install
# Linux (sam) — homebrew on linux works, or follow:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.htmlConfigure AWS once: aws configure (takes an access key, secret,
region — region must be us-east-1).
git clone https://github.com/potomak/drawbang.git
cd drawbang
npm installThat's it. Everything below runs from the repo root.
npm run devVite serves the editor at http://localhost:5173. Hot-reload works for the editor / merch / share / order pages.
npm run ingest:devSpins up a Node HTTP shim on :8787 that accepts the same POST /ingest
the production Lambda does, but stores under ./dev-bucket/ instead of
S3. Pair with the editor by setting VITE_INGEST_URL=http://localhost:8787
in a .env.local before npm run dev.
# operates on ./dev-bucket/ if DRAWBANG_S3_BUCKET is unset
npm run builderSweeps any drawings in dev-bucket/inbox/<day>/ into the static HTML
under dev-bucket/public/.
npm run typecheck # tsc -b --noEmit, fast
npm test # node:test across test/**/*.test.tsMost tests run in <2 seconds. The proof-of-work tests (test/ingest.*)
do real PoW at 16 bits and take 30–60 seconds each — for tight loops:
node --test --import tsx \
'test/gif.test.ts' 'test/pow.test.ts' 'test/share.test.ts' \
'test/builder.test.ts' 'test/drawing-template.test.ts' \
'test/upscale.test.ts' 'test/dispatch.test.ts' \
'test/printify.test.ts' 'test/brand-logo.test.ts' \
'test/merch-lambda.test.ts' 'test/merch-webhook.test.ts'npm run build # tsc + vite build → dist/
npm run lambda:build # esbuild → dist-lambda/{ingest,merch}.jsBoth should print zero errors and finish in a couple of seconds.
You probably don't need to deploy manually. Pushing to master runs
.github/workflows/deploy.yml which:
- Type-checks + tests
sam deploys the Lambda + DynamoDB + IAM- Builds the editor (
npm run build) andaws s3 syncs it to the assets bucket - Runs the gallery builder against the live S3 bucket
- Invalidates the CloudFront cache for changed paths
The workflow needs these GitHub Actions secrets (Settings → Secrets):
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYPRINTIFY_API_TOKEN,PRINTIFY_SHOP_IDSTRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET
# AWS creds need to be active in your shell for both halves
export AWS_PROFILE=drawbang # or set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY directly
# Lambda + infra
npm run lambda:deploy
# Editor static build → S3
npm run build
aws s3 sync dist/ s3://drawbang-assets/public/ --delete \
--exclude "*.html" --cache-control "public,max-age=31536000,immutable"
# HTML entries get a short cache so we can re-deploy quickly
for html in dist/*.html; do
aws s3 cp "$html" "s3://drawbang-assets/public/$(basename "$html")" \
--cache-control "public,max-age=60,must-revalidate"
done
# Builder run + CloudFront invalidation
DRAWBANG_S3_BUCKET=drawbang-assets npm run builder
aws cloudfront create-invalidation --distribution-id <cf-id> --paths "/*"The first-ever deploy on a fresh AWS account also needs the IAM
permissions listed in CLAUDE.md (search for "AWS deployment").
config/ Shared constants + PoW difficulty + product catalog
constants.ts WIDTH, HEIGHT, MAX_FRAMES, PER_PAGE, …
merch.json Tee / mug / sticker variants, prices, blueprints
mockups.json Static mockup PNG paths + placeholder rects
src/ Vite editor (browser TS)
editor/ bitmap, canvas, tools, history, palette, gif
pow.ts, pow.worker.ts
share.ts, submit.ts, local.ts, main.ts
merch.ts, merch-preview.ts, share-page.ts, order.ts
ingest/ Ingest Lambda + dev shim
builder/ Daily batch job; renders static HTML
templates/ Page render functions (drawing, gallery, owner, products, feed)
merch/ Merch Lambda
printify.ts Printify HTTP client (retries, slow-retry on 8502)
brand-logo.ts Inside-neck DRAW! wordmark generator
upscale.ts Bitmap → SVG (uploaded as the print asset)
dispatch.ts Order placement pipeline
orders.ts DynamoDB orders store
stripe.ts, webhook.ts, lambda.ts
infra/aws/ SAM template + esbuild bundler
.github/workflows/ CI / deploy
test/ node:test suites (no test framework dependency)
scripts/ Operator scripts (smoke tests, mockup fetch, …)
legacy/ Archived Ruby+Sinatra prototype, never imported
CLAUDE.md— design invariants, the GIF format we ship, the PoW contract, deployment shape, conventions. Read this before changing anything iningest/,builder/, orsrc/editor/.HANDOFF.md— short-lived notes for in-flight work between sessions. May or may not exist..github/workflows/deploy.yml— the canonical deploy script; manual deploys mirror its steps.
- TypeScript strict. Don't loosen
tsconfig.jsonwithout a reason. - No comments explaining what the code does — only why, and only when non-obvious.
- Tests use
node:test+tsx, no Jest / Vitest dependency. - Storage operations go through the
Storageinterface soFsStorage(dev/tests) andS3Storage(Lambda/builder) stay interchangeable. - Merge directly to
masterwhen typecheck + tests pass. The deploy workflow runs on every push tomaster. No long-lived feature branches.
tscfails afternpm install: deletenode_modulesandpackage-lock.json, re-runnpm install. Old optional deps (@types/...) sometimes get pinned wrong on minor Node version bumps.npm testhangs on the ingest tests: those are doing real PoW. They're slow on purpose. Use the focused test list above for iteration.sam deployfails with "stack does not exist": first deploy needs--guidedonce. Re-run ascd infra/aws && sam deploy --guided.- Editor loads but
/merchor/shareshow "missing drawing id": expected — those pages need a real?d=<sha256-hex>query. Open the editor, draw something, hit "publish to gallery" first.