anhedral 0.4.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/CHANGELOG.md +13 -0
- package/README.md +8 -2
- package/assets/anhedral-cli-init.svg +238 -0
- package/assets/images/svg/ably-symbol-color.svg +8 -0
- package/assets/images/svg/logo-white-subtract.svg +4 -0
- package/dist/scaffold.js +15 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/master-stack-map.md +7 -0
- package/docs/output-tree-contract.md +4 -4
- package/package.json +8 -2
- package/scripts/render-master-stack-map.mjs +510 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.4.1 - 2026-07-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add the public launch artwork, reusable stack-map renderer, and expanded brand asset library to the repository and published package.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Present Anhedral as a cross-platform, modular product stack that lets teams build once, ship everywhere, and scale without repeating framework setup.
|
|
18
|
+
- Automatically create patch versions for ordinary changes merged to `main`, while preserving explicitly declared minor and major releases.
|
|
19
|
+
- Keep project upgrades compatible across automatic patch releases and retain the supported 0.3-to-0.4 ownership migration.
|
|
20
|
+
- Publish from the exact generated release commit and attach npm provenance now that the source repository is public.
|
|
21
|
+
|
|
9
22
|
## 0.4.0 - 2026-07-22
|
|
10
23
|
|
|
11
24
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
Anhedral generates a complete, production-oriented TypeScript stack whose source code stays understandable and customizable.
|
|
7
7
|
|
|
8
|
-
> **
|
|
8
|
+
> **Build once. Ship everywhere. Scale is built in.** Pick the platforms,
|
|
9
|
+
> frameworks, and features your product needs; Anhedral assembles them into a
|
|
10
|
+
> production-ready TypeScript workspace for web, iOS, Android, desktop, APIs,
|
|
11
|
+
> and browser extensions. Start free, skip months of repetitive setup and
|
|
12
|
+
> integration work, and focus on what makes your app unique.
|
|
13
|
+
|
|
14
|
+

