comparable-apps
$
npx mdskill add EpicenterHQ/epicenter/comparable-appsLists comparable apps to identify UX patterns and asymmetric wins
- Solves the problem of finding UX patterns in local-first design
- Relies on existing app ecosystems and design documentation
- Compares app behaviors to categorize and justify design choices
- Delivers results through explicit tables in design specs
SKILL.md
.github/skills/comparable-appsView on GitHub ↗
---
name: comparable-apps
description: List 3-5 comparable apps when planning a UX surface to find asymmetric wins. Use for "what do other apps do", identity, sync state, local-first design.
metadata:
author: epicenter
version: '1.0'
---
# Comparable Apps
Related skills: use [one-sentence-test](../one-sentence-test/SKILL.md) to write the cohesion sentence that the comparable-apps check audits against, and [cohesive-clean-breaks](../cohesive-clean-breaks/SKILL.md) when the comparison surfaces an asymmetric win (refuse a feature).
**Core move.** Before designing a user-facing surface, name 3-5 comparable apps and write one line each about what they do for the same problem. Then ask: which category is Epicenter in, and does the proposed design match that category's pattern? When the design diverges from the category, name the reason. When it converges, the comparison is your evidence that the choice is unsurprising.
**Show your work.** Write the table out in the spec or design doc, visibly. The value is in the reader seeing the pattern (or the deviation from the pattern) explicitly, not hidden inside your reasoning.
## Why this skill exists
Epicenter is a local-first workspace platform. Many design questions ("should we cache email", "should we show identity in chrome", "should the sync state be visible", "do we need multi-account") have well-trodden answers in comparable categories. Asking the question explicitly catches two failure modes:
1. **Importing a pattern from the wrong category.** Caching every profile field because Gmail does is wrong; Gmail is communication-first and Epicenter is not.
2. **Inventing a novel pattern when a settled one exists.** Designing a new identity surface when Linear/Notion/Cursor already converged on "avatar in chrome, email in account menu" is gratuitous.
The skill is a coherence gate, not a recipe. Categories are heuristic. When the comparison says "do X" but Epicenter has a load-bearing reason to do Y, the reason goes in the spec; the comparison surfaced the cost of the deviation.
## The taxonomy
```
COMMUNICATION-FIRST identity is the product; you constantly need
to see "who am I sending as"
examples Gmail, Slack, Discord, Twitter, iMessage
chrome email/handle visible, multi-account chip prominent
cache policy aggressive; identity persisted everywhere
multi-account first class
CREDENTIAL VAULT identity disambiguates which secret store
you just unlocked
examples 1Password, Bitwarden
chrome account email on lock screen
cache policy per-vault, persisted
multi-account common (personal + work vaults)
INFRA / IDENTITY TOOL identity disambiguates which network/tenant
you are routing through
examples Tailscale, Cloudflare Warp, AWS CLI profiles
chrome tray menu shows current tenant
cache policy persisted with explicit switch
multi-account common (multi-tailnet, multi-profile)
TOOL WITH IDENTITY identity is a property of the workspace
you are operating inside; recessive in chrome
examples Linear, Notion, Figma, Asana, Height
chrome avatar in sidebar/corner; email behind a click
cache policy session-cached; email shown on demand
multi-account supported, but most users have one
IDE identity is for authoring/syncing artifacts;
you mostly forget it
examples Cursor, VS Code, JetBrains IDEs
chrome account icon; email behind a click
cache policy session-cached
multi-account rare per editor instance
LOCAL-FIRST WORKSPACE identity is a config detail; the workspace
(vault, graph, doc set) is the unit
examples Obsidian, Logseq, Anytype, Tana, Roam
chrome nothing identity-related; vault name at most
cache policy persisted only if needed for offline sync auth
multi-account rare; one workspace = one identity in practice
```
**Epicenter is local-first workspace.** Use this row first; deviations need justification.
## How to apply
Three steps. Don't skip.
1. **State the design question concretely.** Not "what should the auth UI look like" but "where do we display the signed-in user's email, and is it cached on disk." Concrete questions have concrete comparisons.
2. **Pick 3-5 apps across at least two rows of the taxonomy.** Include the row Epicenter belongs to (local-first workspace) plus the row the design is drifting toward. The drift is where the value lives.
3. **Write one line per app, in a table.** Columns are usually: app, category, where the relevant surface appears, what is cached, whether multi-instance is supported. Add columns the specific question demands. Then write the implication: which pattern is Epicenter borrowing, and which is it refusing?
## Worked example: email in chrome
Question: where do we show the signed-in user's email, and should it persist on disk?
| App | Category | Email in chrome? | Where it appears | Persisted? |
| --- | --- | --- | --- | --- |
| Gmail | Communication-first | Yes, prominent | Avatar chip top-right | Yes |
| 1Password | Credential vault | Yes, on unlock | Vault unlock disambiguator | Yes |
| Tailscale | Infra/identity | Yes, in tray | Tray menu | Yes |
| Linear | Tool with identity | Avatar only | Profile menu on click | Yes |
| Notion | Tool with identity | Avatar only | Account settings | Yes |
| Cursor | IDE | Avatar only | Account dropdown | Yes |
| Obsidian | Local-first | No | Sync settings only | Sync only |
| Logseq | Local-first | No | Sync settings only | Sync only |
Implication: Epicenter is local-first; the closest references (Obsidian, Logseq) keep email out of chrome entirely. The tool-with-identity row (Linear/Notion/Cursor) puts an avatar in chrome and reveals email on click. Either is defensible. Gmail's "email everywhere, cached everywhere" pattern is wrong for the category.
Asymmetric win surfaced: refusing the feature of "email in chrome" lets the runtime state stop carrying email at all. Email becomes a fetched query at the one surface that wants to display it. See `specs/20260514T210000-profile-as-application-data.md` for the resulting design.
## Other questions this lens answers well
```
- Should sync status show in chrome, or only on demand?
comm-first: yes; local-first: minimal, often just an icon.
- Should there be a "switch account" affordance?
comm-first / vault / infra: yes; local-first: no, sign out and sign in.
- Should the app open to a workspace picker or directly to the last workspace?
comm-first (Slack): picker (workspaces); local-first (Obsidian): last one.
- Should we persist user preferences/settings on the server, the device, or both?
comm-first: server; local-first: device first, server optionally.
- Should the offline state surface a banner?
comm-first: yes, prominent; local-first: no, it is the default mode.
- Should we show a "verifying account" loading state?
comm-first: yes; local-first: no, local data renders first.
```
In every row, the local-first answer is "less identity surface, more workspace-centric framing." Use this as a sanity check against the temptation to import patterns from communication apps.
## Common reveals
- **The Gmail import.** Designs that cache everything identity-related because that is the dominant mental model. Catch this with the taxonomy.
- **The category drift.** Surfaces that quietly turn Epicenter into a communication app (e.g., a presence indicator everywhere, a global "switch account" button). If the comparison says "this is a comm-first pattern," ask why we are drifting.
- **The novel-surface gap.** Designing a status panel or settings shape that has no analog in any comparable app. Sometimes correct; usually a sign the design is generating its own surface area rather than borrowing.
- **The "everyone caches it" argument collapsing.** When you list comparable apps and only the wrong row caches the thing, the argument for caching dissolves.
## Anti-patterns
- **Listing one app and stopping.** "Linear does X" is not a comparison; it is an appeal to one authority. Always at least three, from at least two rows.
- **Listing only apps from the row Epicenter is in.** The comparison loses its bite if every reference says "do nothing in chrome." Include the drift category to show what we are refusing.
- **Picking apps that are not actually comparable.** Comparing Epicenter's account chrome to Photoshop's is noise; Photoshop has no online identity model. The apps must have the surface you are designing.
- **Using the comparison to justify a decision already made.** The table goes in *before* the design converges. If the design is already written, the comparison is a sanity check, not a vote.
## Success criteria
After the move, the design doc has a short comparison table that names the category Epicenter belongs to, the category the design is drifting toward (if any), and the pattern being borrowed or refused. A reader who has never seen the design can tell from the table alone whether the proposal is consistent with the category Epicenter occupies.
## What this skill is not
- Not market research. The point is design coherence, not competitive analysis.
- Not a vote. Five apps doing X is evidence, not authority. Epicenter can refuse the pattern; the comparison just makes the refusal explicit.
- Not a substitute for one-sentence-test or cohesive-clean-breaks. This skill surfaces the question; those skills resolve it.
More from EpicenterHQ/epicenter
- agent-goalWrite `/goal` prompts for long-running agent work in Codex or Claude Code. Use for slash goal, agent goal, durable objective, autonomous coding run.
- approachability-auditReview code as a new TypeScript developer. Use when code feels indirect, clever, hard to follow, or needs a pass on abstractions, names, first-read clarity.
- arktypeArktype: runtime validation, discriminated unions with .merge()/.or(), spread keys. Use when mentioning arktype, type(), union types, command/event schemas.
- attach-primitiveContract and invariants for `attach*` composition primitives in `packages/workspace` (side-effectful building blocks like attachIndexedDb, attachSqlite, attachBroadcastChannel, attachEncryption, attachTable, openCollaboration), and when to use `create*` (pure construction) instead. Use when writing or reviewing an `attach*` or `create*` function, naming a new workspace primitive, composing inside a workspace builder, or deciding whether a primitive registers listeners at call time.
- authEpicenter auth packages: `@epicenter/auth`, `@epicenter/auth-svelte`, OAuth sessions, identity state, auth-owned fetch/WebSocket, and workspace lifecycle binding. Use when editing Epicenter auth clients, session state, hosted sign-in, or auth/workspace integration.
- autumnAutumn billing in Epicenter: `autumn.config.ts`, `autumn-js` credit checks, `atmn` CLI, plan gates, and metered AI usage. Use when changing billing, pricing, credits, plan access, refunds, or usage events.
- better-auth-best-practicesBetter Auth server/client setup: `auth.ts`, generated schema, DB adapters, sessions, cookies, env vars, and plugins. Use when mentioning Better Auth, betterauth, auth handlers, OAuth, email/password, or session configuration.
- better-auth-security-best-practicesBetter Auth security hardening: rate limits, secrets, CSRF, trusted origins, cookies, sessions, OAuth tokens, and audit logging. Use when reviewing auth security, brute-force protection, token handling, or deployment safety.
- change-proposalPresent proposed code changes visually before implementing. Use when: "show me options", "compare approaches", "what should we do", or when changes need before/after comparison.
- claude-code-consultUse this skill when the user asks to consult Claude, ask Claude Code, get another model's take, run a taste check, find cleaner options, or prepare a Claude prompt. Create a bounded second-opinion prompt or run a read-only Claude Code consult, then verify Claude's claims against local files.