anhedral 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -1,18 +1,22 @@
1
1
  ---
2
2
  name: anhedral-init
3
- description: Create, extend, explain, or diagnose complete Anhedral-generated TypeScript stacks. Use for new/init/add/ui/doctor; ordinary Next.js, Expo, Fastify, Drizzle/Neon, Clerk, R2, Electron, or WXT development inside a generated workspace; interpreting anhedral.json ownership; reproducing the scaffold manually; or verifying generated applications.
3
+ description: Guide a user through creating, provisioning, extending, explaining, or diagnosing a complete Anhedral TypeScript stack. Use for skill-led new/init/add/ui/doctor workflows; Computer Use or subagent-assisted cloud setup; domains, DNS, Vercel, Cloudflare, Neon, Clerk, R2, billing, stores, and Electron updates; development inside a generated workspace; ownership interpretation; manual reproduction; or verification.
4
4
  ---
5
5
 
6
6
  # Anhedral Init
7
7
 
8
8
  Anhedral is a stack generator, not an application runtime or programming language. Generated projects use ordinary framework source and expose the complete integration code. Do not invent Anhedral-specific models, routes, queries, components, or client hooks when the selected framework already has a documented convention.
9
9
 
10
+ For every new project or provisioning request, begin by asking for the project
11
+ name and whether the user already owns a custom domain for it. Do not run init
12
+ or create provider resources until those answers establish canonical names.
13
+
10
14
  Use Node.js 20.19+ or 22.12+ and pnpm. `new` creates a destination directory; `init` works in an empty current directory where `.git`, `.gitignore`, and `.DS_Store` are allowed.
11
15
 
12
16
  ## Choose the construction path
13
17
 
14
18
  - Use the manual path whenever the user asks to avoid the Anhedral CLI, the CLI is unavailable, or the task requires explaining/reproducing the scaffold itself.
15
- - Before creating or changing files manually, read [references/manual-scaffolding.md](references/manual-scaffolding.md) completely and follow its ordered procedure. Treat it as the canonical manual checklist, including its source-file matrix and feature gates.
19
+ - Before creating or changing files manually, read [docs/references/manual-scaffolding.md](docs/references/manual-scaffolding.md) completely and follow its ordered procedure. Treat it as the canonical manual checklist, including its source-file matrix and feature gates.
16
20
  - Use the CLI path only when the user permits it. Do not silently substitute `anhedral init`, `anhedral add`, imported generator code, or a generated demo for the manual path.
17
21
  - For an exact-version scaffold inside the Anhedral source repository, render from the current `src/templates/*.ts`, `src/scaffold.ts`, and `src/dependencies.ts`; do not copy stale demo output. Outside that repository, implement the behavior and file contract in the manual reference and pin a mutually compatible toolchain.
18
22
 
@@ -22,7 +26,7 @@ The manual path must leave a complete working workspace, not merely a directory
22
26
 
23
27
  Choose app surfaces from `web`, `mobile`, `api`, `desktop`, and `extension`.
24
28
 
25
- Choose backend features from `db`, `auth`, `billing`, `storage`, and `native-subscriptions`.
29
+ Choose features from `db`, `auth`, `billing`, `storage`, `native-subscriptions`, and `electron-updater`.
26
30
 
27
31
  Apply these dependency rules automatically:
28
32
 
@@ -31,10 +35,58 @@ auth -> api + db
31
35
  billing -> auth
32
36
  storage -> auth
33
37
  native-subscriptions -> mobile + billing
38
+ electron-updater -> desktop
34
39
  ```
35
40
 
36
41
  With no module flags, generate the full stack.
37
42
 
43
+ ## Master stack map
44
+
45
+ Read [docs/master-stack-map.md](docs/master-stack-map.md) completely before
46
+ explaining the whole platform, selecting modules for a complete product, or
47
+ planning cross-provider provisioning. Treat it as the canonical map of the
48
+ stack, command surface, runtime connections, agent workflow, and release path.
49
+
50
+ ```text
51
+ project + domain intake
52
+ |
53
+ v
54
+ 1 select + plan -> 2 generate safely -> 3 one pnpm/Turborepo workspace
55
+ |
56
+ v
57
+ 4 connected product capabilities -> 5 provision cloud + DNS
58
+ |
59
+ v
60
+ 6 generated package.json commands -> 7 developer/agent/Computer Use
61
+ |
62
+ v
63
+ 8 verify, release, and operate
64
+ ```
65
+
66
+ Use the full fixed-width ASCII map from the document when the user asks for the
67
+ master stack map. Do not redraw it from memory or omit unselected modules when
68
+ describing Anhedral's complete capability surface; mark modules as selected or
69
+ unselected separately.
70
+
71
+ ## Run the guided init workflow
72
+
73
+ Read [docs/references/provisioning.md](docs/references/provisioning.md) completely before
74
+ starting a new project or provisioning any provider. Follow its intake,
75
+ capability discovery, Computer Use, subagent, authentication, secret-handling,
76
+ domain, provider-order, and verification gates.
77
+
78
+ The lead agent must:
79
+
80
+ 1. Ask for project name and existing/custom-domain status first.
81
+ 2. Discover whether Computer Use/browser control and subagents are available.
82
+ 3. Resolve modules and show the user canonical project/resource names.
83
+ 4. Run the explicit CLI command, then read the generated `PRODUCTION.md`.
84
+ 5. Provision only selected providers, pausing for sign-in, MFA, secret
85
+ generation, purchases, and final submissions exactly as the reference says.
86
+ 6. Keep secrets out of chat and tool output; have the user paste them directly
87
+ into uncommitted environment files and protected provider fields.
88
+ 7. Verify code, cloud resources, DNS, TLS, and provider health before handoff.
89
+
38
90
  ## Initialize
39
91
 
40
92
  ```sh
@@ -74,23 +126,27 @@ Use the native source conventions:
74
126
 
75
127
  - Next.js code belongs in `apps/web/app`, `apps/web/components`, and `apps/web/lib`.
76
128
  - Expo Router code belongs in `apps/mobile/app` and `apps/mobile/components`.
77
- - Fastify routes and server-only services belong in `apps/api/src/routes` and `apps/api/src/services`.
78
- - Shared Zod network schemas belong in `packages/contracts/src`.
79
- - Client-safe HTTP methods belong in `packages/api-client/src`.
80
- - Drizzle schema and queries belong in `packages/db`; generate and review SQL migrations.
129
+ - Product Fastify route registration belongs in `apps/api/src/routes/app.ts`. Server-only feature modules live alongside the managed integrations in `apps/api/src`.
130
+ - Product Zod network schemas belong in `packages/contracts/src/app.ts`.
131
+ - Product client-safe HTTP methods belong in `packages/api-client/src/app.ts`.
132
+ - Product Drizzle tables belong in `packages/db/src/app-schema.ts`; generate and review SQL migrations.
81
133
 
82
134
  Implement an end-to-end feature as `contracts -> database/service -> route -> API client -> frontend`. Frontends may import contracts and client-safe packages, never API services, database connections, or server environments. This stack uses managed Neon and intentionally has no local Postgres service.
83
135
 
84
136
  ## Add modules safely
85
137
 
86
- Run `add` only from a project containing `anhedral.json` schema v5:
138
+ Run `add` only from a project containing `anhedral.json` schema v5 at the current generator version. If `doctor` identifies a supported older generator, preview and apply its transactional upgrade first:
87
139
 
88
140
  ```sh
141
+ pnpm dlx anhedral@latest upgrade --dry-run
142
+ pnpm dlx anhedral@latest upgrade
89
143
  pnpm dlx anhedral@latest add desktop extension
90
144
  pnpm dlx anhedral@latest add storage --dry-run
91
145
  ```
92
146
 
