| name | app-domain-analysis |
| description | Use when the user provides a folder of screenshots from an existing app and wants the backend domain model reverse-engineered, OR when designing a backend for a new app where reference screens exist. Optionally consumes an OpenAPI spec / engineering docs as a secondary signal. Produces a teardown report — domain primitives + temporal invariants + minimal schema + common mistakes — grounded in specific screens and (when available) specific endpoints. |
App Domain Analysis
Overview
Engineers asked to design the backend for an app typically jump straight to a schema, miss the non-obvious primitives, and discover them only after a painful migration. This skill prevents that by looking at the app's screens first to derive the true domain model from observable evidence, then cross-checking against the OpenAPI spec when available.
Two core principles:
- Screen-grounded, not theory-driven. Hypotheses come from what the UI actually shows, not from generic domain templates. Every finding cites the specific screen (and endpoint, when available) it derives from.
- Hunt the patterns engineers reliably miss. The patterns table below lists 12 non-obvious design choices — identity-vs-surface, append-only logs as truth, time-versioned config, idempotency, projections, lifecycle states, etc. — each rendered as observable screen-tells AND API-tells. Walk every row; mark
confirmed / contradicted / no-evidence.
Distilled from two teardowns: Atoms (one-domain habit tracker → references/habit-tracking.md) and Bevel (multi-domain health-tracking app → in the bevel-teardown repo).
When to use
- The user provides a folder path of screenshots and asks for a backend / domain model.
- The user is designing a new app where reference screens exist (e.g., "model my app like Whoop").
- The user provides an OpenAPI spec / engineering docs and asks for a teardown.
- Reviewing such a design — use the patterns table as a checklist.
When NOT to use
- No screens available → use
superpowers:brainstorming instead.
- User wants frontend / visual-design critique → different skill.
- User wants source-code-level review of an existing repo → different skill (codebase analysis).
Procedure
1. Intake
- List image files in the provided folder, sorted (numeric prefix preferred).
- If user names an OpenAPI spec / engineering docs, load them.
- If no screens → refuse: "This skill is screen-grounded; share a folder of screenshots
or use brainstorming for idea-only work."
2. Catalog
a. Screen catalog: for each screen (or near-duplicate cluster), record
{ id, title, primary surface, affordances (buttons / inputs / states),
language used (verbs + nouns) }.
b. API catalog (if spec present): for each operation, record
{ method, path, summary, request shape, response shape,
headers of interest (Idempotency-Key, If-Match, etc.),
status codes (especially 409 / 422 / 412 / 402) }.
3. Cluster
- Screens cluster by domain concept (creation flows / detail / lifecycle / settings / pricing).
- API endpoints cluster by resource and READ-vs-WRITE.
- Note where the two streams map (a "create habit" screen → POST /habits).
4. Hunt patterns (the table below)
- Run each row as a hypothesis against BOTH streams.
- Mark each finding: confirmed-by-screens / confirmed-by-api / corroborated-by-both
/ contradicted / no-evidence.
5. Report (use references/report-template.md)
- Domain primitives (with evidence citations: "screen 04 + POST /habits body")
- Temporal invariants
- Minimal schema (Postgres-flavored)
- Common mistakes table
- Open questions (where evidence is missing or contradictory)
6. Validate
- Surface focused questions citing specific screens or endpoints:
"Screen 11 shows a confidence slider 1–5 but no corresponding endpoint in
the spec — is reflection stored server-side or local-only?"
- Sort question priority:
1. contradicted rows from step 4
2. no-evidence rows where the pattern is category-likely
3. corroborated rows that have ambiguity in detail
The patterns hunt table (the skill's spine)
| # | Pattern | Screen-tell | API-tell | Domain implication |
|---|
| 1 | Identity vs surface noun | Onboarding asks "who do you want to be?" / display sentences combine fragments / typed-memory store | POST body has identity/intent/why separate from action/title | First-class identity fields, not one name |
| 2 | Append-only log = truth, counters = cache | Heatmap, history, "edit a past day" | GET /x/history; backfilled bool; no PUT on a fact row | Source of truth is the log; counters rebuildable |
| 3 | Time-versioned configuration | "Schedule changed" history; per-metric calculation window setting | /x/versions sub-resource; effective_from/effective_to | Close-and-insert a version, not UPDATE |
| 4 | Idempotent write on natural key | "Already logged today" toast on retry | Idempotency-Key header; 409 on dup POST; UNIQUE hint in responses | UNIQUE (entity, day) + ON CONFLICT DO NOTHING |
| 5 | Projection, not stored future | Reminders / upcoming view derived from rules | No POST /reminders; only GET with date range | Worker computes from rules; doesn't persist future rows |
| 6 | Timezone-local truth | "Local time" labels; calendar correct after travel | tz / offset on user; date params (not timestamps) for day-bounded ops | Day = user tz-local date, not server clock |
| 7 | Lifecycle states with slot impact | Pause / snooze / "graduated" / archive / activity-status UIs | PATCH state endpoints; 409 on illegal transition; plan_slots_used derived | State machine; slot accounting on transitions |
| 8 | Server-driven content vs user data | Curated library, "suggestions you might like", feature-flagged greyed items | Read-only catalog endpoints; versioned content blobs | Content versionable + plan-gateable; not user-owned rows |
| 9 | Plan-gating enforced server-side | Upsell modal on cap; "upgrade to unlock"; explicit tier feature list | 402/403 on create past cap; plan on user; cap field exposed | Cap check in service layer, not just UI |
| 10 | Ordinal feedback at cadence | Periodic 1–5 / mood / RPE prompt (not every action) | Reflection endpoint; prompted_after_n_logs or similar config | Cadence-gated ordinal stream, trended for "struggling" detection |
| 11 | Notifications as multi-trigger projection | Multiple times-of-day per item; multiple alarm types | Reminder endpoints accept array of times; offsets enumerated | Time × offset × scheduled-day, not per-row |
| 12 | Soft delete with cascade clarity | "Delete" vs "Archive" distinct in UI; "All recorded data will be saved" warning | DELETE returns 204; archive is a PATCH state | Two operations, two semantics — pick deliberately |
How the table is used at runtime
- After cataloging, walk every row.
- For each pattern, mark
confirmed-by-screens / confirmed-by-api / corroborated-by-both / contradicted / no-evidence.
contradicted rows → top of the validation question list ("Screens imply X, API implies Y — which is true?").
no-evidence rows where the pattern is likely-relevant given the app category → second tier of validation questions.
corroborated-by-both rows → top of the report (highest confidence).
The table is the skill's spine. Future teardowns add rows to references/patterns-catalog.md (e.g., feed app → "feed ranking is a projection over engagement log"); the SKILL.md table stays as the 10–12 most generalizable.
Multi-domain apps
When the app spans more than one domain (Atoms = 1, Bevel = 6), add one more step to the procedure between Catalog and Hunt:
Step 3a — Identify cross-domain dependencies.
Look for formulas where one category's score depends on another's: Bevel's Sleep Needed = Sleep Goal + Recent Strain + Sleep Debt is the clearest example — Sleep depends on Strain depends on workout events depends on the sensor pipeline. The presence of a cross-domain formula is the strongest signal that the domains are projections over shared storage, not independent silos. The minimal schema must reflect that: shared sensor_samples + shared event log + per-domain materialized summaries, not six separate databases.
Engineering-doc "double click" — what to look for in an OpenAPI spec
When an OpenAPI spec is available, drill into these signals beyond the patterns table:
Idempotency-Key header → idempotent writes (#4).
ETag/If-Match → optimistic concurrency.
- Cursor vs offset pagination → storage model hints (cursor → time-ordered log).
- 409 on state-transition endpoints → lifecycle truth (
#7).
- Sub-resource shapes (e.g.
/habits/{id}/schedule-versions) → time-versioned config (#3).
- Nullable/required mix → optionality semantics; required
tz on user means TZ-local truth.
- Batch endpoints → consistency boundaries.
HTTP 402 → plan-gating enforced server-side (#9).
HTTP 412 → preconditional on aggregate state (e.g. "Need 90 days of data to auto-calc Sleep Goal").
oneOf request bodies on a single endpoint → multi-modal ingestion (e.g. sync vs async meal creation paths).
When a hypothesis from screens contradicts the OpenAPI, that's the highest-value validation question to bring to the user.
References
references/habit-tracking.md — the original Atoms teardown. The worked example for single-domain identity-tracker apps. Demonstrates patterns #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12 in one focused domain.
references/bevel-multi-domain.md — a digest of the Bevel teardown's findings. Demonstrates the multi-domain extension (Step 3a above) and patterns #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12 across six interacting domains.
references/report-template.md — the canonical output shape for the deliverable.
references/patterns-catalog.md — growable catalog: each new teardown contributes a row mapping its observation to the patterns table.
references/openapi-tells.md — list of API signals + the domain implications they suggest.
Red flags — stop and reconsider
- The skill produced a domain model without citing any specific screens → the procedure was skipped.
- All findings are "no-evidence" → you haven't actually catalogued; restart at step 2.
- The schema has 1–2 entities and no event logs → you're modeling the UI, not the domain. Patterns #2 and #5 are not being applied.
- No
Open Questions section in the report → either the app is impossibly simple or you're glossing over contradictions.
- Cross-domain dependencies aren't called out in a multi-domain app → step 3a was skipped.