|
|
9
15
|
|
|
10
16
|
Anhedral is not a new programming language and generated applications do not run inside a proprietary application framework. It assembles well-documented tools, connects their difficult integration boundaries, and leaves developers with ordinary Next.js, Expo Router, Fastify, Drizzle, Electron, and WXT projects.
|
|
11
17
|
|
|
@@ -164,7 +170,7 @@ contracts -> database/service -> Fastify route -> typed API client -> frontend
|
|
|
164
170
|
| Authentication | Clerk | Connected frontend and backend identity/session flows |
|
|
165
171
|
| UI | shadcn/ui + React Native Reusables | Accessible source code the application owns |
|
|
166
172
|
| Storage | private Cloudflare R2 | Signed uploads and controlled asset delivery |
|
|
167
|
-
| Billing/realtime | RevenueCat + Ably |
|
|
173
|
+
| Billing/realtime | RevenueCat + Stripe + Ably | Unified billing entitlements and realtime invalidation |
|
|
168
174
|
| Desktop | Electron | Predictable cross-platform TypeScript desktop runtime |
|
|
169
175
|
| Desktop updates | electron-updater + private R2 + Worker | Signed automatic updates through a product-owned custom domain |
|
|
170
176
|
| Extension | WXT | Browser-extension entrypoints, builds, and packaging |
|
|
@@ -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 & 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>
|
package/dist/scaffold.js
CHANGED
|
@@ -171,7 +171,21 @@ function toolchainChannelFromManifest(manifest) {
|
|
|
171
171
|
return resolveToolchainChannel(manifest.toolchain);
|
|
172
172
|
}
|
|
173
173
|
export function isSupportedProjectUpgrade(fromVersion, toVersion) {
|
|
174
|
-
|
|
174
|
+
const parseStableVersion = (version) => {
|
|
175
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
|
|
176
|
+
return match
|
|
177
|
+
? [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
178
|
+
: undefined;
|
|
179
|
+
};
|
|
180
|
+
const from = parseStableVersion(fromVersion);
|
|
181
|
+
const to = parseStableVersion(toVersion);
|
|
182
|
+
if (!from || !to)
|
|
183
|
+
return false;
|
|
184
|
+
const supportedOwnershipMigration = fromVersion === '0.3.0' && to[0] === 0 && to[1] === 4;
|
|
185
|
+
const compatiblePatchUpgrade = from[0] === to[0]
|
|
186
|
+
&& from[1] === to[1]
|
|
187
|
+
&& from[2] < to[2];
|
|
188
|
+
return supportedOwnershipMigration || compatiblePatchUpgrade;
|
|
175
189
|
}
|
|
176
190
|
function ensureScaffoldRoot(root) {
|
|
177
191
|
const rootStat = lstatIfPresent(root);
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Kept in sync with package.json by tests/release checks. */
|
|
2
|
-
export declare const GENERATOR_VERSION = "0.4.
|
|
2
|
+
export declare const GENERATOR_VERSION = "0.4.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Kept in sync with package.json by tests/release checks. */
|
|
2
|
-
export const GENERATOR_VERSION = '0.4.
|
|
2
|
+
export const GENERATOR_VERSION = '0.4.1';
|
package/docs/master-stack-map.md
CHANGED
|
@@ -5,6 +5,13 @@ from skill-led planning through deterministic generation, source layout,
|
|
|
5
5
|
connected capabilities, cloud provisioning, command-driven operations, agent
|
|
6
6
|
experience, and production verification.
|
|
7
7
|
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
The diagram is generated from `scripts/render-master-stack-map.mjs`. Change the
|
|
11
|
+
content or geometry there, then run `pnpm docs:stack-map`; do not hand-edit the
|
|
12
|
+
SVG. The fixed-width map below remains the detailed text and accessibility
|
|
13
|
+
reference.
|
|
14
|
+
|
|
8
15
|
Read top to bottom. Arrows represent lifecycle order, not runtime network calls.
|
|
9
16
|
Runtime and provider connections are stated explicitly inside each stage.
|
|
10
17
|
|
|
@@ -21,10 +21,10 @@ node tests/update-output-tree-contracts.js
|
|
|
21
21
|
|
|
22
22
|
| Scenario | Apps | Features | Contract | Entries | Tree SHA-256 |
|
|
23
23
|
| --- | --- | --- | --- | ---: | --- |
|
|
24
|
-
| `expo-extension` | web, mobile, api, desktop, extension | database, auth, billing, storage, nativeSubscriptions, electronUpdater | deterministic golden | 145 | `
|
|
25
|
-
| `web-api-minimal` | web, api | database, auth | deterministic golden | 65 | `
|
|
26
|
-
| `api-only` | api | | deterministic golden | 32 | `
|
|
27
|
-
| `add-desktop-flow` | api, desktop | database, auth | deterministic golden | 67 | `
|
|
24
|
+
| `expo-extension` | web, mobile, api, desktop, extension | database, auth, billing, storage, nativeSubscriptions, electronUpdater | deterministic golden | 145 | `bc2ffd2b31666349e9fbf385b5c8e2df520968a6a2ef1eeba9c431f3b1690ed2` |
|
|
25
|
+
| `web-api-minimal` | web, api | database, auth | deterministic golden | 65 | `3311e164a5ec03c232dbfeabb35209f9c8206422c1864179d39a65d2d54cf443` |
|
|
26
|
+
| `api-only` | api | | deterministic golden | 32 | `3f0c4317a7c3aa06d5e51f7196dc835c6f47ec34c0a4d6daf8d17355a541baaa` |
|
|
27
|
+
| `add-desktop-flow` | api, desktop | database, auth | deterministic golden | 67 | `f78d74dc4d32688ebb5b88bbe16bc01f68590254c1799be8b7b18f3566ed8c7a` |
|
|
28
28
|
|
|
29
29
|
## expo-extension
|
|
30
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anhedral",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Generate a complete, readable TypeScript product stack with Next.js, Expo, Fastify, Neon, Drizzle, Clerk, R2, Electron updates, WXT, and Vercel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
"bin",
|
|
19
19
|
"dist",
|
|
20
20
|
"docs/conventions.md",
|
|
21
|
+
"assets/anhedral-cli-init.svg",
|
|
22
|
+
"assets/images/svg/ably-symbol-color.svg",
|
|
23
|
+
"assets/images/svg/logo-white-subtract.svg",
|
|
21
24
|
"docs/master-stack-map.md",
|
|
22
25
|
"docs/output-tree-contract.md",
|
|
23
26
|
"docs/references",
|
|
27
|
+
"scripts/render-master-stack-map.mjs",
|
|
24
28
|
"templates",
|
|
25
29
|
"SKILL.md",
|
|
26
30
|
"CHANGELOG.md",
|
|
@@ -84,7 +88,8 @@
|
|
|
84
88
|
"release:pack": "pnpm build && node scripts/create-release-artifact.mjs && node scripts/check-release-secrets.mjs release-artifact/metadata.json",
|
|
85
89
|
"release:artifact:verify": "node scripts/verify-release-artifact.mjs release-artifact/metadata.json && node scripts/check-release-secrets.mjs release-artifact/metadata.json",
|
|
86
90
|
"release:artifact:smoke": "node tests/test-packed-cli.js release-artifact/metadata.json",
|
|
87
|
-
"demo:refresh": "pnpm build && node tests/refresh-demo.js"
|
|
91
|
+
"demo:refresh": "pnpm build && node tests/refresh-demo.js",
|
|
92
|
+
"docs:stack-map": "node scripts/render-master-stack-map.mjs"
|
|
88
93
|
},
|
|
89
94
|
"keywords": [
|
|
90
95
|
"cli",
|
|
@@ -106,6 +111,7 @@
|
|
|
106
111
|
"devDependencies": {
|
|
107
112
|
"@types/node": "20.19.43",
|
|
108
113
|
"c8": "11.0.0",
|
|
114
|
+
"simple-icons": "16.27.0",
|
|
109
115
|
"typescript": "5.9.3"
|
|
110
116
|
}
|
|
111
117
|
}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import {
|
|
5
|
+
siAppstore,
|
|
6
|
+
siClerk,
|
|
7
|
+
siCloudflare,
|
|
8
|
+
siCloudflareworkers,
|
|
9
|
+
siDrizzle,
|
|
10
|
+
siElectron,
|
|
11
|
+
siExpo,
|
|
12
|
+
siFastify,
|
|
13
|
+
siGithub,
|
|
14
|
+
siNeon,
|
|
15
|
+
siNextdotjs,
|
|
16
|
+
siReact,
|
|
17
|
+
siRevenuecat,
|
|
18
|
+
siShadcnui,
|
|
19
|
+
siStripe,
|
|
20
|
+
siTailwindcss,
|
|
21
|
+
siTypescript,
|
|
22
|
+
siVercel,
|
|
23
|
+
siWxt,
|
|
24
|
+
siZod,
|
|
25
|
+
} from 'simple-icons';
|
|
26
|
+
|
|
27
|
+
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const repositoryRoot = resolve(scriptDirectory, '..');
|
|
29
|
+
const outputFlag = process.argv.indexOf('--output');
|
|
30
|
+
const outputPath = resolve(outputFlag >= 0 && process.argv[outputFlag + 1]
|
|
31
|
+
? process.argv[outputFlag + 1]
|
|
32
|
+
: resolve(repositoryRoot, 'assets/anhedral-cli-init.svg'));
|
|
33
|
+
const anhedralMarkPath = resolve(repositoryRoot, 'assets/images/svg/logo-white-subtract.svg');
|
|
34
|
+
const anhedralMarkSource = await readFile(anhedralMarkPath, 'utf8');
|
|
35
|
+
const ablySymbolPath = resolve(repositoryRoot, 'assets/images/svg/ably-symbol-color.svg');
|
|
36
|
+
const ablySymbolSource = await readFile(ablySymbolPath, 'utf8');
|
|
37
|
+
const [, ablySymbolContent = ''] = ablySymbolSource.match(/<svg\b[^>]*>([\s\S]*?)<\/svg>/) ?? [];
|
|
38
|
+
const [, markViewBox = '0 0 1820 2199'] = anhedralMarkSource.match(/viewBox="([^"]+)"/) ?? [];
|
|
39
|
+
const [, , markWidth = '1820', markHeight = '2199'] = markViewBox.split(/\s+/).map(Number);
|
|
40
|
+
const markPaths = [...anhedralMarkSource.matchAll(/<path\b[^>]*\bd="([^"]+)"[^>]*>/g)].map((match) => match[1]);
|
|
41
|
+
|
|
42
|
+
const W = 1920;
|
|
43
|
+
const H = 1180;
|
|
44
|
+
const palette = {
|
|
45
|
+
background: '#0c1117',
|
|
46
|
+
panel: '#0a1724',
|
|
47
|
+
panelEnd: '#07121d',
|
|
48
|
+
border: '#73777b',
|
|
49
|
+
divider: '#34424e',
|
|
50
|
+
text: '#f7f5f0',
|
|
51
|
+
muted: '#c4c7ca',
|
|
52
|
+
yellow: '#e9f600',
|
|
53
|
+
lime: '#b9f500',
|
|
54
|
+
cyan: '#51dce9',
|
|
55
|
+
orange: '#f48120',
|
|
56
|
+
red: '#ff3b3f',
|
|
57
|
+
blue: '#2585f9',
|
|
58
|
+
neon: '#35d6a8',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const svg = [];
|
|
62
|
+
const escapeXml = (value) => String(value)
|
|
63
|
+
.replaceAll('&', '&')
|
|
64
|
+
.replaceAll('<', '<')
|
|
65
|
+
.replaceAll('>', '>')
|
|
66
|
+
.replaceAll('"', '"');
|
|
67
|
+
|
|
68
|
+
function text(x, y, value, {
|
|
69
|
+
size = 18,
|
|
70
|
+
fill = palette.text,
|
|
71
|
+
weight = 400,
|
|
72
|
+
anchor = 'start',
|
|
73
|
+
spacing = 0,
|
|
74
|
+
italic = false,
|
|
75
|
+
} = {}) {
|
|
76
|
+
svg.push(`<text x="${x}" y="${y}" fill="${fill}" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="${size}" font-weight="${weight}" text-anchor="${anchor}" letter-spacing="${spacing}"${italic ? ' font-style="italic"' : ''}>${escapeXml(value)}</text>`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function rect(x, y, width, height, {
|
|
80
|
+
fill = 'url(#card-fill)', stroke = palette.border, strokeWidth = 1.15, radius = 12,
|
|
81
|
+
} = {}) {
|
|
82
|
+
svg.push(`<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}"/>`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function line(x1, y1, x2, y2, {
|
|
86
|
+
stroke = palette.divider, width = 1, dash = '', marker = '',
|
|
87
|
+
} = {}) {
|
|
88
|
+
svg.push(`<path d="M ${x1} ${y1} L ${x2} ${y2}" fill="none" stroke="${stroke}" stroke-width="${width}" stroke-linecap="round"${dash ? ` stroke-dasharray="${dash}"` : ''}${marker ? ` marker-end="url(#${marker})"` : ''}/>`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function path(d, {
|
|
92
|
+
stroke = palette.text, width = 3, dash = '', marker = 'arrow-white',
|
|
93
|
+
} = {}) {
|
|
94
|
+
svg.push(`<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${width}" stroke-linecap="round" stroke-linejoin="round"${dash ? ` stroke-dasharray="${dash}"` : ''}${marker ? ` marker-end="url(#${marker})"` : ''}/>`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function smoothConnector(x1, y1, x2, y2, {
|
|
98
|
+
curvature = 0.46,
|
|
99
|
+
...pathOptions
|
|
100
|
+
} = {}) {
|
|
101
|
+
const horizontalDistance = Math.abs(x2 - x1);
|
|
102
|
+
const direction = Math.sign(x2 - x1) || 1;
|
|
103
|
+
const controlOffset = Math.min(
|
|
104
|
+
horizontalDistance / 2,
|
|
105
|
+
Math.max(36, horizontalDistance * curvature),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
path(
|
|
109
|
+
`M ${x1} ${y1} C ${x1 + direction * controlOffset} ${y1} ${x2 - direction * controlOffset} ${y2} ${x2} ${y2}`,
|
|
110
|
+
pathOptions,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function logo(icon, x, y, size, fill) {
|
|
115
|
+
const scale = size / 24;
|
|
116
|
+
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})"><path d="${icon.path}" fill="${fill}"/></g>`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function anhedralMark(x, y, height) {
|
|
120
|
+
const scale = height / markHeight;
|
|
121
|
+
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})">`);
|
|
122
|
+
for (const d of markPaths) svg.push(`<path d="${d}" fill="${palette.text}"/>`);
|
|
123
|
+
svg.push('</g>');
|
|
124
|
+
return markWidth * scale;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ablySymbol(x, y, width) {
|
|
128
|
+
const scale = width / 78;
|
|
129
|
+
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})">${ablySymbolContent}</g>`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function iconBox(x, y, size = 70) {
|
|
133
|
+
rect(x, y, size, size, { fill: '#07121d', stroke: palette.divider, radius: 11 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function customGlobe(x, y, size, stroke = palette.text) {
|
|
137
|
+
const cx = x + size / 2;
|
|
138
|
+
const cy = y + size / 2;
|
|
139
|
+
const r = size * 0.34;
|
|
140
|
+
svg.push(`<g fill="none" stroke="${stroke}" stroke-width="3">
|
|
141
|
+
<circle cx="${cx}" cy="${cy}" r="${r}"/>
|
|
142
|
+
<ellipse cx="${cx}" cy="${cy}" rx="${r * 0.45}" ry="${r}"/>
|
|
143
|
+
<path d="M ${cx - r} ${cy} H ${cx + r} M ${cx - r * 0.84} ${cy - r * 0.5} H ${cx + r * 0.84} M ${cx - r * 0.84} ${cy + r * 0.5} H ${cx + r * 0.84}"/>
|
|
144
|
+
</g>`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function customDesktop(x, y, size, stroke = palette.text) {
|
|
148
|
+
svg.push(`<g fill="none" stroke="${stroke}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
149
|
+
<rect x="${x + 9}" y="${y + 9}" width="${size - 18}" height="${size - 27}" rx="3"/>
|
|
150
|
+
<path d="M ${x + size / 2} ${y + size - 18} V ${y + size - 8} M ${x + size * 0.28} ${y + size - 8} H ${x + size * 0.72}"/>
|
|
151
|
+
</g>`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function customAppStore(x, y, size) {
|
|
155
|
+
const badgeSize = size - 16;
|
|
156
|
+
const badgeX = x + 8;
|
|
157
|
+
const badgeY = y + 8;
|
|
158
|
+
svg.push(`<rect x="${badgeX}" y="${badgeY}" width="${badgeSize}" height="${badgeSize}" rx="11" fill="url(#app-store-fill)"/>`);
|
|
159
|
+
logo(siAppstore, badgeX + 9, badgeY + 9, badgeSize - 18, palette.text);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function customGooglePlay(x, y, size) {
|
|
163
|
+
const scale = (size - 8) / 64;
|
|
164
|
+
svg.push(`<g transform="translate(${x + 4} ${y + 4}) scale(${scale})" stroke-linejoin="round">
|
|
165
|
+
<path d="M 9 6 L 39 32 L 9 58 Z" fill="#23d7f2"/>
|
|
166
|
+
<path d="M 9 6 L 44 24.5 L 39 32 Z" fill="#2bd66f"/>
|
|
167
|
+
<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"/>
|
|
168
|
+
<path d="M 9 58 L 44 39.5 L 39 32 Z" fill="#f04b55"/>
|
|
169
|
+
</g>`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function customChrome(x, y, size) {
|
|
173
|
+
const badgeSize = size - 10;
|
|
174
|
+
const badgeX = x + 5;
|
|
175
|
+
const badgeY = y + 5;
|
|
176
|
+
const cx = badgeX + badgeSize / 2;
|
|
177
|
+
const cy = badgeY + badgeSize / 2;
|
|
178
|
+
const r = badgeSize * 0.36;
|
|
179
|
+
const halfR = r * 0.5;
|
|
180
|
+
const highR = r * Math.sqrt(3) / 2;
|
|
181
|
+
|
|
182
|
+
svg.push(`<rect x="${badgeX}" y="${badgeY}" width="${badgeSize}" height="${badgeSize}" rx="11" fill="${palette.text}"/>`);
|
|
183
|
+
svg.push(`<path d="M ${cx} ${cy} L ${cx - halfR} ${cy - highR} A ${r} ${r} 0 0 1 ${cx + r} ${cy} Z" fill="#ea4335"/>`);
|
|
184
|
+
svg.push(`<path d="M ${cx} ${cy} L ${cx + r} ${cy} A ${r} ${r} 0 0 1 ${cx - halfR} ${cy + highR} Z" fill="#fbbc04"/>`);
|
|
185
|
+
svg.push(`<path d="M ${cx} ${cy} L ${cx - halfR} ${cy + highR} A ${r} ${r} 0 0 1 ${cx - halfR} ${cy - highR} Z" fill="#34a853"/>`);
|
|
186
|
+
svg.push(`<circle cx="${cx}" cy="${cy}" r="${r * 0.48}" fill="${palette.text}"/>`);
|
|
187
|
+
svg.push(`<circle cx="${cx}" cy="${cy}" r="${r * 0.36}" fill="#4285f4"/>`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function card(x, y, width, height, {
|
|
191
|
+
title,
|
|
192
|
+
subtitle,
|
|
193
|
+
detail = '',
|
|
194
|
+
icon,
|
|
195
|
+
iconColor = palette.text,
|
|
196
|
+
secondIcon,
|
|
197
|
+
secondIconColor = palette.text,
|
|
198
|
+
secondEmbeddedIcon,
|
|
199
|
+
customIcon,
|
|
200
|
+
cornerIcon,
|
|
201
|
+
cornerIconColor = palette.text,
|
|
202
|
+
iconScale = 52,
|
|
203
|
+
compact = false,
|
|
204
|
+
}) {
|
|
205
|
+
rect(x, y, width, height);
|
|
206
|
+
const boxSize = height - 24;
|
|
207
|
+
const boxX = x + 16;
|
|
208
|
+
const boxY = y + 12;
|
|
209
|
+
iconBox(boxX, boxY, boxSize);
|
|
210
|
+
|
|
211
|
+
if (customIcon === 'globe') customGlobe(boxX, boxY, boxSize, iconColor);
|
|
212
|
+
else if (customIcon === 'desktop') customDesktop(boxX, boxY, boxSize, iconColor);
|
|
213
|
+
else if (customIcon === 'app-store') customAppStore(boxX, boxY, boxSize);
|
|
214
|
+
else if (customIcon === 'google-play') customGooglePlay(boxX, boxY, boxSize);
|
|
215
|
+
else if (customIcon === 'chrome') customChrome(boxX, boxY, boxSize);
|
|
216
|
+
else if (customIcon === 'ably') {
|
|
217
|
+
const ablyWidth = boxSize - 12;
|
|
218
|
+
ablySymbol(boxX + 6, boxY + (boxSize - ablyWidth * 64 / 78) / 2, ablyWidth);
|
|
219
|
+
}
|
|
220
|
+
else if (icon) {
|
|
221
|
+
if (secondIcon || secondEmbeddedIcon) {
|
|
222
|
+
const pairedSize = compact ? 26 : 38;
|
|
223
|
+
const pairedInset = compact ? 5 : 6;
|
|
224
|
+
logo(icon, boxX + pairedInset, boxY + (boxSize - pairedSize) / 2, pairedSize, iconColor);
|
|
225
|
+
if (secondEmbeddedIcon === 'ably') {
|
|
226
|
+
ablySymbol(boxX + boxSize - pairedSize - 4, boxY + (boxSize - pairedSize * 64 / 78) / 2, pairedSize);
|
|
227
|
+
} else {
|
|
228
|
+
logo(
|
|
229
|
+
secondIcon,
|
|
230
|
+
boxX + boxSize - pairedSize - pairedInset,
|
|
231
|
+
boxY + (boxSize - pairedSize) / 2,
|
|
232
|
+
pairedSize,
|
|
233
|
+
secondIconColor,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
const renderedIconScale = compact ? Math.min(iconScale, boxSize - 8) : iconScale;
|
|
238
|
+
logo(
|
|
239
|
+
icon,
|
|
240
|
+
boxX + (boxSize - renderedIconScale) / 2,
|
|
241
|
+
boxY + (boxSize - renderedIconScale) / 2,
|
|
242
|
+
renderedIconScale,
|
|
243
|
+
iconColor,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const dividerX = boxX + boxSize + (compact ? 12 : 10);
|
|
249
|
+
const textX = dividerX + (compact ? 20 : 24);
|
|
250
|
+
line(dividerX, y + 14, dividerX, y + height - 14);
|
|
251
|
+
text(textX, y + (compact ? 31 : 42), title, { size: compact ? 18 : 22, weight: 500 });
|
|
252
|
+
text(textX, y + (compact ? 55 : 70), subtitle, { size: compact ? 14 : 16, fill: palette.muted });
|
|
253
|
+
if (detail) text(textX, y + (compact ? 75 : 91), detail, { size: compact ? 12 : 13, fill: palette.muted });
|
|
254
|
+
if (cornerIcon) logo(cornerIcon, x + width - 32, y + 15, 17, cornerIconColor);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function heading(x, y, width, value) {
|
|
258
|
+
text(x + width / 2, y, value, {
|
|
259
|
+
size: 17, fill: palette.yellow, weight: 600, anchor: 'middle', spacing: 3.2,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function endpoint(x, y) {
|
|
264
|
+
svg.push(`<circle cx="${x}" cy="${y}" r="8.5" fill="${palette.lime}" filter="url(#endpoint-glow)"/>`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function dxTool(x, y, {
|
|
268
|
+
icon,
|
|
269
|
+
label,
|
|
270
|
+
color = palette.text,
|
|
271
|
+
anhedral = false,
|
|
272
|
+
}) {
|
|
273
|
+
const width = 88;
|
|
274
|
+
const height = 92;
|
|
275
|
+
rect(x, y, width, height, { fill: '#07121d', stroke: palette.divider, radius: 11 });
|
|
276
|
+
if (anhedral) {
|
|
277
|
+
const renderedWidth = markWidth / markHeight * 34;
|
|
278
|
+
anhedralMark(x + (width - renderedWidth) / 2, y + 14, 34);
|
|
279
|
+
} else {
|
|
280
|
+
logo(icon, x + 27, y + 14, 34, color);
|
|
281
|
+
}
|
|
282
|
+
text(x + width / 2, y + 72, label, {
|
|
283
|
+
size: 11,
|
|
284
|
+
fill: palette.muted,
|
|
285
|
+
weight: 500,
|
|
286
|
+
anchor: 'middle',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
svg.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-labelledby="title description">`);
|
|
291
|
+
svg.push('<title id="title">Anhedral Init Stack</title>');
|
|
292
|
+
svg.push('<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>');
|
|
293
|
+
svg.push(`<defs>
|
|
294
|
+
<linearGradient id="card-fill" x1="0" y1="0" x2="1" y2="1">
|
|
295
|
+
<stop offset="0" stop-color="#0d1b29"/>
|
|
296
|
+
<stop offset="1" stop-color="#07121d"/>
|
|
297
|
+
</linearGradient>
|
|
298
|
+
<linearGradient id="app-store-fill" x1="0" y1="0" x2="0" y2="1">
|
|
299
|
+
<stop offset="0" stop-color="#24c7fa"/>
|
|
300
|
+
<stop offset="1" stop-color="#0878f9"/>
|
|
301
|
+
</linearGradient>
|
|
302
|
+
<filter id="endpoint-glow" x="-100%" y="-100%" width="300%" height="300%">
|
|
303
|
+
<feGaussianBlur in="SourceGraphic" stdDeviation="1.7"/>
|
|
304
|
+
</filter>
|
|
305
|
+
<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="${palette.text}"/></marker>
|
|
306
|
+
<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="${palette.yellow}"/></marker>
|
|
307
|
+
</defs>`);
|
|
308
|
+
svg.push(`<rect width="${W}" height="${H}" fill="${palette.background}"/>`);
|
|
309
|
+
|
|
310
|
+
// Official Anhedral asset plus wordmark.
|
|
311
|
+
const renderedMarkWidth = anhedralMark(28, 34, 58);
|
|
312
|
+
const brandRuleX = 28 + renderedMarkWidth + 17;
|
|
313
|
+
line(brandRuleX, 26, brandRuleX, 104, { stroke: palette.border, width: 1.5 });
|
|
314
|
+
text(brandRuleX + 22, 80, 'ANHEDRAL', { size: 27, weight: 500, spacing: 1.3 });
|
|
315
|
+
[
|
|
316
|
+
{ x: 32, value: 'Anhedral', underlineEnd: 366 },
|
|
317
|
+
{ x: 392, value: 'Init', underlineEnd: 506 },
|
|
318
|
+
{ x: 532, value: 'Stack', underlineEnd: 747 },
|
|
319
|
+
].forEach(({ x, value, underlineEnd }) => {
|
|
320
|
+
text(x, 176, value, { size: 78, weight: 700 });
|
|
321
|
+
line(x + 1, 195, underlineEnd, 195, { stroke: palette.yellow, width: 6 });
|
|
322
|
+
});
|
|
323
|
+
text(34, 240, 'One init flow for web, native apps, extensions, backend,', { size: 24 });
|
|
324
|
+
text(34, 274, 'storage, auth, subscriptions, and seamless desktop updates.', { size: 24 });
|
|
325
|
+
|
|
326
|
+
const dxToolStartX = 1187;
|
|
327
|
+
const dxToolY = 76;
|
|
328
|
+
const dxToolGap = 98;
|
|
329
|
+
heading(dxToolStartX, 52, 676, 'DX TOOLS');
|
|
330
|
+
[
|
|
331
|
+
{ icon: siTailwindcss, label: 'Tailwind', color: '#06b6d4' },
|
|
332
|
+
{ icon: siShadcnui, label: 'shadcn/ui', color: palette.text },
|
|
333
|
+
{ icon: siReact, label: 'RN Reusables', color: '#61dafb' },
|
|
334
|
+
{ icon: siTypescript, label: 'TypeScript', color: '#3178c6' },
|
|
335
|
+
{ icon: siZod, label: 'Zod', color: '#3e67b1' },
|
|
336
|
+
{ icon: siGithub, label: 'GitHub', color: palette.text },
|
|
337
|
+
{ label: 'Anhedral Skills', anhedral: true },
|
|
338
|
+
].forEach((tool, index) => dxTool(dxToolStartX + index * dxToolGap, dxToolY, tool));
|
|
339
|
+
|
|
340
|
+
const deployX = 54;
|
|
341
|
+
const deployW = 396;
|
|
342
|
+
const clientX = 620;
|
|
343
|
+
const clientW = 370;
|
|
344
|
+
const apiX = 1130;
|
|
345
|
+
const apiW = 208;
|
|
346
|
+
const serviceX = 1435;
|
|
347
|
+
const serviceW = 430;
|
|
348
|
+
|
|
349
|
+
heading(deployX, 318, deployW, 'DEPLOY & DISTRIBUTE');
|
|
350
|
+
heading(clientX, 318, clientW, 'CLIENT SURFACES');
|
|
351
|
+
heading(apiX, 318, serviceX + serviceW - apiX, 'BACKEND + SERVICES');
|
|
352
|
+
|
|
353
|
+
const deployRows = [342, 456, 570, 684, 798];
|
|
354
|
+
[
|
|
355
|
+
{ title: 'Web', subtitle: 'Vercel preview + production', detail: 'hosts web + API', customIcon: 'globe' },
|
|
356
|
+
{ title: 'App Store', subtitle: 'iOS distribution', detail: 'EAS signed release', customIcon: 'app-store' },
|
|
357
|
+
{ title: 'Google Play', subtitle: 'Android distribution', detail: 'EAS signed release', customIcon: 'google-play' },
|
|
358
|
+
{ title: 'Browser Web Stores', subtitle: 'extension distribution', detail: 'reviewed WXT ZIP', customIcon: 'chrome' },
|
|
359
|
+
{ title: 'Desktop Releases', subtitle: 'macOS · Windows · Linux', detail: 'signed installers + seamless updates', customIcon: 'desktop', iconColor: palette.text },
|
|
360
|
+
].forEach((item, index) => card(deployX, deployRows[index], deployW, 102, item));
|
|
361
|
+
|
|
362
|
+
const clientRows = [342, 494, 646, 798];
|
|
363
|
+
[
|
|
364
|
+
{
|
|
365
|
+
title: 'Next.js',
|
|
366
|
+
subtitle: 'shadcn/ui',
|
|
367
|
+
detail: 'web product + server routes',
|
|
368
|
+
icon: siNextdotjs,
|
|
369
|
+
iconColor: palette.text,
|
|
370
|
+
iconScale: 58,
|
|
371
|
+
cornerIcon: siVercel,
|
|
372
|
+
},
|
|
373
|
+
{ title: 'Expo Native', subtitle: 'React Native Reusables', detail: 'iOS + Android', icon: siExpo, iconColor: palette.text, iconScale: 58 },
|
|
374
|
+
{ title: 'WXT Extension', subtitle: 'Chrome · Firefox · Edge', detail: 'MV3 background + side panel', icon: siWxt, iconColor: palette.lime, iconScale: 58 },
|
|
375
|
+
{ title: 'Electron Desktop', subtitle: 'macOS · Windows · Linux', detail: 'electron-updater enabled', icon: siElectron, iconColor: palette.cyan, iconScale: 58 },
|
|
376
|
+
].forEach((item, index) => card(clientX, clientRows[index], clientW, 112, item));
|
|
377
|
+
|
|
378
|
+
// One central API, matching the supplied simplified architecture.
|
|
379
|
+
rect(apiX, 474, apiW, 296, { radius: 14 });
|
|
380
|
+
const apiIconBoxSize = 88;
|
|
381
|
+
const apiIconBoxX = apiX + (apiW - apiIconBoxSize) / 2;
|
|
382
|
+
const apiIconBoxY = 490;
|
|
383
|
+
iconBox(apiIconBoxX, apiIconBoxY, apiIconBoxSize);
|
|
384
|
+
logo(siFastify, apiIconBoxX + 10, apiIconBoxY + 10, 68, palette.text);
|
|
385
|
+
logo(siVercel, apiX + apiW - 32, 490, 17, palette.text);
|
|
386
|
+
line(apiX + 18, 602, apiX + apiW - 18, 602, { stroke: palette.divider });
|
|
387
|
+
text(apiX + apiW / 2, 643, 'Fastify Backend', { size: 25, weight: 500, anchor: 'middle' });
|
|
388
|
+
text(apiX + apiW / 2, 675, 'API + business logic', { size: 17, fill: palette.muted, anchor: 'middle' });
|
|
389
|
+
|
|
390
|
+
const serviceRows = [342, 438, 534, 630, 726, 822, 918];
|
|
391
|
+
const serviceCardHeight = 88;
|
|
392
|
+
[
|
|
393
|
+
{
|
|
394
|
+
title: 'Neon + Drizzle',
|
|
395
|
+
subtitle: 'Postgres + ORM',
|
|
396
|
+
detail: 'reviewed migrations',
|
|
397
|
+
icon: siNeon,
|
|
398
|
+
iconColor: palette.neon,
|
|
399
|
+
secondIcon: siDrizzle,
|
|
400
|
+
secondIconColor: palette.yellow,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
title: 'Clerk Auth',
|
|
404
|
+
subtitle: 'identity + sessions',
|
|
405
|
+
detail: 'verified server-side',
|
|
406
|
+
icon: siClerk,
|
|
407
|
+
iconColor: palette.text,
|
|
408
|
+
iconScale: 56,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
title: 'RevenueCat + Stripe',
|
|
412
|
+
subtitle: 'subscriptions + payments',
|
|
413
|
+
detail: 'billing integration + shared entitlements',
|
|
414
|
+
icon: siRevenuecat,
|
|
415
|
+
iconColor: palette.red,
|
|
416
|
+
secondIcon: siStripe,
|
|
417
|
+
secondIconColor: '#635bff',
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
title: 'Ably Realtime',
|
|
421
|
+
subtitle: 'scoped invalidations',
|
|
422
|
+
detail: 'outbox → event → client refetch',
|
|
423
|
+
customIcon: 'ably',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
title: 'Assets Worker',
|
|
427
|
+
subtitle: 'PRIVATE R2 assets binding',
|
|
428
|
+
detail: 'assets.example.com · authorized reads',
|
|
429
|
+
icon: siCloudflareworkers,
|
|
430
|
+
iconColor: palette.orange,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
title: 'Updater Worker',
|
|
434
|
+
subtitle: 'PRIVATE R2 updates binding',
|
|
435
|
+
detail: 'updates.example.com → electron-updater',
|
|
436
|
+
icon: siCloudflareworkers,
|
|
437
|
+
iconColor: palette.orange,
|
|
438
|
+
secondIcon: siElectron,
|
|
439
|
+
secondIconColor: palette.cyan,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
title: 'Cloudflare DNS + Vercel',
|
|
443
|
+
subtitle: 'app domain: DNS-only → Vercel',
|
|
444
|
+
detail: 'DNSSEC · assets/updates Worker domains',
|
|
445
|
+
icon: siCloudflare,
|
|
446
|
+
iconColor: palette.orange,
|
|
447
|
+
secondIcon: siVercel,
|
|
448
|
+
secondIconColor: palette.text,
|
|
449
|
+
},
|
|
450
|
+
].forEach((item, index) => card(serviceX, serviceRows[index], serviceW, serviceCardHeight, { ...item, compact: true }));
|
|
451
|
+
|
|
452
|
+
// Publish connections are mapped by product surface rather than by row. Expo
|
|
453
|
+
// intentionally fans out to both native stores; every lane meets both card
|
|
454
|
+
// boundaries, even when their vertical centers differ.
|
|
455
|
+
const publishConnections = [
|
|
456
|
+
{ clientIndex: 0, deployIndex: 0 },
|
|
457
|
+
{ clientIndex: 1, deployIndex: 1 },
|
|
458
|
+
{ clientIndex: 1, deployIndex: 2 },
|
|
459
|
+
{ clientIndex: 2, deployIndex: 3 },
|
|
460
|
+
{ clientIndex: 3, deployIndex: 4 },
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
publishConnections.forEach(({ clientIndex, deployIndex }) => {
|
|
464
|
+
smoothConnector(
|
|
465
|
+
clientX,
|
|
466
|
+
clientRows[clientIndex] + 56,
|
|
467
|
+
deployX + deployW,
|
|
468
|
+
deployRows[deployIndex] + 51,
|
|
469
|
+
{
|
|
470
|
+
stroke: palette.yellow,
|
|
471
|
+
dash: '10 8',
|
|
472
|
+
marker: 'arrow-yellow',
|
|
473
|
+
},
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Client-to-API runtime lines.
|
|
478
|
+
const apiTargets = [520, 590, 660, 730];
|
|
479
|
+
clientRows.forEach((row, index) => {
|
|
480
|
+
const sourceY = row + 56;
|
|
481
|
+
const targetY = apiTargets[index];
|
|
482
|
+
smoothConnector(clientX + clientW, sourceY, apiX, targetY);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// API-to-service lines. Billing and realtime remain separate downstream
|
|
486
|
+
// services, and the assets API keeps its own fifth connection.
|
|
487
|
+
const serviceApiSources = [500, 555, 610, 665, 720];
|
|
488
|
+
serviceRows.slice(0, 5).forEach((row, index) => {
|
|
489
|
+
const sourceY = serviceApiSources[index];
|
|
490
|
+
const targetY = row + serviceCardHeight / 2;
|
|
491
|
+
smoothConnector(apiX + apiW, sourceY, serviceX, targetY, { marker: '' });
|
|
492
|
+
endpoint(serviceX, targetY);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// One compact legend; no command, agent, or updater panels below the architecture.
|
|
496
|
+
const legendY = 1050;
|
|
497
|
+
const legendLineY = legendY + 43;
|
|
498
|
+
rect(126, legendY, 1668, 86, { radius: 12 });
|
|
499
|
+
line(246, legendLineY, 350, legendLineY, { stroke: palette.text, width: 3, marker: 'arrow-white' });
|
|
500
|
+
text(374, legendLineY + 7, 'runtime API flow', { size: 17, fill: palette.muted });
|
|
501
|
+
line(720, legendLineY, 824, legendLineY, { stroke: palette.yellow, width: 3, dash: '10 8', marker: 'arrow-yellow' });
|
|
502
|
+
text(848, legendLineY + 7, 'deploy / publish', { size: 17, fill: palette.muted });
|
|
503
|
+
line(1198, legendLineY, 1302, legendLineY, { stroke: palette.text, width: 3 });
|
|
504
|
+
endpoint(1302, legendLineY);
|
|
505
|
+
text(1332, legendLineY + 7, 'backend service connection', { size: 17, fill: palette.muted });
|
|
506
|
+
|
|
507
|
+
svg.push('</svg>');
|
|
508
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
509
|
+
await writeFile(outputPath, `${svg.join('\n')}\n`, 'utf8');
|
|
510
|
+
console.log(`Rendered ${outputPath}`);
|