93
- Use `--dry-run` before a consequential add. Use `--json` when another program needs the plan; JSON failures include a stable `code` and a human-readable `error`. Use `--verbose` for interactive child-command diagnostics. Do not bypass ownership conflicts: Anhedral intentionally refuses modified managed files, user-owned files, symlinks, and unowned collisions.
147
+ Use `--dry-run` before a consequential add. Use `--json` when another program needs the plan; JSON failures include a stable `code` and a human-readable `error`. Use `--verbose` for interactive child-command diagnostics. Anhedral preserves recorded user-owned extension files and intentionally refuses modified managed files, symlinks, and unowned collisions; do not bypass those conflicts.
148
+
149
+ For a 0.3 upgrade blocked by a modified managed file, preserve the change in source control, restore that file to its recorded 0.3 content, apply the upgrade, and port the product behavior into the corresponding 0.4 user-owned extension seam. Never approve the upgrade by rewriting manifest hashes.
94
150
 
95
151
  Require the manifest generator version to match the running CLI. Regenerate projects created by another version; do not add compatibility branches.
96
152
 
@@ -145,10 +201,16 @@ Common selected keys are:
145
201
  - Ably billing synchronization: server-only `ABLY_API_KEY`; clients obtain scoped tokens from the API
146
202
  - R2: `BASE_URL`, `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`, `R2_PREFIX=storage`, `R2_PROXY_READ_URL_TTL_SECONDS=600`; keep `CLOUDFLARE_API_TOKEN` operations/CI-only
147
203
  - Internal billing/storage jobs: high-entropy server-only `CRON_SECRET`
204
+ - Electron updates: `DESKTOP_UPDATE_BASE_URL` in `apps/desktop/electron-builder.env`; the private R2 binding needs no credential in the packaged application
148
205
 
149
206
  Never commit real `.env` files. Keep `ANHEDRAL_DEMO=false` in production.
150
207
 
151
- Run only scripts present for installed modules. Typical commands include `dev:web`, `dev:mobile`, `dev:api`, `dev:desktop`, `dev:extension`, corresponding `verify:*` scripts, and database scripts for `db` projects.
208
+ Run only scripts present for installed modules. `pnpm dev` starts the primary
209
+ product loop—web plus API when web is selected, otherwise the first selected
210
+ surface. A multi-surface project also exposes `pnpm dev:all`; use it only when
211
+ the user wants every selected development server. Typical focused commands
212
+ include `dev:web`, `dev:mobile`, `dev:api`, `dev:desktop`, `dev:extension`,
213
+ corresponding `verify:*` scripts, and database scripts for `db` projects.
152
214
 
153
215
  ## Understand deployment
154
216
 
@@ -158,6 +220,11 @@ For billing, keep Neon authoritative: RevenueCat reconciliation writes the subsc
158
220
 
159
221
  Use `apps/mobile` for EAS, `pnpm desktop:build` for a current-host Electron artifact, matching platform-specific desktop scripts in CI, and `pnpm extension:zip` for the WXT archive.
160
222
 
223
+ For `electron-updater`, provision the generated private R2 bucket and
224
+ `desktop-updater` Worker at `updates.<domain>`. Keep the Worker custom domain and
225
+ `DESKTOP_UPDATE_BASE_URL` identical. Build signed artifacts on each native
226
+ platform and publish artifacts before mutable channel metadata.
227
+
161
228
  For storage, generate `apps/assets-private-proxy` plus `cloudflare/r2-cors.template.json` and keep the R2 bucket private. The Worker must be named `assets-private-proxy`, bind the normalized `<project>-assets` bucket as `ASSETS`, disable `workers.dev`, and own `assets.<domain>` as a Worker Custom Domain. Presigned uploads continue on the R2 S3 API hostname. Root every key below `R2_PREFIX=storage`; expose only `storage/confirmed/` through the Worker and reject staging plus `generation-inputs`. Authenticated private reads must verify upload ownership and return a presigned GET URL bounded by `R2_PROXY_READ_URL_TTL_SECONDS`.
162
229
 
163
230
  When explaining domains, distinguish DNS delegation from registrar transfer. A GoDaddy registration can use Cloudflare authoritative nameservers immediately; transferring registration/billing to Cloudflare Registrar is optional and subject to eligibility locks. Keep Vercel app/API A or CNAME records DNS-only in Cloudflare, while Worker Custom Domains stay Cloudflare-managed and proxied.
@@ -174,4 +241,4 @@ pnpm build
174
241
 
175
242
  Use `pnpm --filter ./apps/api test` when the API exists. If verification fails, report the exact package and command rather than replacing generated ownership records manually.
176
243
 
