@laitszkin/apollo-toolkit 3.10.0 → 3.11.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  3. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  4. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  5. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  6. package/generate-spec/SKILL.md +17 -15
  7. package/generate-spec/agents/openai.yaml +1 -1
  8. package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
  9. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  10. package/init-project-html/SKILL.md +117 -125
  11. package/init-project-html/agents/openai.yaml +18 -9
  12. package/init-project-html/lib/atlas/assets/architecture.css +161 -0
  13. package/init-project-html/lib/atlas/assets/viewer.client.js +136 -0
  14. package/init-project-html/lib/atlas/cli.js +1023 -0
  15. package/init-project-html/lib/atlas/layout.js +330 -0
  16. package/init-project-html/lib/atlas/render.js +583 -0
  17. package/init-project-html/lib/atlas/schema.js +347 -0
  18. package/init-project-html/lib/atlas/state.js +402 -0
  19. package/init-project-html/references/TEMPLATE_SPEC.md +140 -83
  20. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +160 -1058
  21. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +136 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +172 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
  26. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +64 -163
  27. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +102 -196
  28. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +82 -163
  29. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +88 -150
  30. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +83 -138
  31. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
  32. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +84 -159
  33. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +81 -143
  34. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +98 -188
  35. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +83 -138
  36. package/init-project-html/sample-demo/resources/project-architecture/index.html +256 -335
  37. package/init-project-html/scripts/architecture.js +65 -247
  38. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  39. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  40. package/package.json +6 -2
  41. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  42. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  43. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  44. package/spec-to-project-html/SKILL.md +74 -67
  45. package/spec-to-project-html/agents/openai.yaml +14 -8
  46. package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -83
  47. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -1,337 +1,258 @@
1
1
  <!DOCTYPE html>
2
- <html lang="zh-Hant">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="generator" content="apollo-toolkit:init-project-html (sample demo)" />
7
- <meta name="architecture-root" content="resources/project-architecture" />
8
- <title>Acme App — 宏觀架構(功能模組 × 子模組)</title>
9
- <link rel="preconnect" href="https://fonts.googleapis.com" />
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
- <link
12
- href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;600&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap"
13
- rel="stylesheet"
14
- />
15
- <link rel="stylesheet" href="assets/architecture.css" />
16
- </head>
17
- <body>
18
- <main class="atlas-page atlas-page--macro">
19
- <header class="atlas-header">
20
- <p class="atlas-kicker">Architecture atlas — macro</p>
21
- <h1 class="atlas-title">Acme App — 功能模組 × 子模組(同一張圖)</h1>
22
- <p class="atlas-meta">
23
- 更新於 <time datetime="2026-05-11T12:00:00Z">2026-05-11(UTC)</time>
24
- · 此頁同時呈現「功能模組之間」與「子模組之間」的多對多交互;子模組頁本身只描述<strong>自身函式 I/O 與內部資料流</strong>。
25
- </p>
26
- </header>
27
-
28
- <section class="atlas-summary">
29
- <h2>為何把兩層放同一張圖</h2>
30
- <p>
31
- 功能模組(對使用者可見的能力)的「宏觀交互」實際上是由其底下的子模組互相呼叫與資料交換達成的。為了讓讀者能在<strong>同一張圖</strong>同時看到「功能 A 功能 B」與「達成這條路徑的具體子模組是誰呼叫誰、誰讀誰寫」,本宏觀圖以兩個功能模組為<strong>群組(cluster)</strong>,群組內畫出子模組節點與彼此的呼叫、回傳與寫讀資料邊。
32
- </p>
33
- <p>
34
- 一個子模組可能<strong>同時是生產者與消費者</strong>。本示範刻意保留:<code>postgresql</code> 的 <code>invite_codes</code> 表在「獲取邀請碼」功能裡被<strong>寫入</strong>,產生的列在「邀請碼註冊」功能裡被<strong>讀取/核銷</strong>;<code>registration-service</code> 對 <code>postgresql</code> 同時是<strong>讀者(SELECT FOR UPDATE)</strong>與<strong>寫者(INSERT user / UPDATE invite consumed)</strong>;<code>invite-issuance-service</code> 把 <code>invite-code-generator</code> 當成多次呼叫的內嵌函式並回收其結果。
35
- </p>
36
- </section>
37
-
38
- <section class="atlas-legend">
39
- <h2>圖例</h2>
40
- <ul>
41
- <li><strong>虛線大方框</strong>:一個<strong>功能模組</strong>(feature module)= 對使用者可見的能力。標題即連到該功能總覽。</li>
42
- <li><strong>實心小方塊</strong>:<strong>子模組</strong>。每一塊點擊進入該子模組頁(內容只談自身函式 I/O 與資料流)。</li>
43
- <li><strong>實線箭頭</strong>:函式呼叫/HTTP 請求(caller callee)。</li>
44
- <li><strong>虛線細箭頭</strong>:回傳值/HTTP 回應(callee caller)。</li>
45
- <li><strong>暖色粗虛線</strong>:跨功能<strong>資料行</strong>級別的生產者→消費者關係(與函式呼叫不同:透過 DB 行傳遞)。</li>
46
- <li><strong>同一對節點可有多條邊</strong>:例如 <code>registration-service</code> 對 <code>postgresql</code> 有 SELECT、INSERT、UPDATE 三條獨立邊。</li>
47
- </ul>
48
- </section>
49
-
50
- <section class="flow-section flow-section--macro">
51
- <h2>宏觀圖:功能模組 × 子模組</h2>
52
- <figure class="flow-chart flow-chart--macro flow-chart--svg" data-flow-id="macro-graph" aria-label="功能模組與子模組多對多交互">
53
- <div class="macro-svg__wrap">
54
- <svg
55
- class="macro-svg"
56
- viewBox="0 0 1200 1020"
57
- role="img"
58
- xmlns="http://www.w3.org/2000/svg"
59
- preserveAspectRatio="xMidYMid meet"
60
- >
61
- <defs>
62
- <marker id="mk-solid" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
63
- <path d="M0,0 L10,5 L0,10 Z" fill="currentColor"></path>
64
- </marker>
65
- <marker id="mk-thin" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
66
- <path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
67
- </marker>
68
- <marker id="mk-cross" markerWidth="11" markerHeight="11" refX="10" refY="5.5" orient="auto">
69
- <path d="M0,0 L11,5.5 L0,11 Z" fill="#e07a5f"></path>
70
- </marker>
71
- </defs>
72
-
73
- <!-- Actors -->
74
- <g class="m-actor" data-node-id="actor-visitor">
75
- <rect x="220" y="40" width="200" height="56" rx="28"></rect>
76
- <text x="320" y="74" text-anchor="middle">訪客(未註冊)</text>
77
- </g>
78
- <g class="m-actor" data-node-id="actor-member">
79
- <rect x="800" y="40" width="200" height="56" rx="28"></rect>
80
- <text x="900" y="74" text-anchor="middle">已註冊/已登入</text>
81
- </g>
82
-
83
- <!-- Feature A cluster: invite-code-registration -->
84
- <g class="m-cluster" data-feature-id="invite-code-registration">
85
- <rect x="60" y="130" width="520" height="850" rx="18" class="m-cluster__rect"></rect>
86
- <a href="features/invite-code-registration/index.html">
87
- <text x="78" y="160" class="m-cluster__title">功能模組 邀請碼註冊</text>
88
- </a>
89
- <text x="78" y="180" class="m-cluster__sub">訪客憑邀請碼建立帳號(消費邀請碼)</text>
90
-
91
- <a href="features/invite-code-registration/web-register-ui.html">
92
- <g class="m-sub" id="m-A1" data-submodule-id="web-register-ui">
93
- <rect x="180" y="210" width="280" height="64" rx="10"></rect>
94
- <text x="320" y="237" text-anchor="middle" class="m-sub__title">web-register-ui</text>
95
- <text x="320" y="256" text-anchor="middle" class="m-sub__kind">React 頁面 · 表單提交</text>
96
- </g>
97
- </a>
98
-
99
- <a href="features/invite-code-registration/public-api.html">
100
- <g class="m-sub" id="m-A2" data-submodule-id="public-api">
101
- <rect x="180" y="320" width="280" height="64" rx="10"></rect>
102
- <text x="320" y="347" text-anchor="middle" class="m-sub__title">public-api(reg)</text>
103
- <text x="320" y="366" text-anchor="middle" class="m-sub__kind">Go HTTP handler</text>
104
- </g>
105
- </a>
106
-
107
- <a href="features/invite-code-registration/registration-service.html">
108
- <g class="m-sub" id="m-A3" data-submodule-id="registration-service">
109
- <rect x="180" y="450" width="280" height="64" rx="10"></rect>
110
- <text x="320" y="477" text-anchor="middle" class="m-sub__title">registration-service</text>
111
- <text x="320" y="496" text-anchor="middle" class="m-sub__kind">交易編排 · 網域規則</text>
112
- </g>
113
- </a>
114
-
115
- <a href="features/invite-code-registration/postgresql.html">
116
- <g class="m-sub m-sub--db" id="m-A4" data-submodule-id="postgresql">
117
- <rect x="180" y="700" width="280" height="84" rx="10"></rect>
118
- <text x="320" y="728" text-anchor="middle" class="m-sub__title">postgresql(reg 視角)</text>
119
- <text x="320" y="748" text-anchor="middle" class="m-sub__kind">表:invite_codes · users</text>
120
- <text x="320" y="768" text-anchor="middle" class="m-sub__kind">作用:讀邀請碼 · 寫 user · 核銷</text>
121
- </g>
122
- </a>
123
- </g>
124
-
125
- <!-- Feature B cluster: get-invite-codes -->
126
- <g class="m-cluster" data-feature-id="get-invite-codes">
127
- <rect x="620" y="130" width="520" height="850" rx="18" class="m-cluster__rect"></rect>
128
- <a href="features/get-invite-codes/index.html">
129
- <text x="638" y="160" class="m-cluster__title">功能模組 ▸ 獲取邀請碼</text>
130
- </a>
131
- <text x="638" y="180" class="m-cluster__sub">已登入使用者請求發放新邀請碼(生產邀請碼)</text>
132
-
133
- <a href="features/get-invite-codes/web-get-invite-ui.html">
134
- <g class="m-sub" id="m-B1" data-submodule-id="web-get-invite-ui">
135
- <rect x="740" y="210" width="280" height="64" rx="10"></rect>
136
- <text x="880" y="237" text-anchor="middle" class="m-sub__title">web-get-invite-ui</text>
137
- <text x="880" y="256" text-anchor="middle" class="m-sub__kind">React 頁面 · 列表 + 生成按鈕</text>
138
- </g>
139
- </a>
140
-
141
- <a href="features/get-invite-codes/public-api.html">
142
- <g class="m-sub" id="m-B2" data-submodule-id="public-api">
143
- <rect x="740" y="320" width="280" height="64" rx="10"></rect>
144
- <text x="880" y="347" text-anchor="middle" class="m-sub__title">public-api(iss)</text>
145
- <text x="880" y="366" text-anchor="middle" class="m-sub__kind">Go HTTP handler</text>
146
- </g>
147
- </a>
148
-
149
- <a href="features/get-invite-codes/invite-issuance-service.html">
150
- <g class="m-sub" id="m-B3" data-submodule-id="invite-issuance-service">
151
- <rect x="740" y="450" width="280" height="64" rx="10"></rect>
152
- <text x="880" y="477" text-anchor="middle" class="m-sub__title">invite-issuance-service</text>
153
- <text x="880" y="496" text-anchor="middle" class="m-sub__kind">額度 · 交易 · 重試</text>
154
- </g>
155
- </a>
156
-
157
- <a href="features/get-invite-codes/invite-code-generator.html">
158
- <g class="m-sub" id="m-B4" data-submodule-id="invite-code-generator">
159
- <rect x="740" y="570" width="280" height="64" rx="10"></rect>
160
- <text x="880" y="597" text-anchor="middle" class="m-sub__title">invite-code-generator</text>
161
- <text x="880" y="616" text-anchor="middle" class="m-sub__kind">純函式 · crypto/rand → 字串</text>
162
- </g>
163
- </a>
164
-
165
- <a href="features/get-invite-codes/postgresql.html">
166
- <g class="m-sub m-sub--db" id="m-B5" data-submodule-id="postgresql">
167
- <rect x="740" y="700" width="280" height="84" rx="10"></rect>
168
- <text x="880" y="728" text-anchor="middle" class="m-sub__title">postgresql(iss 視角)</text>
169
- <text x="880" y="748" text-anchor="middle" class="m-sub__kind">表:invite_codes</text>
170
- <text x="880" y="768" text-anchor="middle" class="m-sub__kind">作用:INSERT 新行(屬於 owner)</text>
171
- </g>
172
- </a>
173
- </g>
174
-
175
- <!-- ============ INTRA-FEATURE A EDGES ============ -->
176
- <!-- visitor -> A1 -->
177
- <g class="m-edge m-edge--call" data-edge-id="e-A-1">
178
- <path d="M 320 96 L 320 206" marker-end="url(#mk-solid)"></path>
179
- <text x="332" y="160" class="m-edge__label">提交註冊表單</text>
180
- </g>
181
- <!-- A1 -> A2 -->
182
- <g class="m-edge m-edge--call" data-edge-id="e-A-2">
183
- <path d="M 320 274 L 320 316" marker-end="url(#mk-solid)"></path>
184
- <text x="332" y="300" class="m-edge__label">HTTP POST /register · {email, password, inviteCode}</text>
185
- </g>
186
- <!-- A2 -> A3 -->
187
- <g class="m-edge m-edge--call" data-edge-id="e-A-3">
188
- <path d="M 320 384 L 320 446" marker-end="url(#mk-solid)"></path>
189
- <text x="332" y="420" class="m-edge__label">RegisterWithInvite(ctx, RegisterInput)</text>
190
- </g>
191
- <!-- A3 -> A4 (read invite) on RIGHT side -->
192
- <g class="m-edge m-edge--call" data-edge-id="e-A-4-read">
193
- <path d="M 415 514 C 510 540, 510 670, 415 700" marker-end="url(#mk-solid)"></path>
194
- <text x="520" y="610" class="m-edge__label">SELECT … FOR UPDATE (code)</text>
195
- </g>
196
- <!-- A4 -> A3 (return InviteRow) on right inner -->
197
- <g class="m-edge m-edge--return" data-edge-id="e-A-4-row">
198
- <path d="M 360 700 C 360 600, 360 580, 360 514" marker-end="url(#mk-thin)"></path>
199
- <text x="370" y="600" class="m-edge__label">⇠ InviteRow | NotFound</text>
200
- </g>
201
- <!-- A3 -> A4 (insert user) on LEFT -->
202
- <g class="m-edge m-edge--call" data-edge-id="e-A-4-insert-user">
203
- <path d="M 225 514 C 130 560, 130 660, 225 700" marker-end="url(#mk-solid)"></path>
204
- <text x="78" y="600" class="m-edge__label">INSERT users(...)</text>
205
- </g>
206
- <!-- A3 -> A4 (update invite consumed) center-left -->
207
- <g class="m-edge m-edge--call" data-edge-id="e-A-4-update">
208
- <path d="M 280 514 C 240 560, 240 660, 280 700" marker-end="url(#mk-solid)"></path>
209
- <text x="158" y="660" class="m-edge__label">UPDATE invite_codes SET consumed_at=now()</text>
210
- </g>
211
- <!-- A2 -> visitor (response) -->
212
- <g class="m-edge m-edge--return" data-edge-id="e-A-resp">
213
- <path d="M 270 320 C 180 220, 180 130, 245 96" marker-end="url(#mk-thin)"></path>
214
- <text x="72" y="220" class="m-edge__label">⇠ 201 / 409</text>
215
- </g>
216
-
217
- <!-- ============ INTRA-FEATURE B EDGES ============ -->
218
- <!-- member -> B1 -->
219
- <g class="m-edge m-edge--call" data-edge-id="e-B-1">
220
- <path d="M 880 96 L 880 206" marker-end="url(#mk-solid)"></path>
221
- <text x="892" y="160" class="m-edge__label">點擊「產生邀請碼」</text>
222
- </g>
223
- <!-- B1 -> B2 -->
224
- <g class="m-edge m-edge--call" data-edge-id="e-B-2">
225
- <path d="M 880 274 L 880 316" marker-end="url(#mk-solid)"></path>
226
- <text x="892" y="300" class="m-edge__label">HTTP POST /me/invite-codes</text>
227
- </g>
228
- <!-- B2 -> B3 -->
229
- <g class="m-edge m-edge--call" data-edge-id="e-B-3">
230
- <path d="M 880 384 L 880 446" marker-end="url(#mk-solid)"></path>
231
- <text x="892" y="420" class="m-edge__label">IssueInviteCode(ctx, userID)</text>
232
- </g>
233
- <!-- B3 -> B4 (call Next) on LEFT inner -->
234
- <g class="m-edge m-edge--call" data-edge-id="e-B-3-gen-call">
235
- <path d="M 820 514 C 770 540, 770 560, 820 566" marker-end="url(#mk-solid)"></path>
236
- <text x="650" y="555" class="m-edge__label">Next()</text>
237
- </g>
238
- <!-- B4 -> B3 (return candidate) on RIGHT inner -->
239
- <g class="m-edge m-edge--return" data-edge-id="e-B-3-gen-ret">
240
- <path d="M 950 570 C 1010 560, 1010 540, 950 514" marker-end="url(#mk-thin)"></path>
241
- <text x="1020" y="555" class="m-edge__label">⇠ 候選字串</text>
242
- </g>
243
- <!-- B3 -> B5 (INSERT) -->
244
- <g class="m-edge m-edge--call" data-edge-id="e-B-5-insert">
245
- <path d="M 860 514 C 860 600, 860 660, 860 700" marker-end="url(#mk-solid)"></path>
246
- <text x="872" y="610" class="m-edge__label">INSERT invite_codes(owner=userID, code)</text>
247
- </g>
248
- <!-- B5 -> B3 (return ok / unique_violation) -->
249
- <g class="m-edge m-edge--return" data-edge-id="e-B-5-ret">
250
- <path d="M 920 700 C 980 660, 980 560, 920 514" marker-end="url(#mk-thin)"></path>
251
- <text x="990" y="610" class="m-edge__label">⇠ ok | unique_violation</text>
252
- </g>
253
- <!-- B2 -> member (response) -->
254
- <g class="m-edge m-edge--return" data-edge-id="e-B-resp">
255
- <path d="M 940 320 C 1030 220, 1030 130, 965 96" marker-end="url(#mk-thin)"></path>
256
- <text x="1040" y="220" class="m-edge__label">⇠ 201 / 429</text>
257
- </g>
258
-
259
- <!-- ============ CROSS-FEATURE DATA-ROW EDGE ============ -->
260
- <!-- B5 produces row → A4 consumes row -->
261
- <g class="m-edge m-edge--cross" data-edge-id="x-row-flow">
262
- <path
263
- d="M 740 742 C 600 880, 600 880, 460 742"
264
- marker-end="url(#mk-cross)"
265
- ></path>
266
- <text x="600" y="900" text-anchor="middle" class="m-edge__label m-edge__label--cross">
267
- 資料行流向:B5 寫入 <tspan font-style="italic">invite_codes</tspan> 行 → A4 之後讀取/核銷該行(非函式呼叫)
268
- </text>
269
- </g>
270
- </svg>
271
- </div>
272
-
273
- <ol class="flow-edge-manifest flow-edge-manifest--macro">
274
- <li data-edge-id="e-A-1" data-edge-kind="call"><span class="flow-edge-from">#actor-visitor</span> → <span class="flow-edge-to">#m-A1</span> <span class="flow-edge-label">提交註冊</span></li>
275
- <li data-edge-id="e-A-2" data-edge-kind="call"><span class="flow-edge-from">#m-A1</span> → <span class="flow-edge-to">#m-A2</span> <span class="flow-edge-label">HTTP POST</span></li>
276
- <li data-edge-id="e-A-3" data-edge-kind="call"><span class="flow-edge-from">#m-A2</span> → <span class="flow-edge-to">#m-A3</span> <span class="flow-edge-label">RegisterWithInvite(ctx, in)</span></li>
277
- <li data-edge-id="e-A-4-read" data-edge-kind="call"><span class="flow-edge-from">#m-A3</span> → <span class="flow-edge-to">#m-A4</span> <span class="flow-edge-label">SELECT FOR UPDATE invite_codes</span></li>
278
- <li data-edge-id="e-A-4-row" data-edge-kind="return"><span class="flow-edge-from">#m-A4</span> ⇠ <span class="flow-edge-to">#m-A3</span> <span class="flow-edge-label">InviteRow | NotFound</span></li>
279
- <li data-edge-id="e-A-4-insert-user" data-edge-kind="call"><span class="flow-edge-from">#m-A3</span> → <span class="flow-edge-to">#m-A4</span> <span class="flow-edge-label">INSERT users</span></li>
280
- <li data-edge-id="e-A-4-update" data-edge-kind="call"><span class="flow-edge-from">#m-A3</span> → <span class="flow-edge-to">#m-A4</span> <span class="flow-edge-label">UPDATE invite_codes SET consumed_at</span></li>
281
- <li data-edge-id="e-A-resp" data-edge-kind="return"><span class="flow-edge-from">#m-A2</span> ⇠ <span class="flow-edge-to">#actor-visitor</span> <span class="flow-edge-label">201 / 409</span></li>
282
-
283
- <li data-edge-id="e-B-1" data-edge-kind="call"><span class="flow-edge-from">#actor-member</span> → <span class="flow-edge-to">#m-B1</span> <span class="flow-edge-label">點生成</span></li>
284
- <li data-edge-id="e-B-2" data-edge-kind="call"><span class="flow-edge-from">#m-B1</span> → <span class="flow-edge-to">#m-B2</span> <span class="flow-edge-label">HTTP POST</span></li>
285
- <li data-edge-id="e-B-3" data-edge-kind="call"><span class="flow-edge-from">#m-B2</span> → <span class="flow-edge-to">#m-B3</span> <span class="flow-edge-label">IssueInviteCode(ctx, userID)</span></li>
286
- <li data-edge-id="e-B-3-gen-call" data-edge-kind="call"><span class="flow-edge-from">#m-B3</span> → <span class="flow-edge-to">#m-B4</span> <span class="flow-edge-label">Next() · 純函式</span></li>
287
- <li data-edge-id="e-B-3-gen-ret" data-edge-kind="return"><span class="flow-edge-from">#m-B4</span> ⇠ <span class="flow-edge-to">#m-B3</span> <span class="flow-edge-label">候選字串</span></li>
288
- <li data-edge-id="e-B-5-insert" data-edge-kind="call"><span class="flow-edge-from">#m-B3</span> → <span class="flow-edge-to">#m-B5</span> <span class="flow-edge-label">INSERT invite_codes</span></li>
289
- <li data-edge-id="e-B-5-ret" data-edge-kind="return"><span class="flow-edge-from">#m-B5</span> ⇠ <span class="flow-edge-to">#m-B3</span> <span class="flow-edge-label">ok / unique_violation(碰撞 → 重新 Next)</span></li>
290
- <li data-edge-id="e-B-resp" data-edge-kind="return"><span class="flow-edge-from">#m-B2</span> ⇠ <span class="flow-edge-to">#actor-member</span> <span class="flow-edge-label">201 / 429</span></li>
291
-
292
- <li data-edge-id="x-row-flow" data-edge-kind="data-row"><span class="flow-edge-from">#m-B5</span> ⇒ <span class="flow-edge-to">#m-A4</span> <span class="flow-edge-label">同一 invite_codes 表:B 寫入行被 A 讀取/核銷(非函式呼叫,純資料行傳遞)</span></li>
293
- </ol>
294
- <figcaption class="flow-caption">
295
- 「同一對節點 N 條邊」是真實的(例如 A3 → A4 同時有 SELECT、INSERT、UPDATE 三條;B3 ↔ B4、B3 ↔ B5 皆有呼叫/回傳對)。子模組頁不重述這些跨界邊;它們只在此處統一呈現。
296
- </figcaption>
297
- </figure>
298
- </section>
299
-
300
- <nav class="atlas-submodule-index">
301
- <h2>子模組索引(每一個子模組各一頁)</h2>
302
- <ul>
303
- <li>
304
- <strong>邀請碼註冊 ▸</strong>
305
- <a href="features/invite-code-registration/web-register-ui.html">web-register-ui</a> ·
306
- <a href="features/invite-code-registration/public-api.html">public-api</a> ·
307
- <a href="features/invite-code-registration/registration-service.html">registration-service</a> ·
308
- <a href="features/invite-code-registration/postgresql.html">postgresql</a>
309
- </li>
310
- <li>
311
- <strong>獲取邀請碼 ▸</strong>
312
- <a href="features/get-invite-codes/web-get-invite-ui.html">web-get-invite-ui</a> ·
313
- <a href="features/get-invite-codes/public-api.html">public-api</a> ·
314
- <a href="features/get-invite-codes/invite-issuance-service.html">invite-issuance-service</a> ·
315
- <a href="features/get-invite-codes/invite-code-generator.html">invite-code-generator</a> ·
316
- <a href="features/get-invite-codes/postgresql.html">postgresql</a>
317
- </li>
318
- </ul>
319
- <p class="atlas-meta">
320
- 每一頁只描述<strong>該子模組自身的函式輸入/輸出/副作用</strong>與<strong>內部資料流</strong>;跨子模組的呼叫關係請回到上方宏觀圖。
321
- </p>
322
- </nav>
323
-
324
- <nav class="atlas-features">
325
- <h2>功能模組總覽(中層)</h2>
326
- <ul>
327
- <li><a href="features/invite-code-registration/index.html">邀請碼註冊(功能總覽)</a></li>
328
- <li><a href="features/get-invite-codes/index.html">獲取邀請碼(功能總覽)</a></li>
329
- </ul>
330
- </nav>
331
-
332
- <footer class="atlas-meta" style="margin-top: 2rem">
333
- <p>示範:<code>init-project-html/sample-demo/resources/project-architecture/</code></p>
334
- </footer>
335
- </main>
336
- </body>
2
+ <html lang="en" data-atlas-page="macro">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Acme App sample atlas</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link rel="stylesheet" href="assets/architecture.css">
8
+ </head>
9
+ <body>
10
+ <header class="atlas-header">
11
+ <h1>Acme App — sample atlas</h1>
12
+ <p class="atlas-summary">Two end-to-end feature modules — minting invite codes and consuming them during account registration — produced by `apltk architecture` from a declarative YAML source. The macro diagram surfaces both feature clusters and every sub-module they own, plus the cross-feature data-row that carries an `invite_codes` row between them.</p>
13
+ </header>
14
+ <main class="atlas-main">
15
+ <section class="atlas-canvas" aria-label="Macro architecture diagram">
16
+ <div class="atlas-canvas__toolbar" role="toolbar" aria-label="Diagram controls">
17
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
18
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
19
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
20
+ </div>
21
+ <div class="atlas-canvas__viewport" data-pan-zoom-viewport>
22
+ <svg class="atlas-svg" viewBox="0 0 3831 521" role="img" aria-label="Project architecture atlas" data-atlas-svg="macro">
23
+ <defs>
24
+ <marker id="arrow-call" class="m-arrow m-arrow--call" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
25
+ <marker id="arrow-return" class="m-arrow m-arrow--return" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
26
+ <marker id="arrow-data-row" class="m-arrow m-arrow--data-row" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
27
+ <marker id="arrow-failure" class="m-arrow m-arrow--failure" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
28
+ </defs>
29
+ <g transform="translate(24,24)">
30
+ <g class="m-cluster" data-feature="get-invite-codes">
31
+ <rect class="m-cluster__bg" x="40.00" y="40.00" width="1576.00" height="393.00" rx="14" ry="14" />
32
+ <text class="m-cluster__title" x="828.00" y="66.00" text-anchor="middle">Get invite codes</text>
33
+ </g>
34
+ <g class="m-cluster" data-feature="invite-code-registration">
35
+ <rect class="m-cluster__bg" x="2017.00" y="133.00" width="1726.00" height="266.40" rx="14" ry="14" />
36
+ <text class="m-cluster__title" x="2880.00" y="159.00" text-anchor="middle">Invite-code registration</text>
37
+ </g>
38
+ <a class="m-node m-node--ui" href="features/get-invite-codes/web-get-invite-ui.html" data-feature="get-invite-codes" data-submodule="web-get-invite-ui" tabindex="0" aria-label="web-get-invite-ui — React page that lets a signed-in member request a new invite code. — open sub-module page">
39
+ <title>web-get-invite-ui — React page that lets a signed-in member request a new invite code.</title>
40
+ <rect x="64.00" y="166.80" width="220.00" height="126.00" rx="10" ry="10" />
41
+ <text class="m-node__title" x="174.00" y="196.80" text-anchor="middle">web-get-invite-ui</text>
42
+ <text class="m-node__kind" x="174.00" y="212.80" text-anchor="middle">UI</text>
43
+ <text class="m-node__role" x="174.00" y="232.80" text-anchor="middle">React page that lets a</text>
44
+ <text class="m-node__role" x="174.00" y="248.80" text-anchor="middle">signed-in member request a new</text>
45
+ <text class="m-node__role" x="174.00" y="264.80" text-anchor="middle">invite code.</text>
46
+ </a>
47
+ <a class="m-node m-node--api" href="features/get-invite-codes/public-api.html" data-feature="get-invite-codes" data-submodule="public-api" tabindex="0" aria-label="public-api — HTTP boundary for `/api/invites` POST requests. — open sub-module page">
48
+ <title>public-api — HTTP boundary for `/api/invites` POST requests.</title>
49
+ <rect x="491.00" y="174.80" width="220.00" height="110.00" rx="10" ry="10" />
50
+ <text class="m-node__title" x="601.00" y="204.80" text-anchor="middle">public-api</text>
51
+ <text class="m-node__kind" x="601.00" y="220.80" text-anchor="middle">API</text>
52
+ <text class="m-node__role" x="601.00" y="240.80" text-anchor="middle">HTTP boundary for</text>
53
+ <text class="m-node__role" x="601.00" y="256.80" text-anchor="middle">`/api/invites` POST requests.</text>
54
+ </a>
55
+ <a class="m-node m-node--service" href="features/get-invite-codes/invite-issuance-service.html" data-feature="get-invite-codes" data-submodule="invite-issuance-service" tabindex="0" aria-label="invite-issuance-service — Domain service that mints and persists a single invite row per request. — open sub-module page">
56
+ <title>invite-issuance-service Domain service that mints and persists a single invite row per request.</title>
57
+ <rect x="925.00" y="166.80" width="226.00" height="126.00" rx="10" ry="10" />
58
+ <text class="m-node__title" x="1038.00" y="196.80" text-anchor="middle">invite-issuance-service</text>
59
+ <text class="m-node__kind" x="1038.00" y="212.80" text-anchor="middle">service</text>
60
+ <text class="m-node__role" x="1038.00" y="232.80" text-anchor="middle">Domain service that mints and</text>
61
+ <text class="m-node__role" x="1038.00" y="248.80" text-anchor="middle">persists a single invite row per</text>
62
+ <text class="m-node__role" x="1038.00" y="264.80" text-anchor="middle">request.</text>
63
+ </a>
64
+ <a class="m-node m-node--pure-fn" href="features/get-invite-codes/invite-code-generator.html" data-feature="get-invite-codes" data-submodule="invite-code-generator" tabindex="0" aria-label="invite-code-generator — Pure helper that turns random bytes into a printable invite code. — open sub-module page">
65
+ <title>invite-code-generator Pure helper that turns random bytes into a printable invite code.</title>
66
+ <rect x="1372.00" y="129.00" width="220.00" height="126.00" rx="10" ry="10" />
67
+ <text class="m-node__title" x="1482.00" y="159.00" text-anchor="middle">invite-code-generator</text>
68
+ <text class="m-node__kind" x="1482.00" y="175.00" text-anchor="middle">pure function</text>
69
+ <text class="m-node__role" x="1482.00" y="195.00" text-anchor="middle">Pure helper that turns random</text>
70
+ <text class="m-node__role" x="1482.00" y="211.00" text-anchor="middle">bytes into a printable invite</text>
71
+ <text class="m-node__role" x="1482.00" y="227.00" text-anchor="middle">code.</text>
72
+ </a>
73
+ <a class="m-node m-node--db" href="features/get-invite-codes/postgresql.html" data-feature="get-invite-codes" data-submodule="postgresql" tabindex="0" aria-label="postgresql — Owns the `invite_codes` table (producer side of the cross-feature data row). — open sub-module page">
74
+ <title>postgresql — Owns the `invite_codes` table (producer side of the cross-feature data row).</title>
75
+ <rect x="1372.00" y="279.00" width="220.00" height="126.00" rx="10" ry="10" />
76
+ <text class="m-node__title" x="1482.00" y="309.00" text-anchor="middle">postgresql</text>
77
+ <text class="m-node__kind" x="1482.00" y="325.00" text-anchor="middle">database</text>
78
+ <text class="m-node__role" x="1482.00" y="345.00" text-anchor="middle">Owns the `invite_codes` table</text>
79
+ <text class="m-node__role" x="1482.00" y="361.00" text-anchor="middle">(producer side of the</text>
80
+ <text class="m-node__role" x="1482.00" y="377.00" text-anchor="middle">cross-feature data row).</text>
81
+ </a>
82
+ <a class="m-node m-node--ui" href="features/invite-code-registration/web-register-ui.html" data-feature="invite-code-registration" data-submodule="web-register-ui" tabindex="0" aria-label="web-register-ui — React page that captures email, password, and invite code. — open sub-module page">
83
+ <title>web-register-ui React page that captures email, password, and invite code.</title>
84
+ <rect x="2041.00" y="222.00" width="220.00" height="110.00" rx="10" ry="10" />
85
+ <text class="m-node__title" x="2151.00" y="252.00" text-anchor="middle">web-register-ui</text>
86
+ <text class="m-node__kind" x="2151.00" y="268.00" text-anchor="middle">UI</text>
87
+ <text class="m-node__role" x="2151.00" y="288.00" text-anchor="middle">React page that captures email,</text>
88
+ <text class="m-node__role" x="2151.00" y="304.00" text-anchor="middle">password, and invite code.</text>
89
+ </a>
90
+ <a class="m-node m-node--api" href="features/invite-code-registration/public-api.html" data-feature="invite-code-registration" data-submodule="public-api" tabindex="0" aria-label="public-api — HTTP boundary for `/api/register` POST requests. — open sub-module page">
91
+ <title>public-api — HTTP boundary for `/api/register` POST requests.</title>
92
+ <rect x="2475.00" y="222.00" width="220.00" height="110.00" rx="10" ry="10" />
93
+ <text class="m-node__title" x="2585.00" y="252.00" text-anchor="middle">public-api</text>
94
+ <text class="m-node__kind" x="2585.00" y="268.00" text-anchor="middle">API</text>
95
+ <text class="m-node__role" x="2585.00" y="288.00" text-anchor="middle">HTTP boundary for</text>
96
+ <text class="m-node__role" x="2585.00" y="304.00" text-anchor="middle">`/api/register` POST requests.</text>
97
+ </a>
98
+ <a class="m-node m-node--service" href="features/invite-code-registration/registration-service.html" data-feature="invite-code-registration" data-submodule="registration-service" tabindex="0" aria-label="registration-service — Owns the registration transaction (consumer side of the invite_codes data row). — open sub-module page">
99
+ <title>registration-service — Owns the registration transaction (consumer side of the invite_codes data row).</title>
100
+ <rect x="2987.00" y="235.00" width="220.00" height="126.00" rx="10" ry="10" />
101
+ <text class="m-node__title" x="3097.00" y="265.00" text-anchor="middle">registration-service</text>
102
+ <text class="m-node__kind" x="3097.00" y="281.00" text-anchor="middle">service</text>
103
+ <text class="m-node__role" x="3097.00" y="301.00" text-anchor="middle">Owns the registration</text>
104
+ <text class="m-node__role" x="3097.00" y="317.00" text-anchor="middle">transaction (consumer side of</text>
105
+ <text class="m-node__role" x="3097.00" y="333.00" text-anchor="middle">the invite_codes data row).</text>
106
+ </a>
107
+ <a class="m-node m-node--db" href="features/invite-code-registration/postgresql.html" data-feature="invite-code-registration" data-submodule="postgresql" tabindex="0" aria-label="postgresql — Stores `users` rows and applies invite-code state transitions inside the registration tx. — open sub-module page">
108
+ <title>postgresql Stores `users` rows and applies invite-code state transitions inside the registration tx.</title>
109
+ <rect x="3499.00" y="228.20" width="220.00" height="126.00" rx="10" ry="10" />
110
+ <text class="m-node__title" x="3609.00" y="258.20" text-anchor="middle">postgresql</text>
111
+ <text class="m-node__kind" x="3609.00" y="274.20" text-anchor="middle">database</text>
112
+ <text class="m-node__role" x="3609.00" y="294.20" text-anchor="middle">Stores `users` rows and applies</text>
113
+ <text class="m-node__role" x="3609.00" y="310.20" text-anchor="middle">invite-code state transitions</text>
114
+ <text class="m-node__role" x="3609.00" y="326.20" text-anchor="middle">inside the registration tx.</text>
115
+ </a>
116
+ <g class="m-edge m-edge--data-row" data-edge="cross-issuance-to-postgres-codes">
117
+ <path d="M1111.00,227.60 L1121.00,227.60 L1121.00,334.00 L1306.00,334.00 L1306.00,333.50 L1332.00,333.50" fill="none" marker-end="url(#arrow-data-row)" />
118
+ <text class="m-edge__label" x="1221.50" y="350.00" text-anchor="middle">INSERT invite_codes</text>
119
+ </g>
120
+ <g class="m-edge m-edge--data-row" data-edge="cross-postgres-codes-to-registration">
121
+ <path d="M1592.00,342.00 L2961.00,342.00 L2961.00,319.00 L2987.00,319.00" fill="none" marker-end="url(#arrow-data-row)" />
122
+ <text class="m-edge__label" x="1816.50" y="358.00" text-anchor="middle">read/consume invite_codes</text>
123
+ </g>
124
+ <g class="m-edge m-edge--call" data-edge="e-ui-api">
125
+ <path d="M284.00,229.80 L491.00,229.80" fill="none" marker-end="url(#arrow-call)" />
126
+ <text class="m-edge__label" x="387.50" y="245.80" text-anchor="middle">POST /api/invites</text>
127
+ </g>
128
+ <g class="m-edge m-edge--call" data-edge="e-api-svc">
129
+ <path d="M711.00,229.80 L925.00,229.80" fill="none" marker-end="url(#arrow-call)" />
130
+ <text class="m-edge__label" x="818.00" y="245.80" text-anchor="middle">Issue(ctx, userId)</text>
131
+ </g>
132
+ <g class="m-edge m-edge--call" data-edge="e-svc-gen">
133
+ <path d="M1151.00,192.00 L1372.00,192.00" fill="none" marker-end="url(#arrow-call)" />
134
+ <text class="m-edge__label" x="1261.50" y="208.00" text-anchor="middle">encode(rand)</text>
135
+ </g>
136
+ <g class="m-edge m-edge--call" data-edge="e-svc-db">
137
+ <path d="M1151.00,242.40 L1171.00,242.40 L1171.00,342.00 L1372.00,342.00" fill="none" marker-end="url(#arrow-call)" />
138
+ <text class="m-edge__label" x="1261.50" y="358.00" text-anchor="middle">INSERT invite_codes</text>
139
+ </g>
140
+ <g class="m-edge m-edge--return" data-edge="e-db-svc-return">
141
+ <path d="M1372.00,310.50 L1346.00,310.50 L1346.00,224.00 L1161.00,224.00 L1161.00,217.20 L1151.00,217.20" fill="none" marker-end="url(#arrow-return)" />
142
+ <text class="m-edge__label" x="1261.50" y="240.00" text-anchor="middle">rowid</text>
143
+ </g>
144
+ <g class="m-edge m-edge--call" data-edge="e-ui-api">
145
+ <path d="M2261.00,277.00 L2475.00,277.00" fill="none" marker-end="url(#arrow-call)" />
146
+ <text class="m-edge__label" x="2368.00" y="269.00" text-anchor="middle">POST /api/register</text>
147
+ </g>
148
+ <g class="m-edge m-edge--call" data-edge="e-api-svc">
149
+ <path d="M2695.00,277.00 L2987.00,277.00" fill="none" marker-end="url(#arrow-call)" />
150
+ <text class="m-edge__label" x="2841.00" y="269.00" text-anchor="middle">RegisterWithInvite(ctx, RegisterInput)</text>
151
+ </g>
152
+ <g class="m-edge m-edge--call" data-edge="e-svc-db-select">
153
+ <path d="M3207.00,285.40 L3473.00,285.40 L3473.00,278.60 L3499.00,278.60" fill="none" marker-end="url(#arrow-call)" />
154
+ <text class="m-edge__label" x="3353.00" y="301.40" text-anchor="middle">SELECT invite_codes FOR UPDATE</text>
155
+ </g>
156
+ <g class="m-edge m-edge--call" data-edge="e-svc-db-insert">
157
+ <path d="M3207.00,310.60 L3217.00,310.60 L3217.00,317.40 L3473.00,317.40 L3473.00,303.80 L3499.00,303.80" fill="none" marker-end="url(#arrow-call)" />
158
+ <text class="m-edge__label" x="3353.00" y="333.40" text-anchor="middle">INSERT users</text>
159
+ </g>
160
+ <g class="m-edge m-edge--call" data-edge="e-svc-db-update">
161
+ <path d="M3207.00,335.80 L3217.00,335.80 L3217.00,349.40 L3473.00,349.40 L3473.00,329.00 L3499.00,329.00" fill="none" marker-end="url(#arrow-call)" />
162
+ <text class="m-edge__label" x="3353.00" y="365.40" text-anchor="middle">UPDATE invite_codes.consumed_at</text>
163
+ </g>
164
+ <g class="m-edge m-edge--return" data-edge="e-db-svc-return">
165
+ <path d="M3499.00,253.40 L3217.00,253.40 L3217.00,260.20 L3207.00,260.20" fill="none" marker-end="url(#arrow-return)" />
166
+ <text class="m-edge__label" x="3353.00" y="269.40" text-anchor="middle">row | rows_affected</text>
167
+ </g>
168
+ </g>
169
+ </svg>
170
+ </div>
171
+ <ol class="atlas-legend" aria-label="Edge legend">
172
+ <li><span class="legend-swatch legend-swatch--call"></span>call</li>
173
+ <li><span class="legend-swatch legend-swatch--return"></span>return</li>
174
+ <li><span class="legend-swatch legend-swatch--data-row"></span>data-row</li>
175
+ <li><span class="legend-swatch legend-swatch--failure"></span>failure</li>
176
+ </ol>
177
+ </section>
178
+ <section class="atlas-index" aria-label="Submodule index">
179
+ <h2>Submodule index</h2>
180
+ <ul class="atlas-submodule-index">
181
+ <li class="atlas-submodule-index__item">
182
+ <a href="features/get-invite-codes/web-get-invite-ui.html">
183
+ <span class="atlas-submodule-index__feature">Get invite codes</span>
184
+ <span class="atlas-submodule-index__sub">web-get-invite-ui</span>
185
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--ui">UI</span>
186
+ </a>
187
+ <p class="atlas-submodule-index__role">React page that lets a signed-in member request a new invite code.</p>
188
+ </li>
189
+ <li class="atlas-submodule-index__item">
190
+ <a href="features/get-invite-codes/public-api.html">
191
+ <span class="atlas-submodule-index__feature">Get invite codes</span>
192
+ <span class="atlas-submodule-index__sub">public-api</span>
193
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--api">API</span>
194
+ </a>
195
+ <p class="atlas-submodule-index__role">HTTP boundary for `/api/invites` POST requests.</p>
196
+ </li>
197
+ <li class="atlas-submodule-index__item">
198
+ <a href="features/get-invite-codes/invite-issuance-service.html">
199
+ <span class="atlas-submodule-index__feature">Get invite codes</span>
200
+ <span class="atlas-submodule-index__sub">invite-issuance-service</span>
201
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--service">Service</span>
202
+ </a>
203
+ <p class="atlas-submodule-index__role">Domain service that mints and persists a single invite row per request.</p>
204
+ </li>
205
+ <li class="atlas-submodule-index__item">
206
+ <a href="features/get-invite-codes/invite-code-generator.html">
207
+ <span class="atlas-submodule-index__feature">Get invite codes</span>
208
+ <span class="atlas-submodule-index__sub">invite-code-generator</span>
209
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--pure-fn">Pure fn</span>
210
+ </a>
211
+ <p class="atlas-submodule-index__role">Pure helper that turns random bytes into a printable invite code.</p>
212
+ </li>
213
+ <li class="atlas-submodule-index__item">
214
+ <a href="features/get-invite-codes/postgresql.html">
215
+ <span class="atlas-submodule-index__feature">Get invite codes</span>
216
+ <span class="atlas-submodule-index__sub">postgresql</span>
217
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--db">DB</span>
218
+ </a>
219
+ <p class="atlas-submodule-index__role">Owns the `invite_codes` table (producer side of the cross-feature data row).</p>
220
+ </li>
221
+ <li class="atlas-submodule-index__item">
222
+ <a href="features/invite-code-registration/web-register-ui.html">
223
+ <span class="atlas-submodule-index__feature">Invite-code registration</span>
224
+ <span class="atlas-submodule-index__sub">web-register-ui</span>
225
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--ui">UI</span>
226
+ </a>
227
+ <p class="atlas-submodule-index__role">React page that captures email, password, and invite code.</p>
228
+ </li>
229
+ <li class="atlas-submodule-index__item">
230
+ <a href="features/invite-code-registration/public-api.html">
231
+ <span class="atlas-submodule-index__feature">Invite-code registration</span>
232
+ <span class="atlas-submodule-index__sub">public-api</span>
233
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--api">API</span>
234
+ </a>
235
+ <p class="atlas-submodule-index__role">HTTP boundary for `/api/register` POST requests.</p>
236
+ </li>
237
+ <li class="atlas-submodule-index__item">
238
+ <a href="features/invite-code-registration/registration-service.html">
239
+ <span class="atlas-submodule-index__feature">Invite-code registration</span>
240
+ <span class="atlas-submodule-index__sub">registration-service</span>
241
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--service">Service</span>
242
+ </a>
243
+ <p class="atlas-submodule-index__role">Owns the registration transaction (consumer side of the invite_codes data row).</p>
244
+ </li>
245
+ <li class="atlas-submodule-index__item">
246
+ <a href="features/invite-code-registration/postgresql.html">
247
+ <span class="atlas-submodule-index__feature">Invite-code registration</span>
248
+ <span class="atlas-submodule-index__sub">postgresql</span>
249
+ <span class="atlas-submodule-index__kind atlas-submodule-index__kind--db">DB</span>
250
+ </a>
251
+ <p class="atlas-submodule-index__role">Stores `users` rows and applies invite-code state transitions inside the registration tx.</p>
252
+ </li>
253
+ </ul>
254
+ </section>
255
+ </main>
256
+ <script src="assets/viewer.client.js" defer></script>
257
+ </body>
337
258
  </html>