177
- For exact representative trees, consult `docs/output-tree-contract.md` in the Anhedral repository. During manual construction, also compare the finished workspace with the file matrix in [references/manual-scaffolding.md](references/manual-scaffolding.md); every selected conditional file must exist and every unselected provider integration must be absent.
244
+ For exact representative trees, consult `docs/output-tree-contract.md` in the Anhedral repository. During manual construction, also compare the finished workspace with the file matrix in [docs/references/manual-scaffolding.md](docs/references/manual-scaffolding.md); every selected conditional file must exist and every unselected provider integration must be absent.
@@ -0,0 +1,238 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1180" viewBox="0 0 1920 1180" role="img" aria-labelledby="title description">
2
+ <title id="title">Anhedral Init Stack</title>
3
+ <desc id="description">A simplified architecture diagram showing DX tools, deployment destinations, four generated client surfaces, their shared Fastify API, RevenueCat and Stripe billing, Ably realtime, backend services, and a dedicated private R2 updater Worker.</desc>
4
+ <defs>
5
+ <linearGradient id="card-fill" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#0d1b29"/>
7
+ <stop offset="1" stop-color="#07121d"/>
8
+ </linearGradient>
9
+ <linearGradient id="app-store-fill" x1="0" y1="0" x2="0" y2="1">
10
+ <stop offset="0" stop-color="#24c7fa"/>
11
+ <stop offset="1" stop-color="#0878f9"/>
12
+ </linearGradient>
13
+ <filter id="endpoint-glow" x="-100%" y="-100%" width="300%" height="300%">
14
+ <feGaussianBlur in="SourceGraphic" stdDeviation="1.7"/>
15
+ </filter>
16
+ <marker id="arrow-white" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="#f7f5f0"/></marker>
17
+ <marker id="arrow-yellow" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="#e9f600"/></marker>
18
+ </defs>
19
+ <rect width="1920" height="1180" fill="#0c1117"/>
20
+ <g transform="translate(28 34) scale(0.0263756252842201)">
21
+ <path d="M1604.59 26.7414C1694.33 -39.6752 1821.31 25.3379 1819.89 136.971L1809.28 971.026C1774.28 969.39 1738.87 969.434 1703.15 971.237C1334.16 989.855 1017.8 1191.61 837.641 1484.06L122.496 1416.65C-0.793297 1405.03 -44.6847 1247.41 54.8543 1173.73L1604.59 26.7414Z" fill="#f7f5f0"/>
22
+ <path d="M1795.36 2064.84C1793.79 2188.66 1640.25 2245.21 1558.74 2151.98L1083.84 1608.79C1214.01 1305.09 1479.98 1071.11 1809.07 987.262L1795.36 2064.84Z" fill="#f7f5f0"/>
23
+ </g>
24
+ <path d="M 93.00363801728058 26 L 93.00363801728058 104" fill="none" stroke="#73777b" stroke-width="1.5" stroke-linecap="round"/>
25
+ <text x="115.00363801728058" y="80" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="27" font-weight="500" text-anchor="start" letter-spacing="1.3">ANHEDRAL</text>
26
+ <text x="32" y="176" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="78" font-weight="700" text-anchor="start" letter-spacing="0">Anhedral</text>
27
+ <path d="M 33 195 L 366 195" fill="none" stroke="#e9f600" stroke-width="6" stroke-linecap="round"/>
28
+ <text x="392" y="176" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="78" font-weight="700" text-anchor="start" letter-spacing="0">Init</text>
29
+ <path d="M 393 195 L 506 195" fill="none" stroke="#e9f600" stroke-width="6" stroke-linecap="round"/>
30
+ <text x="532" y="176" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="78" font-weight="700" text-anchor="start" letter-spacing="0">Stack</text>
31
+ <path d="M 533 195 L 747 195" fill="none" stroke="#e9f600" stroke-width="6" stroke-linecap="round"/>
32
+ <text x="34" y="240" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="24" font-weight="400" text-anchor="start" letter-spacing="0">One init flow for web, native apps, extensions, backend,</text>
33
+ <text x="34" y="274" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="24" font-weight="400" text-anchor="start" letter-spacing="0">storage, auth, subscriptions, and seamless desktop updates.</text>
34
+ <text x="1525" y="52" fill="#e9f600" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="600" text-anchor="middle" letter-spacing="3.2">DX TOOLS</text>
35
+ <rect x="1187" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
36
+ <g transform="translate(1214 90) scale(1.4166666666666667)"><path d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z" fill="#06b6d4"/></g>
37
+ <text x="1231" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">Tailwind</text>
38
+ <rect x="1285" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
39
+ <g transform="translate(1312 90) scale(1.4166666666666667)"><path d="M22.219 11.784 11.784 22.219c-.407.407-.407 1.068 0 1.476.407.407 1.068.407 1.476 0L23.695 13.26c.407-.408.407-1.069 0-1.476-.408-.407-1.069-.407-1.476 0ZM20.132.305.305 20.132c-.407.407-.407 1.068 0 1.476.408.407 1.069.407 1.476 0L21.608 1.781c.407-.407.407-1.068 0-1.476-.408-.407-1.069-.407-1.476 0Z" fill="#f7f5f0"/></g>
40
+ <text x="1329" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">shadcn/ui</text>
41
+ <rect x="1383" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
42
+ <g transform="translate(1410 90) scale(1.4166666666666667)"><path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" fill="#61dafb"/></g>
43
+ <text x="1427" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">RN Reusables</text>
44
+ <rect x="1481" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
45
+ <g transform="translate(1508 90) scale(1.4166666666666667)"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z" fill="#3178c6"/></g>
46
+ <text x="1525" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">TypeScript</text>
47
+ <rect x="1579" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
48
+ <g transform="translate(1606 90) scale(1.4166666666666667)"><path d="M2.584 3.582a2.247 2.247 0 0 1 2.112-1.479h14.617c.948 0 1.794.595 2.115 1.487l2.44 6.777a2.248 2.248 0 0 1-.624 2.443l-9.61 8.52a2.247 2.247 0 0 1-2.963.018L.776 12.773a2.248 2.248 0 0 1-.64-2.467Zm12.038 4.887-9.11 5.537 5.74 5.007c.456.399 1.139.396 1.593-.006l5.643-5.001H14.4l6.239-3.957c.488-.328.69-.947.491-1.5l-1.24-3.446a1.535 1.535 0 0 0-1.456-1.015H5.545a1.535 1.535 0 0 0-1.431 1.01l-1.228 3.37z" fill="#3e67b1"/></g>
49
+ <text x="1623" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">Zod</text>
50
+ <rect x="1677" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
51
+ <g transform="translate(1704 90) scale(1.4166666666666667)"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" fill="#f7f5f0"/></g>
52
+ <text x="1721" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">GitHub</text>
53
+ <rect x="1775" y="76" width="88" height="92" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
54
+ <g transform="translate(1804.9299681673488 90) scale(0.015461573442473852)">
55
+ <path d="M1604.59 26.7414C1694.33 -39.6752 1821.31 25.3379 1819.89 136.971L1809.28 971.026C1774.28 969.39 1738.87 969.434 1703.15 971.237C1334.16 989.855 1017.8 1191.61 837.641 1484.06L122.496 1416.65C-0.793297 1405.03 -44.6847 1247.41 54.8543 1173.73L1604.59 26.7414Z" fill="#f7f5f0"/>
56
+ <path d="M1795.36 2064.84C1793.79 2188.66 1640.25 2245.21 1558.74 2151.98L1083.84 1608.79C1214.01 1305.09 1479.98 1071.11 1809.07 987.262L1795.36 2064.84Z" fill="#f7f5f0"/>
57
+ </g>
58
+ <text x="1819" y="148" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="11" font-weight="500" text-anchor="middle" letter-spacing="0">Anhedral Skills</text>
59
+ <text x="252" y="318" fill="#e9f600" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="600" text-anchor="middle" letter-spacing="3.2">DEPLOY &amp; DISTRIBUTE</text>
60
+ <text x="805" y="318" fill="#e9f600" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="600" text-anchor="middle" letter-spacing="3.2">CLIENT SURFACES</text>
61
+ <text x="1497.5" y="318" fill="#e9f600" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="600" text-anchor="middle" letter-spacing="3.2">BACKEND + SERVICES</text>
62
+ <rect x="54" y="342" width="396" height="102" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
63
+ <rect x="70" y="354" width="78" height="78" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
64
+ <g fill="none" stroke="#f7f5f0" stroke-width="3">
65
+ <circle cx="109" cy="393" r="26.520000000000003"/>
66
+ <ellipse cx="109" cy="393" rx="11.934000000000001" ry="26.520000000000003"/>
67
+ <path d="M 82.47999999999999 393 H 135.52 M 86.72319999999999 379.74 H 131.2768 M 86.72319999999999 406.26 H 131.2768"/>
68
+ </g>
69
+ <path d="M 158 356 L 158 430" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
70
+ <text x="182" y="384" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Web</text>
71
+ <text x="182" y="412" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">Vercel preview + production</text>
72
+ <text x="182" y="433" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">hosts web + API</text>
73
+ <rect x="54" y="456" width="396" height="102" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
74
+ <rect x="70" y="468" width="78" height="78" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
75
+ <rect x="78" y="476" width="62" height="62" rx="11" fill="url(#app-store-fill)"/>
76
+ <g transform="translate(87 485) scale(1.8333333333333333)"><path d="M8.8086 14.9194l6.1107-11.0368c.0837-.1513.1682-.302.2437-.4584.0685-.142.1267-.2854.1646-.4403.0803-.3259.0588-.6656-.066-.9767-.1238-.3095-.3417-.5678-.6201-.7355a1.4175 1.4175 0 0 0-.921-.1924c-.3207.043-.6135.1935-.8443.4288-.1094.1118-.1996.2361-.2832.369-.092.1463-.175.2979-.259.4492l-.3864.6979-.3865-.6979c-.0837-.1515-.1667-.303-.2587-.4492-.0837-.1329-.1739-.2572-.2835-.369-.2305-.2353-.5233-.3857-.844-.429a1.4181 1.4181 0 0 0-.921.1926c-.2784.1677-.4964.426-.6203.7355-.1246.311-.1461.6508-.066.9767.038.155.0962.2984.1648.4403.0753.1564.1598.307.2437.4584l1.248 2.2543-4.8625 8.7825H2.0295c-.1676 0-.3351-.0007-.5026.0092-.1522.009-.3004.0284-.448.0714-.3108.0906-.5822.2798-.7783.548-.195.2665-.3006.5929-.3006.9279 0 .3352.1057.6612.3006.9277.196.2683.4675.4575.7782.548.1477.043.296.0623.4481.0715.1675.01.335.009.5026.009h13.0974c.0171-.0357.059-.1294.1-.2697.415-1.4151-.6156-2.843-2.0347-2.843zM3.113 18.5418l-.7922 1.5008c-.0818.1553-.1644.31-.2384.4705-.067.1458-.124.293-.1611.452-.0785.3346-.0576.6834.0645 1.0029.1212.3175.3346.583.607.7549.2727.172.5891.2416.9013.1975.3139-.044.6005-.1986.8263-.4402.1072-.1148.1954-.2424.2772-.3787.0902-.1503.1714-.3059.2535-.4612L6 19.4636c-.0896-.149-.9473-1.4704-2.887-.9218m20.5861-3.0056a1.4707 1.4707 0 0 0-.779-.5407c-.1476-.0425-.2961-.0616-.4483-.0705-.1678-.0099-.3352-.0091-.503-.0091H18.648l-4.3891-7.817c-.6655.7005-.9632 1.485-1.0773 2.1976-.1655 1.0333.0367 2.0934.546 3.0004l5.2741 9.3933c.084.1494.167.299.2591.4435.0837.131.1739.2537.2836.364.231.2323.5238.3809.8449.4232.3192.0424.643-.0244.9217-.1899.2784-.1653.4968-.4204.621-.7257.1246-.3072.146-.6425.0658-.9641-.0381-.1529-.0962-.2945-.165-.4346-.0753-.1543-.1598-.303-.2438-.4524l-1.216-2.1662h1.596c.1677 0 .3351.0009.5029-.009.1522-.009.3007-.028.4483-.0705a1.4707 1.4707 0 0 0 .779-.5407A1.5386 1.5386 0 0 0 24 16.452a1.539 1.539 0 0 0-.3009-.9158Z" fill="#f7f5f0"/></g>
77
+ <path d="M 158 470 L 158 544" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
78
+ <text x="182" y="498" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">App Store</text>
79
+ <text x="182" y="526" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">iOS distribution</text>
80
+ <text x="182" y="547" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">EAS signed release</text>
81
+ <rect x="54" y="570" width="396" height="102" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
82
+ <rect x="70" y="582" width="78" height="78" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
83
+ <g transform="translate(74 586) scale(1.09375)" stroke-linejoin="round">
84
+ <path d="M 9 6 L 39 32 L 9 58 Z" fill="#23d7f2"/>
85
+ <path d="M 9 6 L 44 24.5 L 39 32 Z" fill="#2bd66f"/>
86
+ <path d="M 39 32 L 44 24.5 L 56 30.5 Q 59 32 56 33.5 L 44 39.5 Z" fill="#ffd43b"/>
87
+ <path d="M 9 58 L 44 39.5 L 39 32 Z" fill="#f04b55"/>
88
+ </g>
89
+ <path d="M 158 584 L 158 658" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
90
+ <text x="182" y="612" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Google Play</text>
91
+ <text x="182" y="640" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">Android distribution</text>
92
+ <text x="182" y="661" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">EAS signed release</text>
93
+ <rect x="54" y="684" width="396" height="102" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
94
+ <rect x="70" y="696" width="78" height="78" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
95
+ <rect x="75" y="701" width="68" height="68" rx="11" fill="#f7f5f0"/>
96
+ <path d="M 109 735 L 96.76 713.7996981153569 A 24.48 24.48 0 0 1 133.48 735 Z" fill="#ea4335"/>
97
+ <path d="M 109 735 L 133.48 735 A 24.48 24.48 0 0 1 96.76 756.2003018846431 Z" fill="#fbbc04"/>
98
+ <path d="M 109 735 L 96.76 756.2003018846431 A 24.48 24.48 0 0 1 96.76 713.7996981153569 Z" fill="#34a853"/>
99
+ <circle cx="109" cy="735" r="11.750399999999999" fill="#f7f5f0"/>
100
+ <circle cx="109" cy="735" r="8.8128" fill="#4285f4"/>
101
+ <path d="M 158 698 L 158 772" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
102
+ <text x="182" y="726" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Browser Web Stores</text>
103
+ <text x="182" y="754" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">extension distribution</text>
104
+ <text x="182" y="775" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">reviewed WXT ZIP</text>
105
+ <rect x="54" y="798" width="396" height="102" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
106
+ <rect x="70" y="810" width="78" height="78" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
107
+ <g fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
108
+ <rect x="79" y="819" width="60" height="51" rx="3"/>
109
+ <path d="M 109 870 V 880 M 91.84 880 H 126.16"/>
110
+ </g>
111
+ <path d="M 158 812 L 158 886" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
112
+ <text x="182" y="840" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Desktop Releases</text>
113
+ <text x="182" y="868" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">macOS · Windows · Linux</text>
114
+ <text x="182" y="889" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">signed installers + seamless updates</text>
115
+ <rect x="620" y="342" width="370" height="112" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
116
+ <rect x="636" y="354" width="88" height="88" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
117
+ <g transform="translate(651 369) scale(2.4166666666666665)"><path d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z" fill="#f7f5f0"/></g>
118
+ <path d="M 734 356 L 734 440" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
119
+ <text x="758" y="384" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Next.js</text>
120
+ <text x="758" y="412" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">shadcn/ui</text>
121
+ <text x="758" y="433" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">web product + server routes</text>
122
+ <g transform="translate(958 357) scale(0.7083333333333334)"><path d="m12 1.608 12 20.784H0Z" fill="#f7f5f0"/></g>
123
+ <rect x="620" y="494" width="370" height="112" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
124
+ <rect x="636" y="506" width="88" height="88" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
125
+ <g transform="translate(651 521) scale(2.4166666666666665)"><path d="M0 20.084c.043.53.23 1.063.718 1.778.58.849 1.576 1.315 2.303.567.49-.505 5.794-9.776 8.35-13.29a.761.761 0 011.248 0c2.556 3.514 7.86 12.785 8.35 13.29.727.748 1.723.282 2.303-.567.57-.835.728-1.42.728-2.046 0-.426-8.26-15.798-9.092-17.078-.8-1.23-1.044-1.498-2.397-1.542h-1.032c-1.353.044-1.597.311-2.398 1.542C8.267 3.991.33 18.758 0 19.77Z" fill="#f7f5f0"/></g>
126
+ <path d="M 734 508 L 734 592" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
127
+ <text x="758" y="536" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Expo Native</text>
128
+ <text x="758" y="564" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">React Native Reusables</text>
129
+ <text x="758" y="585" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">iOS + Android</text>
130
+ <rect x="620" y="646" width="370" height="112" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
131
+ <rect x="636" y="658" width="88" height="88" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
132
+ <g transform="translate(651 673) scale(2.4166666666666665)"><path d="M10.18 0c-2.081 0-3.807 1.608-4 3.64H4.019A4.033 4.033 0 0 0 0 7.66v4.017h1.498a2.13 2.13 0 0 1 2.143 2.144 2.13 2.13 0 0 1-2.143 2.143H0V24h8.036v-1.498a2.13 2.13 0 0 1 2.144-2.143 2.13 2.13 0 0 1 2.143 2.143V24h4.018a4.03 4.03 0 0 0 4.018-4.018v-2.163C22.392 17.627 24 15.901 24 13.821s-1.608-3.807-3.64-4V7.66a4.03 4.03 0 0 0-4.019-4.018h-2.162C13.986 1.608 12.26 0 10.179 0m0 1.875a2.13 2.13 0 0 1 2.143 2.143v1.498h4.018a2.13 2.13 0 0 1 2.143 2.143v4.018h1.498a2.13 2.13 0 0 1 2.143 2.144 2.13 2.13 0 0 1-2.143 2.143h-1.498v4.018a2.13 2.13 0 0 1-2.143 2.143h-2.162c-.193-2.033-1.919-3.64-4-3.64s-3.806 1.607-3.998 3.64H1.875V17.82c2.033-.192 3.64-1.918 3.64-3.998s-1.607-3.807-3.64-4V7.66a2.13 2.13 0 0 1 2.143-2.143h4.018V4.018a2.13 2.13 0 0 1 2.144-2.143" fill="#b9f500"/></g>
133
+ <path d="M 734 660 L 734 744" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
134
+ <text x="758" y="688" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">WXT Extension</text>
135
+ <text x="758" y="716" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">Chrome · Firefox · Edge</text>
136
+ <text x="758" y="737" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">MV3 background + side panel</text>
137
+ <rect x="620" y="798" width="370" height="112" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
138
+ <rect x="636" y="810" width="88" height="88" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
139
+ <g transform="translate(651 825) scale(2.4166666666666665)"><path d="M12.0111 0c-.85 0-1.5392.6891-1.5392 1.5392 0 .8501.6891 1.5393 1.5392 1.5393.595 0 1.11-.338 1.3662-.832 2.2208 1.2675 3.847 5.4728 3.847 10.3623 0 2.0715-.2891 4.056-.825 5.7685a.3215.3215 0 0 0 .2107.403.322.322 0 0 0 .4033-.2111c.5558-1.7763.8542-3.8251.8542-5.9604 0-5.1927-1.7717-9.686-4.3206-11.0027.001-.0223.0035-.0443.0035-.0669 0-.85-.6891-1.5392-1.5393-1.5392zm0 .6432a.896.896 0 1 1 0 1.792.896.896 0 1 1 0-1.792zm-5.486 4.3052c-2.067.0074-3.6473.6646-4.3885 1.9485-.7375 1.2774-.5267 2.971.5113 4.7813a.3217.3217 0 0 0 .558-.32C2.271 9.7274 2.089 8.266 2.6938 7.2185c.821-1.422 3.033-1.9552 5.9321-1.4271a.3216.3216 0 0 0 .1153-.6329c-.784-.1428-1.5271-.2125-2.216-.21zm11.0522.0176a.3216.3216 0 0 0-.0084.6432c1.8337.0239 3.1556.5956 3.7502 1.6256.8192 1.419.1798 3.5947-1.7182 5.837a.322.322 0 0 0 .0377.4535.3215.3215 0 0 0 .4532-.0377c2.0535-2.426 2.7708-4.8661 1.7845-6.5744-.7257-1.257-2.26-1.9207-4.299-1.9472zm-2.6984.2924a.3225.3225 0 0 0-.0647.0072c-1.8568.3979-3.8333 1.1755-5.7314 2.2714-4.5699 2.6384-7.5924 6.4948-7.3601 9.3717-.4726.2628-.7928.7664-.7928 1.3455 0 .85.6892 1.5392 1.5393 1.5392.85 0 1.5392-.6891 1.5392-1.5392 0-.8501-.6891-1.5393-1.5392-1.5393-.038 0-.0754.003-.1128.0057-.1002-2.5597 2.7434-6.1412 7.048-8.6265 1.8413-1.063 3.7551-1.8163 5.5445-2.1997a.3217.3217 0 0 0-.07-.636zm-2.8787 6.2364a1.1192 1.1192 0 0 0-.2243.0255c-.6012.1301-.983.7225-.8533 1.3238.1302.6012.7226.9832 1.3238.8533.6012-.1302.9832-.7226.8533-1.3238-.1139-.526-.5816-.8844-1.0995-.8788zM4.532 13.341a.321.321 0 0 0-.2318.0835.3214.3214 0 0 0-.0214.4542c1.2682 1.3936 2.9157 2.701 4.7946 3.7857 4.4146 2.5489 9.1056 3.2849 11.5608 1.8392a1.53 1.53 0 0 0 .8966.2899c.8501 0 1.5392-.6891 1.5392-1.5392 0-.8501-.689-1.5393-1.5392-1.5393-.85 0-1.5392.6892-1.5392 1.5393 0 .276.0737.5344.201.7584-2.2448 1.214-6.631.5002-10.7976-1.9054-1.8228-1.0524-3.418-2.3181-4.6404-3.6614a.3206.3206 0 0 0-.2226-.1049zm-2.0628 4.0172a.896.896 0 1 1 0 1.792.896.896 0 1 1 0-1.792zm19.0616 0a.896.896 0 1 1 0 1.792.891.891 0 0 1-.5864-.2194c-.0025-.004-.0039-.0083-.0066-.0123a.3195.3195 0 0 0-.0957-.0914.896.896 0 0 1 .6887-1.4689zm-14.0045 1.368a.3215.3215 0 0 0-.3207.4296C8.2793 22.154 10.036 24 12.0111 24c1.4406 0 2.7735-.9822 3.8128-2.711a.3215.3215 0 0 0-.11-.4413.3219.3219 0 0 0-.4415.11c-.934 1.5537-2.0812 2.399-3.2613 2.399-1.6407 0-3.2075-1.6465-4.2-4.4179a.3216.3216 0 0 0-.2848-.2126z" fill="#51dce9"/></g>
140
+ <path d="M 734 812 L 734 896" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
141
+ <text x="758" y="840" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" text-anchor="start" letter-spacing="0">Electron Desktop</text>
142
+ <text x="758" y="868" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="16" font-weight="400" text-anchor="start" letter-spacing="0">macOS · Windows · Linux</text>
143
+ <text x="758" y="889" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="13" font-weight="400" text-anchor="start" letter-spacing="0">electron-updater enabled</text>
144
+ <rect x="1130" y="474" width="208" height="296" rx="14" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
145
+ <rect x="1190" y="490" width="88" height="88" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
146
+ <g transform="translate(1200 500) scale(2.8333333333333335)"><path d="M23.245 6.49L24 4.533l-.031-.121-7.473 1.967c.797-1.153.523-2.078.523-2.078s-2.387 1.524-4.193 1.485c-1.804-.04-2.387-.52-5.155.362-2.768.882-3.551 3.59-4.351 4.173-.804.583-3.32 2.477-3.32 2.477l.006.034 2.27-.724s-.622.585-1.945 2.37l-.062-.057.002.011s1.064 1.626 2.107 1.324a2.14 2.14 0 0 0 .353-.147c.419.234.967.463 1.572.525 0 0-.41-.475-.752-1.017l.238-.154.865.318-.096-.812c.003-.003.006-.003.008-.006l.849.311-.105-.738a5.65 5.65 0 0 1 .322-.158l.885-3.345 3.662-2.497-.291.733c-.741 1.826-2.135 2.256-2.135 2.256l-.582.22c-.433.512-.614.637-.764 2.353.348-.088.682-.107.984-.028 1.564.421 2.107 2.307 1.685 2.827-.104.13-.356.354-.673.617H7.77l-.008.514-.065.051h-.645l-.009.504-.17.127c-.607.011-1.373-.518-1.373-.518 0 .481.401 1.225.401 1.225l.07-.034-.061.045s1.625 1.083 2.646.681c.91-.356 3.263-2.213 5.296-3.093l6.15-1.62.811-2.1-4.688 1.235v-1.889l5.5-1.448.811-2.1-6.31 1.662V8.367zm-11.163 4l1.459-.384.02.074-.455 1.179-1.513.398zm.503 2.526l-1.512.398.489-1.266 1.459-.385.02.074zm1.971-.424l-1.513.398.49-1.266 1.459-.385.02.073Z" fill="#f7f5f0"/></g>
147
+ <g transform="translate(1306 490) scale(0.7083333333333334)"><path d="m12 1.608 12 20.784H0Z" fill="#f7f5f0"/></g>
148
+ <path d="M 1148 602 L 1320 602" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
149
+ <text x="1234" y="643" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="25" font-weight="500" text-anchor="middle" letter-spacing="0">Fastify Backend</text>
150
+ <text x="1234" y="675" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="400" text-anchor="middle" letter-spacing="0">API + business logic</text>
151
+ <rect x="1435" y="342" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
152
+ <rect x="1451" y="354" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
153
+ <g transform="translate(1456 373) scale(1.0833333333333333)"><path d="M24 0V24l-9.365-8.045V24H0V0ZM2.942 21.087h8.751V9.563l9.365 8.204V2.919L2.942 2.914Z" fill="#35d6a8"/></g>
154
+ <g transform="translate(1484 373) scale(1.0833333333333333)"><path d="M5.353 11.823a1.036 1.036 0 0 0-.395-1.422 1.063 1.063 0 0 0-1.437.399L.138 16.702a1.035 1.035 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.398l3.383-5.903Zm11.216 0a1.036 1.036 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.399l-3.382 5.902a1.036 1.036 0 0 0 .394 1.422c.506.283 1.15.104 1.438-.398l3.382-5.903Zm7.293-4.525a1.036 1.036 0 0 0-.395-1.422 1.062 1.062 0 0 0-1.437.399l-3.383 5.902a1.036 1.036 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.399l3.383-5.902Zm-11.219 0a1.035 1.035 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.398l-3.382 5.903a1.036 1.036 0 0 0 .394 1.422c.506.282 1.15.104 1.438-.399l3.382-5.902Z" fill="#e9f600"/></g>
155
+ <path d="M 1527 356 L 1527 416" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
156
+ <text x="1547" y="373" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Neon + Drizzle</text>
157
+ <text x="1547" y="397" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">Postgres + ORM</text>
158
+ <text x="1547" y="417" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">reviewed migrations</text>
159
+ <rect x="1435" y="438" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
160
+ <rect x="1451" y="450" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
161
+ <g transform="translate(1455 454) scale(2.3333333333333335)"><path d="m21.47 20.829-2.881-2.881a.572.572 0 0 0-.7-.084 6.854 6.854 0 0 1-7.081 0 .576.576 0 0 0-.7.084l-2.881 2.881a.576.576 0 0 0-.103.69.57.57 0 0 0 .166.186 12 12 0 0 0 14.113 0 .58.58 0 0 0 .239-.423.576.576 0 0 0-.172-.453Zm.002-17.668-2.88 2.88a.569.569 0 0 1-.701.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0-1.222 3.692 6.86 6.86 0 0 0 .978 3.764.573.573 0 0 1-.083.699l-2.881 2.88a.567.567 0 0 1-.864-.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637-.405.566.566 0 0 1 .232.418.57.57 0 0 1-.168.448Zm-7.118 12.261a3.427 3.427 0 1 0 0-6.854 3.427 3.427 0 0 0 0 6.854Z" fill="#f7f5f0"/></g>
162
+ <path d="M 1527 452 L 1527 512" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
163
+ <text x="1547" y="469" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Clerk Auth</text>
164
+ <text x="1547" y="493" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">identity + sessions</text>
165
+ <text x="1547" y="513" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">verified server-side</text>
166
+ <rect x="1435" y="534" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
167
+ <rect x="1451" y="546" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
168
+ <g transform="translate(1456 565) scale(1.0833333333333333)"><path d="M4.3036.3999c-1.5246 0-3.2129.1508-4.303.4136v14.9997c.3083.1722.8432.28 1.5632.28.7404 0 1.2553-.1072 1.5433-.28v-5.2323a14.8588 14.8588 0 0 0 2.121.1512h.3294l2.8604 5.0588c.432.195 1.0288.3024 1.9348.3024.8033 0 1.38-.1104 1.6476-.3024l-3.437-5.8358c1.4195-.8004 2.326-2.2698 2.326-4.4964C10.8894 1.827 8.4232.4 4.3037.4zm15.4543 0c-1.3788 0-2.624.2707-3.6901.7945-2.4552 1.203-3.9609 3.7376-3.9609 7.3627 0 4.8245 2.6552 7.7155 7.1659 7.7155.9 0 1.5868-.3014 2.005.2554.4194.5568-.3582 1.2165-.7746 1.5105-1.6338 1.1544-5.7217-.1024-9.4908-.4804C5.5994 17.015.9264 16.3009.146 19.1928c-.4104 1.5264.1225 2.5013.6421 3.0503 1.044 1.1046 2.882 1.357 4.344 1.357a13.959 13.959 0 0 0 2.0508-.1558 1.311 1.311 0 0 0 1.023-.8063c.1674-.4254.0861-.904-.212-1.2562a1.3464 1.3464 0 0 0-1.2352-.4523c-1.5012.2706-3.6213.8685-4.4343.0105-.2748-.291-.2268-1.0037 0-1.2257.6048-.8748 4.493-.5393 8.4127-.0293 4.329.4344 8.4023 1.8609 10.945.6351.9955-.48 2.318-1.1941 2.318-3.792h-.0012c0-1.1473-.1489-2.274-.4476-3.3797-1.3818.1872-2.4783.2857-3.2883.2941-2.845 0-4.869-1.4484-4.869-5.0963 0-3.648 2.0461-5.1573 5.0179-5.1573 1.2011 0 2.129.2512 3.1405.7336.1062-.9234-.1058-2.1605-.5906-2.8523-.78-.4608-2.0014-.6703-3.2038-.6703zM4.51 3.1889c2.0579 0 3.2108.7111 3.2108 2.4421 0 1.6824-1.0912 2.3554-2.8816 2.3554a10.2838 10.2838 0 0 1-1.7706-.1511V3.3166a7.7782 7.7782 0 0 1 1.4413-.1277z" fill="#ff3b3f"/></g>
169
+ <g transform="translate(1484 565) scale(1.0833333333333333)"><path d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z" fill="#635bff"/></g>
170
+ <path d="M 1527 548 L 1527 608" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
171
+ <text x="1547" y="565" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">RevenueCat + Stripe</text>
172
+ <text x="1547" y="589" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">subscriptions + payments</text>
173
+ <text x="1547" y="609" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">billing integration + shared entitlements</text>
174
+ <rect x="1435" y="630" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
175
+ <rect x="1451" y="642" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
176
+ <g transform="translate(1457 652.6666666666666) scale(0.6666666666666666)">
177
+ <path d="M38.5717 0L6.29575 59.0738L0 54.6586L29.8639 0H38.5717ZM39.0214 0L71.2973 59.0738L77.5931 54.6586L47.7292 0H39.0214Z" fill="url(#ably-symbol-a)"/>
178
+ <path d="M70.8477 59.4213L38.7966 34.3201L6.74548 59.4213L13.2865 64L38.7966 44.0294L64.3066 64L70.8477 59.4213Z" fill="url(#ably-symbol-b)"/>
179
+ <defs>
180
+ <linearGradient id="ably-symbol-a" x1="10.9472" y1="74.8439" x2="64.9206" y2="14.9005" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
181
+ <linearGradient id="ably-symbol-b" x1="21.4169" y1="78.7187" x2="53.3166" y2="43.2904" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
182
+ </defs>
183
+ </g>
184
+ <path d="M 1527 644 L 1527 704" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
185
+ <text x="1547" y="661" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Ably Realtime</text>
186
+ <text x="1547" y="685" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">scoped invalidations</text>
187
+ <text x="1547" y="705" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">outbox → event → client refetch</text>
188
+ <rect x="1435" y="726" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
189
+ <rect x="1451" y="738" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
190
+ <g transform="translate(1457 744) scale(2.1666666666666665)"><path d="m8.213.063 8.879 12.136-8.67 11.739h2.476l8.665-11.735-8.89-12.14Zm4.728 0 9.02 11.992-9.018 11.883h2.496L24 12.656v-1.199L15.434.063ZM7.178 2.02.01 11.398l-.01 1.2 7.203 9.644 1.238-1.676-6.396-8.556 6.361-8.313Z" fill="#f48120"/></g>
191
+ <path d="M 1527 740 L 1527 800" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
192
+ <text x="1547" y="757" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Assets Worker</text>
193
+ <text x="1547" y="781" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">PRIVATE R2 assets binding</text>
194
+ <text x="1547" y="801" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">assets.example.com · authorized reads</text>
195
+ <rect x="1435" y="822" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
196
+ <rect x="1451" y="834" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
197
+ <g transform="translate(1456 853) scale(1.0833333333333333)"><path d="m8.213.063 8.879 12.136-8.67 11.739h2.476l8.665-11.735-8.89-12.14Zm4.728 0 9.02 11.992-9.018 11.883h2.496L24 12.656v-1.199L15.434.063ZM7.178 2.02.01 11.398l-.01 1.2 7.203 9.644 1.238-1.676-6.396-8.556 6.361-8.313Z" fill="#f48120"/></g>
198
+ <g transform="translate(1484 853) scale(1.0833333333333333)"><path d="M12.0111 0c-.85 0-1.5392.6891-1.5392 1.5392 0 .8501.6891 1.5393 1.5392 1.5393.595 0 1.11-.338 1.3662-.832 2.2208 1.2675 3.847 5.4728 3.847 10.3623 0 2.0715-.2891 4.056-.825 5.7685a.3215.3215 0 0 0 .2107.403.322.322 0 0 0 .4033-.2111c.5558-1.7763.8542-3.8251.8542-5.9604 0-5.1927-1.7717-9.686-4.3206-11.0027.001-.0223.0035-.0443.0035-.0669 0-.85-.6891-1.5392-1.5393-1.5392zm0 .6432a.896.896 0 1 1 0 1.792.896.896 0 1 1 0-1.792zm-5.486 4.3052c-2.067.0074-3.6473.6646-4.3885 1.9485-.7375 1.2774-.5267 2.971.5113 4.7813a.3217.3217 0 0 0 .558-.32C2.271 9.7274 2.089 8.266 2.6938 7.2185c.821-1.422 3.033-1.9552 5.9321-1.4271a.3216.3216 0 0 0 .1153-.6329c-.784-.1428-1.5271-.2125-2.216-.21zm11.0522.0176a.3216.3216 0 0 0-.0084.6432c1.8337.0239 3.1556.5956 3.7502 1.6256.8192 1.419.1798 3.5947-1.7182 5.837a.322.322 0 0 0 .0377.4535.3215.3215 0 0 0 .4532-.0377c2.0535-2.426 2.7708-4.8661 1.7845-6.5744-.7257-1.257-2.26-1.9207-4.299-1.9472zm-2.6984.2924a.3225.3225 0 0 0-.0647.0072c-1.8568.3979-3.8333 1.1755-5.7314 2.2714-4.5699 2.6384-7.5924 6.4948-7.3601 9.3717-.4726.2628-.7928.7664-.7928 1.3455 0 .85.6892 1.5392 1.5393 1.5392.85 0 1.5392-.6891 1.5392-1.5392 0-.8501-.6891-1.5393-1.5392-1.5393-.038 0-.0754.003-.1128.0057-.1002-2.5597 2.7434-6.1412 7.048-8.6265 1.8413-1.063 3.7551-1.8163 5.5445-2.1997a.3217.3217 0 0 0-.07-.636zm-2.8787 6.2364a1.1192 1.1192 0 0 0-.2243.0255c-.6012.1301-.983.7225-.8533 1.3238.1302.6012.7226.9832 1.3238.8533.6012-.1302.9832-.7226.8533-1.3238-.1139-.526-.5816-.8844-1.0995-.8788zM4.532 13.341a.321.321 0 0 0-.2318.0835.3214.3214 0 0 0-.0214.4542c1.2682 1.3936 2.9157 2.701 4.7946 3.7857 4.4146 2.5489 9.1056 3.2849 11.5608 1.8392a1.53 1.53 0 0 0 .8966.2899c.8501 0 1.5392-.6891 1.5392-1.5392 0-.8501-.689-1.5393-1.5392-1.5393-.85 0-1.5392.6892-1.5392 1.5393 0 .276.0737.5344.201.7584-2.2448 1.214-6.631.5002-10.7976-1.9054-1.8228-1.0524-3.418-2.3181-4.6404-3.6614a.3206.3206 0 0 0-.2226-.1049zm-2.0628 4.0172a.896.896 0 1 1 0 1.792.896.896 0 1 1 0-1.792zm19.0616 0a.896.896 0 1 1 0 1.792.891.891 0 0 1-.5864-.2194c-.0025-.004-.0039-.0083-.0066-.0123a.3195.3195 0 0 0-.0957-.0914.896.896 0 0 1 .6887-1.4689zm-14.0045 1.368a.3215.3215 0 0 0-.3207.4296C8.2793 22.154 10.036 24 12.0111 24c1.4406 0 2.7735-.9822 3.8128-2.711a.3215.3215 0 0 0-.11-.4413.3219.3219 0 0 0-.4415.11c-.934 1.5537-2.0812 2.399-3.2613 2.399-1.6407 0-3.2075-1.6465-4.2-4.4179a.3216.3216 0 0 0-.2848-.2126z" fill="#51dce9"/></g>
199
+ <path d="M 1527 836 L 1527 896" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
200
+ <text x="1547" y="853" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Updater Worker</text>
201
+ <text x="1547" y="877" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">PRIVATE R2 updates binding</text>
202
+ <text x="1547" y="897" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">updates.example.com → electron-updater</text>
203
+ <rect x="1435" y="918" width="430" height="88" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
204
+ <rect x="1451" y="930" width="64" height="64" rx="11" fill="#07121d" stroke="#34424e" stroke-width="1.15"/>
205
+ <g transform="translate(1456 949) scale(1.0833333333333333)"><path d="M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727" fill="#f48120"/></g>
206
+ <g transform="translate(1484 949) scale(1.0833333333333333)"><path d="m12 1.608 12 20.784H0Z" fill="#f7f5f0"/></g>
207
+ <path d="M 1527 932 L 1527 992" fill="none" stroke="#34424e" stroke-width="1" stroke-linecap="round"/>
208
+ <text x="1547" y="949" fill="#f7f5f0" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="18" font-weight="500" text-anchor="start" letter-spacing="0">Cloudflare DNS + Vercel</text>
209
+ <text x="1547" y="973" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="14" font-weight="400" text-anchor="start" letter-spacing="0">app domain: DNS-only → Vercel</text>
210
+ <text x="1547" y="993" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="12" font-weight="400" text-anchor="start" letter-spacing="0">DNSSEC · assets/updates Worker domains</text>
211
+ <path d="M 620 398 C 541.8 398 528.2 393 450 393" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
212
+ <path d="M 620 550 C 541.8 550 528.2 507 450 507" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
213
+ <path d="M 620 550 C 541.8 550 528.2 621 450 621" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
214
+ <path d="M 620 702 C 541.8 702 528.2 735 450 735" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
215
+ <path d="M 620 854 C 541.8 854 528.2 849 450 849" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
216
+ <path d="M 990 398 C 1054.4 398 1065.6 520 1130 520" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-white)"/>
217
+ <path d="M 990 550 C 1054.4 550 1065.6 590 1130 590" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-white)"/>
218
+ <path d="M 990 702 C 1054.4 702 1065.6 660 1130 660" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-white)"/>
219
+ <path d="M 990 854 C 1054.4 854 1065.6 730 1130 730" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-white)"/>
220
+ <path d="M 1338 500 C 1382.62 500 1390.38 386 1435 386" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
221
+ <circle cx="1435" cy="386" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
222
+ <path d="M 1338 555 C 1382.62 555 1390.38 482 1435 482" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
223
+ <circle cx="1435" cy="482" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
224
+ <path d="M 1338 610 C 1382.62 610 1390.38 578 1435 578" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
225
+ <circle cx="1435" cy="578" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
226
+ <path d="M 1338 665 C 1382.62 665 1390.38 674 1435 674" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
227
+ <circle cx="1435" cy="674" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
228
+ <path d="M 1338 720 C 1382.62 720 1390.38 770 1435 770" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
229
+ <circle cx="1435" cy="770" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
230
+ <rect x="126" y="1050" width="1668" height="86" rx="12" fill="url(#card-fill)" stroke="#73777b" stroke-width="1.15"/>
231
+ <path d="M 246 1093 L 350 1093" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round" marker-end="url(#arrow-white)"/>
232
+ <text x="374" y="1100" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="400" text-anchor="start" letter-spacing="0">runtime API flow</text>
233
+ <path d="M 720 1093 L 824 1093" fill="none" stroke="#e9f600" stroke-width="3" stroke-linecap="round" stroke-dasharray="10 8" marker-end="url(#arrow-yellow)"/>
234
+ <text x="848" y="1100" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="400" text-anchor="start" letter-spacing="0">deploy / publish</text>
235
+ <path d="M 1198 1093 L 1302 1093" fill="none" stroke="#f7f5f0" stroke-width="3" stroke-linecap="round"/>
236
+ <circle cx="1302" cy="1093" r="8.5" fill="#b9f500" filter="url(#endpoint-glow)"/>
237
+ <text x="1332" y="1100" fill="#c4c7ca" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="17" font-weight="400" text-anchor="start" letter-spacing="0">backend service connection</text>
238
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg width="78" height="64" viewBox="0 0 78 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M38.5717 0L6.29575 59.0738L0 54.6586L29.8639 0H38.5717ZM39.0214 0L71.2973 59.0738L77.5931 54.6586L47.7292 0H39.0214Z" fill="url(#ably-symbol-a)"/>
3
+ <path d="M70.8477 59.4213L38.7966 34.3201L6.74548 59.4213L13.2865 64L38.7966 44.0294L64.3066 64L70.8477 59.4213Z" fill="url(#ably-symbol-b)"/>
4
+ <defs>
5
+ <linearGradient id="ably-symbol-a" x1="10.9472" y1="74.8439" x2="64.9206" y2="14.9005" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
6
+ <linearGradient id="ably-symbol-b" x1="21.4169" y1="78.7187" x2="53.3166" y2="43.2904" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
7
+ </defs>
8
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="1820" height="2199" viewBox="0 0 1820 2199" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M1604.59 26.7414C1694.33 -39.6752 1821.31 25.3379 1819.89 136.971L1809.28 971.026C1774.28 969.39 1738.87 969.434 1703.15 971.237C1334.16 989.855 1017.8 1191.61 837.641 1484.06L122.496 1416.65C-0.793297 1405.03 -44.6847 1247.41 54.8543 1173.73L1604.59 26.7414Z" fill="white"/>
3
+ <path d="M1795.36 2064.84C1793.79 2188.66 1640.25 2245.21 1558.74 2151.98L1083.84 1608.79C1214.01 1305.09 1479.98 1071.11 1809.07 987.262L1795.36 2064.84Z" fill="white"/>
4
+ </svg>
@@ -71,6 +71,13 @@ const MODULE_CONTRIBUTIONS = Object.freeze({
71
71
  environment: EMPTY,
72
72
  crons: EMPTY,
73
73
  }),
74
+ 'electron-updater': Object.freeze({
75
+ module: 'electron-updater',
76
+ environment: Object.freeze([
77
+ { name: 'DESKTOP_UPDATE_BASE_URL', defaultValue: 'https://updates.example.com' },
78
+ ]),
79
+ crons: EMPTY,
80
+ }),
74
81
  });
75
82
  function collectEnvironmentFromRegistry(modules, registry) {
76
83
  const byName = new Map();
@@ -1,4 +1,4 @@
1
- export declare const MODULE_IDS: readonly ["web", "mobile", "api", "desktop", "extension", "db", "auth", "billing", "storage", "native-subscriptions"];
1
+ export declare const MODULE_IDS: readonly ["web", "mobile", "api", "desktop", "extension", "db", "auth", "billing", "storage", "native-subscriptions", "electron-updater"];
2
2
  export type ModuleId = (typeof MODULE_IDS)[number];
3
3
  export type ModuleKind = 'app' | 'feature';
4
4
  export type ModuleDefinition = {
@@ -33,5 +33,5 @@ export declare class ModuleResolutionError extends Error {
33
33
  export declare function isModuleId(value: unknown): value is ModuleId;
34
34
  export declare function createModuleRegistry(definitions: readonly ModuleDefinition[]): ModuleRegistry;
35
35
  export declare const DEFAULT_MODULE_DEFINITIONS: readonly ModuleDefinition[];
36
- export declare const DEFAULT_MODULE_REGISTRY: Readonly<Record<"auth" | "billing" | "storage" | "api" | "desktop" | "web" | "mobile" | "extension" | "db" | "native-subscriptions", ModuleDefinition>>;
36
+ export declare const DEFAULT_MODULE_REGISTRY: Readonly<Record<"electron-updater" | "auth" | "billing" | "storage" | "api" | "desktop" | "web" | "mobile" | "extension" | "db" | "native-subscriptions", ModuleDefinition>>;
37
37
  export declare function resolveModules(requested: readonly string[], registry?: ModuleRegistry): ModuleResolution;
@@ -9,6 +9,7 @@ export const MODULE_IDS = [
9
9
  'billing',
10
10
  'storage',
11
11
  'native-subscriptions',
12
+ 'electron-updater',
12
13
  ];
13
14
  export class ModuleRegistryError extends Error {
14
15
  code;
@@ -119,6 +120,7 @@ const DEFAULT_MODULE_DEFINITION_INPUTS = [
119
120
  { id: 'billing', kind: 'feature', requires: ['auth'], conflicts: [] },
120
121
  { id: 'storage', kind: 'feature', requires: ['auth'], conflicts: [] },
121
122
  { id: 'native-subscriptions', kind: 'feature', requires: ['mobile', 'billing'], conflicts: [] },
123
+ { id: 'electron-updater', kind: 'feature', requires: ['desktop'], conflicts: [] },
122
124
  ];
123
125
  export const DEFAULT_MODULE_DEFINITIONS = Object.freeze(DEFAULT_MODULE_DEFINITION_INPUTS.map(freezeDefinition));
124
126
  export const DEFAULT_MODULE_REGISTRY = createModuleRegistry(DEFAULT_MODULE_DEFINITIONS);
@@ -162,14 +164,20 @@ export function resolveModules(requested, registry = DEFAULT_MODULE_REGISTRY) {
162
164
  const modules = Object.freeze([...new Set(conflicts.flat())].sort(compareModules));
163
165
  throw new ModuleResolutionError('MODULE_CONFLICT', `Conflicting modules: ${conflicts.map(([left, right]) => `${left} + ${right}`).join(', ')}`, modules);
164
166
  }
167
+ // Resolution must have one stable representation regardless of whether a
168
+ // dependency was requested directly or discovered while walking another
169
+ // module. Without this final normalization, adding a module to an existing
170
+ // project could produce a different manifest order than creating the same
171
+ // topology directly (for example, adding electron-updater after db/auth).
172
+ const resolvedModules = normalizeModuleList(ordered);
165
173
  const requestedSet = new Set(requestedModules);
166
- const dependencyAddedModules = ordered.filter((moduleId) => !requestedSet.has(moduleId));
167
- const dependencyEdges = ordered.flatMap((moduleId) => registry[moduleId].requires
174
+ const dependencyAddedModules = resolvedModules.filter((moduleId) => !requestedSet.has(moduleId));
175
+ const dependencyEdges = resolvedModules.flatMap((moduleId) => registry[moduleId].requires
168
176
  .filter((required) => resolved.has(required))
169
177
  .map((required) => Object.freeze({ module: moduleId, requires: required })));
170
178
  return Object.freeze({
171
179
  requestedModules,
172
- resolvedModules: Object.freeze(ordered),
180
+ resolvedModules,
173
181
  dependencyAddedModules: Object.freeze(dependencyAddedModules),
174
182
  dependencyEdges: Object.freeze(dependencyEdges),
175
183
  });