@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,190 +1,100 @@
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
- <title>子模組 · registration-service — 邀請碼註冊</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;600&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap"
11
- rel="stylesheet"
12
- />
13
- <link rel="stylesheet" href="../../assets/architecture.css" />
14
- </head>
15
- <body>
16
- <main class="atlas-page atlas-page--feature">
17
- <nav class="breadcrumb" aria-label="階層導航">
18
- <a href="../../index.html">宏觀架構</a> · <a href="./index.html">邀請碼註冊</a> ·
19
- <span>registration-service</span>
20
- </nav>
21
-
22
- <header class="atlas-header">
23
- <p class="atlas-kicker">Submodule · registration-service</p>
24
- <h1 class="atlas-title">registration-service</h1>
25
- <p class="atlas-meta">
26
- 自身職責:在<strong>單一交易</strong>內以邀請碼狀態判定是否建立 user 並核銷邀請碼。本頁只記述<strong>本子模組自身函式的 I/O 與內部資料流</strong>;與其他子模組之間的呼叫關係請見<a href="../../index.html">宏觀架構圖</a>。
27
- </p>
28
- </header>
29
-
30
- <section class="sub-io">
31
- <h2>函式 I/O</h2>
32
- <table>
33
- <thead>
34
- <tr><th>函式</th><th>簽名/I/O</th><th>副作用</th><th>用途</th></tr>
35
- </thead>
36
- <tbody>
37
- <tr>
38
- <td><code>RegisterWithInvite</code></td>
39
- <td class="sub-io__signature">
40
- <strong>in:</strong> <code>ctx</code>, <code>RegisterInput{Email, Password, InviteCode}</code><br>
41
- <strong>out:</strong> <code>NewUserID</code> | <code>error</code>
42
- </td>
43
- <td>
44
- <span class="sub-io__side sub-io__side--io">tx</span> 開/關交易<br>
45
- <span class="sub-io__side sub-io__side--write">db</span> 條件成立時寫入 user + 標記邀請碼已用<br>
46
- <span class="sub-io__side sub-io__side--io">hash</span> 對密碼做 hash
47
- </td>
48
- <td>對外唯一入口。</td>
49
- </tr>
50
- <tr>
51
- <td><code>validateInput</code></td>
52
- <td class="sub-io__signature">
53
- <strong>in:</strong> <code>RegisterInput</code><br>
54
- <strong>out:</strong> <code>NormalizedInput</code> | <code>ErrInvalidInput</code>
55
- </td>
56
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
57
- <td>email 規範化、邀請碼 trim/大小寫策略、密碼最小強度。</td>
58
- </tr>
59
- <tr>
60
- <td><code>decideInviteValid</code></td>
61
- <td class="sub-io__signature">
62
- <strong>in:</strong> <code>InviteRow | nil</code>, <code>now</code><br>
63
- <strong>out:</strong> <code>true | ErrInviteInvalid</code>
64
- </td>
65
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
66
- <td>判斷邀請列是否存在、未過期、未核銷、<code>allowed_registration==true</code>。</td>
67
- </tr>
68
- <tr>
69
- <td><code>hashPassword</code></td>
70
- <td class="sub-io__signature">
71
- <strong>in:</strong> <code>plaintext</code><br>
72
- <strong>out:</strong> <code>passwordHash</code>
73
- </td>
74
- <td><span class="sub-io__side sub-io__side--io">cpu</span> 高成本(bcrypt/argon2)</td>
75
- <td>密碼絕不外傳明文;本函式回傳後 plaintext 應被丟棄。</td>
76
- </tr>
77
- </tbody>
78
- </table>
79
- </section>
80
-
81
- <section class="sub-vars">
82
- <h2>變數與業務用途</h2>
83
- <p class="sub-vars__intro">下列為本子模組在「邀請碼註冊」業務裡實際持有/流動的識別子。型別僅供讀者對齊,業務用途優先。</p>
84
- <table>
85
- <thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
86
- <tbody>
87
- <tr>
88
- <td class="sub-vars__name">ctx</td>
89
- <td class="sub-vars__type">context.Context</td>
90
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
91
- <td>限制單次註冊請求佔用資源的時間預算;逾時或取消即放棄交易,避免長時間握列鎖把其他註冊請求拖垮。</td>
92
- </tr>
93
- <tr>
94
- <td class="sub-vars__name">RegisterInput</td>
95
- <td class="sub-vars__type">{Email, Password, InviteCode}</td>
96
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
97
- <td>使用者註冊意圖承載:要為誰建立帳號、用什麼憑證、套用哪張邀請碼;任一欄缺值即無法進入業務判定。</td>
98
- </tr>
99
- <tr>
100
- <td class="sub-vars__name">NormalizedInput</td>
101
- <td class="sub-vars__type">{email_lc, code_norm, pwd_plain}</td>
102
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
103
- <td>規範化後的業務輸入;保證 email/邀請碼比較在同一基準上,避免大小寫造成的可避免失敗。<code>pwd_plain</code> 在 hash 之後即被丟棄。</td>
104
- </tr>
105
- <tr>
106
- <td class="sub-vars__name">row / InviteRow</td>
107
- <td class="sub-vars__type">DB row</td>
108
- <td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
109
- <td>「能否註冊」的唯一證據;<code>AllowedRegistration</code> / <code>ConsumedAt</code> / <code>ExpiresAt</code> 三欄直接決定進入「拒絕」或「建立」分支;列鎖在本 <code>tx</code> 內持續至結束。</td>
110
- </tr>
111
- <tr>
112
- <td class="sub-vars__name">now</td>
113
- <td class="sub-vars__type">time.Time</td>
114
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
115
- <td>決策基準時間:判斷邀請碼是否過期、寫入 <code>consumed_at</code> 的官方時戳;測試可注入以避免依賴牆鐘。</td>
116
- </tr>
117
- <tr>
118
- <td class="sub-vars__name">passwordHash</td>
119
- <td class="sub-vars__type">[]byte</td>
120
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
121
- <td>不可逆密碼承載;確保任何明文不會離開本子模組或進入持久化路徑與日誌。</td>
122
- </tr>
123
- <tr>
124
- <td class="sub-vars__name">UserToInsert</td>
125
- <td class="sub-vars__type">{email_lc, passwordHash, invite_id}</td>
126
- <td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
127
- <td>把「業務通過」結果翻譯成 DB 寫入承載;<code>invite_id</code> 把新使用者與其邀請來源綁定,作為日後審計依據。</td>
128
- </tr>
129
- <tr>
130
- <td class="sub-vars__name">tx</td>
131
- <td class="sub-vars__type">*sql.Tx</td>
132
- <td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
133
- <td>把「邀請判定 + 建立 user + 核銷邀請」綁成原子單元;commit/rollback 決定本次註冊是否在世界上留下任何持久化痕跡。</td>
134
- </tr>
135
- </tbody>
136
- </table>
137
- </section>
138
-
139
- <section class="sub-dataflow">
140
- <h2>內部資料流</h2>
141
- <p>下列步序描述<strong>同一個 <code>RegisterWithInvite</code> 呼叫</strong>內部資料如何在自身函式之間流動。每一步只標記值的變化與作用,不展開外部子模組的細節。</p>
142
- <ol>
143
- <li><code>RegisterInput</code> → <code>validateInput</code> → <code>NormalizedInput{email_lc, code_norm, pwd_plain}</code>。</li>
144
- <li><code>NormalizedInput.code_norm</code> 作為查鎖讀的鍵;讀回 <code>InviteRow | nil</code>,記在區域變數 <code>row</code>。</li>
145
- <li><code>row</code> 連同 <code>time.Now()</code> 傳入 <code>decideInviteValid</code>;不通過則拋 <code>ErrInviteInvalid</code> 並結束(交易在外層被回滾)。</li>
146
- <li><code>NormalizedInput.pwd_plain</code> → <code>hashPassword</code> → <code>passwordHash</code>;之後 <code>pwd_plain</code> 不再使用。</li>
147
- <li>組合 <code>UserToInsert{email_lc, passwordHash, invite_id=row.ID}</code>,作為寫入承載。</li>
148
- <li>條件分支結束後將控制權交給交易管理;本子模組函式自此不再持有任何明文敏感資料。</li>
149
- </ol>
150
- <svg class="sub-dataflow__svg" viewBox="0 0 720 220" role="img" aria-label="內部資料流">
151
- <defs>
152
- <marker id="d-arr" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
153
- <path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
154
- </marker>
155
- </defs>
156
- <g class="d-node"><rect x="20" y="30" width="150" height="50" rx="8"></rect><text x="95" y="58" text-anchor="middle">RegisterInput</text></g>
157
- <g class="d-node"><rect x="200" y="30" width="150" height="50" rx="8"></rect><text x="275" y="50" text-anchor="middle">validateInput</text><text x="275" y="68" text-anchor="middle" class="d-label">→ NormalizedInput</text></g>
158
- <g class="d-node"><rect x="380" y="30" width="170" height="50" rx="8"></rect><text x="465" y="50" text-anchor="middle">decideInviteValid</text><text x="465" y="68" text-anchor="middle" class="d-label">(row, now) → bool</text></g>
159
- <g class="d-node"><rect x="580" y="30" width="120" height="50" rx="8"></rect><text x="640" y="58" text-anchor="middle">ErrInviteInvalid</text></g>
160
-
161
- <g class="d-node"><rect x="200" y="140" width="150" height="50" rx="8"></rect><text x="275" y="160" text-anchor="middle">hashPassword</text><text x="275" y="178" text-anchor="middle" class="d-label">pwd → passwordHash</text></g>
162
- <g class="d-node"><rect x="380" y="140" width="170" height="50" rx="8"></rect><text x="465" y="160" text-anchor="middle">UserToInsert</text><text x="465" y="178" text-anchor="middle" class="d-label">{email_lc, hash, invite_id}</text></g>
163
-
164
- <line class="d-edge" x1="170" y1="55" x2="196" y2="55" marker-end="url(#d-arr)"></line>
165
- <line class="d-edge" x1="350" y1="55" x2="376" y2="55" marker-end="url(#d-arr)"></line>
166
- <line class="d-edge d-edge--side" x1="550" y1="55" x2="576" y2="55" marker-end="url(#d-arr)"></line>
167
- <line class="d-edge" x1="275" y1="80" x2="275" y2="136" marker-end="url(#d-arr)"></line>
168
- <line class="d-edge" x1="350" y1="165" x2="376" y2="165" marker-end="url(#d-arr)"></line>
169
- </svg>
170
- </section>
171
-
172
- <section class="sub-errors">
173
- <h2>本子模組會拋出的錯誤</h2>
174
- <table class="sub-io__inline">
175
- <thead><tr><th>錯誤</th><th>觸發條件</th><th>後續責任</th></tr></thead>
176
- <tbody>
177
- <tr><td><code>ErrInvalidInput</code></td><td><code>validateInput</code> 失敗</td><td>呼叫端決定 HTTP 對映;本子模組不開交易。</td></tr>
178
- <tr><td><code>ErrInviteInvalid</code></td><td><code>decideInviteValid</code> 為 false</td><td>呼叫端負責回滾交易並回應對外錯誤碼。</td></tr>
179
- <tr><td><code>ErrTransient</code></td><td>外部 I/O 暫時失敗(例如 hash CPU 中斷、context 取消)</td><td>呼叫端決定是否重試;本子模組僅保證未提交。</td></tr>
180
- </tbody>
181
- </table>
182
- <p class="atlas-meta">回滾/重試怎麼影響其他子模組的狀態,屬於跨界議題;請見<a href="../../index.html">宏觀架構圖</a>的 manifest 與資料行邊。</p>
183
- </section>
184
-
185
- <footer class="atlas-meta" style="margin-top: 2rem">
186
- <p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
187
- </footer>
188
- </main>
189
- </body>
2
+ <html lang="en" data-atlas-page="submodule">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Invite-code registration · registration-service</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="submodule-header">
11
+ <nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Invite-code registration</a></nav>
12
+ <h1>registration-service <small class="submodule-kind submodule-kind--service">Service</small></h1>
13
+ <p class="submodule-role">Owns the registration transaction (consumer side of the invite_codes data row).</p>
14
+ </header>
15
+ <main class="submodule-main">
16
+ <section class="sub-io" aria-label="Function I/O">
17
+ <h2>Function I/O</h2>
18
+ <table class="sub-table">
19
+ <thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
20
+ <tbody>
21
+ <tr><td>RegisterWithInvite</td><td>ctx, RegisterInput</td><td>NewUserID | ErrInvalidCode | ErrEmailTaken</td><td>tx</td><td>Validates code, hashes password, inserts user, consumes invite.</td></tr>
22
+ <tr><td>decideInviteValid</td><td>row, now</td><td>true | ErrInvalidCode</td><td>pure</td><td>Checks existence, expiry, consumption state.</td></tr>
23
+ </tbody>
24
+ </table>
25
+ </section>
26
+ <section class="sub-vars" aria-label="Variables">
27
+ <h2>Variables</h2>
28
+ <table class="sub-table">
29
+ <thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
30
+ <tbody>
31
+ <tr><td>inviteRow</td><td>InviteCodeRow</td><td>tx</td><td>Row read with SELECT FOR UPDATE; gates the rest of the flow.</td></tr>
32
+ <tr><td>passwordHash</td><td>string</td><td>tx</td><td>bcrypt/argon2 hash of the submitted password; plaintext is discarded.</td></tr>
33
+ </tbody>
34
+ </table>
35
+ </section>
36
+ <section class="sub-dataflow" aria-label="Internal data flow">
37
+ <h2>Internal data flow</h2>
38
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
39
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
40
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
41
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
42
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
43
+ </div>
44
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
45
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 600" role="img" aria-label="Internal dataflow">
46
+ <defs>
47
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
48
+ </defs>
49
+ <g class="sub-dataflow__step">
50
+ <circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
51
+ <text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
52
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
53
+ <text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Open transaction.</text>
54
+ </g>
55
+ <line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
56
+ <g class="sub-dataflow__step">
57
+ <circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
58
+ <text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
59
+ <rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
60
+ <text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">SELECT invite_codes FOR UPDATE WHERE code = ?</text>
61
+ </g>
62
+ <line class="sub-dataflow__arrow" x1="340" y1="226" x2="340" y2="256" marker-end="url(#sub-arrow)" />
63
+ <g class="sub-dataflow__step">
64
+ <circle class="sub-dataflow__badge" cx="42" cy="300" r="18" />
65
+ <text class="sub-dataflow__badge-text" x="42" y="305" text-anchor="middle">3</text>
66
+ <rect class="sub-dataflow__box" x="80" y="264" width="520" height="72" rx="14" ry="14" />
67
+ <text class="sub-dataflow__text" x="340" y="306" text-anchor="middle">decideInviteValid against current time.</text>
68
+ </g>
69
+ <line class="sub-dataflow__arrow" x1="340" y1="342" x2="340" y2="372" marker-end="url(#sub-arrow)" />
70
+ <g class="sub-dataflow__step">
71
+ <circle class="sub-dataflow__badge" cx="42" cy="416" r="18" />
72
+ <text class="sub-dataflow__badge-text" x="42" y="421" text-anchor="middle">4</text>
73
+ <rect class="sub-dataflow__box" x="80" y="380" width="520" height="72" rx="14" ry="14" />
74
+ <text class="sub-dataflow__text" x="340" y="422" text-anchor="middle">INSERT users row and UPDATE invite_codes.consumed_at.</text>
75
+ </g>
76
+ <line class="sub-dataflow__arrow" x1="340" y1="458" x2="340" y2="488" marker-end="url(#sub-arrow)" />
77
+ <g class="sub-dataflow__step">
78
+ <circle class="sub-dataflow__badge" cx="42" cy="532" r="18" />
79
+ <text class="sub-dataflow__badge-text" x="42" y="537" text-anchor="middle">5</text>
80
+ <rect class="sub-dataflow__box" x="80" y="496" width="520" height="72" rx="14" ry="14" />
81
+ <text class="sub-dataflow__text" x="340" y="538" text-anchor="middle">Commit and return NewUserID.</text>
82
+ </g>
83
+ </svg>
84
+ </div>
85
+ </div>
86
+ </section>
87
+ <section class="sub-errors" aria-label="Errors">
88
+ <h2>Errors</h2>
89
+ <table class="sub-table">
90
+ <thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
91
+ <tbody>
92
+ <tr><td>ErrInvalidCode</td><td>Row missing, expired, or already consumed.</td><td>422 response with `invite_code` reason.</td></tr>
93
+ <tr><td>ErrEmailTaken</td><td>Unique violation on users.email.</td><td>422 response with `email` reason.</td></tr>
94
+ </tbody>
95
+ </table>
96
+ </section>
97
+ </main>
98
+ <script src="../../assets/viewer.client.js" defer></script>
99
+ </body>
190
100
  </html>
@@ -1,140 +1,85 @@
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
- <title>子模組 · web-register-ui — 邀請碼註冊</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;600&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap"
11
- rel="stylesheet"
12
- />
13
- <link rel="stylesheet" href="../../assets/architecture.css" />
14
- </head>
15
- <body>
16
- <main class="atlas-page atlas-page--feature">
17
- <nav class="breadcrumb" aria-label="階層導航">
18
- <a href="../../index.html">宏觀架構</a> · <a href="./index.html">邀請碼註冊</a> ·
19
- <span>web-register-ui</span>
20
- </nav>
21
-
22
- <header class="atlas-header">
23
- <p class="atlas-kicker">Submodule · web-register-ui</p>
24
- <h1 class="atlas-title">web-register-ui</h1>
25
- <p class="atlas-meta">自身職責:渲染註冊表單、客戶端驗證、發出 HTTP 請求、把結果映射成 UI 狀態。本頁只談自身函式 I/O 與資料流。</p>
26
- </header>
27
-
28
- <section class="sub-io">
29
- <h2>函式 I/O</h2>
30
- <table>
31
- <thead><tr><th>函式</th><th>簽名</th><th>副作用</th><th>用途</th></tr></thead>
32
- <tbody>
33
- <tr>
34
- <td><code>useRegisterForm()</code></td>
35
- <td class="sub-io__signature">
36
- <strong>in:</strong> —<br>
37
- <strong>out:</strong> <code>{values, errors, setField, submit}</code>
38
- </td>
39
- <td><span class="sub-io__side sub-io__side--io">state</span> React state hook</td>
40
- <td>表單狀態容器。</td>
41
- </tr>
42
- <tr>
43
- <td><code>validateClientSide</code></td>
44
- <td class="sub-io__signature">
45
- <strong>in:</strong> <code>FormValues</code><br>
46
- <strong>out:</strong> <code>FieldErrors | null</code>
47
- </td>
48
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
49
- <td>必填、email 格式、密碼長度等規則。</td>
50
- </tr>
51
- <tr>
52
- <td><code>submit</code></td>
53
- <td class="sub-io__signature">
54
- <strong>in:</strong> <code>FormValues</code><br>
55
- <strong>out:</strong> <code>Result&lt;NewUserID, UiError&gt;</code>
56
- </td>
57
- <td><span class="sub-io__side sub-io__side--io">network</span> fetch POST</td>
58
- <td>送出後驅動 loading / success / error UI。</td>
59
- </tr>
60
- <tr>
61
- <td><code>mapApiError</code></td>
62
- <td class="sub-io__signature">
63
- <strong>in:</strong> <code>ApiError{code, message}</code><br>
64
- <strong>out:</strong> <code>UiMessage{lang_tag, text}</code>
65
- </td>
66
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
67
- <td>機讀錯誤碼轉成本地化訊息。</td>
68
- </tr>
69
- </tbody>
70
- </table>
71
- </section>
72
-
73
- <section class="sub-vars">
74
- <h2>變數與業務用途</h2>
75
- <p class="sub-vars__intro">本表盤點本子模組在記憶體中持有、決定 UI 行為的關鍵識別子。</p>
76
- <table>
77
- <thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
78
- <tbody>
79
- <tr>
80
- <td class="sub-vars__name">FormValues</td>
81
- <td class="sub-vars__type">{email, password, inviteCode}</td>
82
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
83
- <td>使用者目前打算送出的註冊資料;驅動所有後續驗證、按鈕可用性與請求 body。</td>
84
- </tr>
85
- <tr>
86
- <td class="sub-vars__name">FieldErrors</td>
87
- <td class="sub-vars__type">Record&lt;field,msg&gt; | null</td>
88
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
89
- <td>客戶端驗證結果;非空時阻擋網路請求,避免明顯無效嘗試打到後端造成不必要負擔與錯誤訊息。</td>
90
- </tr>
91
- <tr>
92
- <td class="sub-vars__name">UIState</td>
93
- <td class="sub-vars__type">idle | loading | error | success</td>
94
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
95
- <td>UI 機器狀態;<code>loading</code> 期間禁用送出按鈕避免重複提交;其他狀態決定要顯示哪個訊息/導向。</td>
96
- </tr>
97
- <tr>
98
- <td class="sub-vars__name">Result</td>
99
- <td class="sub-vars__type">{ok: NewUserID} | {err: UiError}</td>
100
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
101
- <td>一次 <code>submit</code> 的最終回應;驅動「導向後續頁」或「停留修改再試」兩條業務分支。</td>
102
- </tr>
103
- <tr>
104
- <td class="sub-vars__name">UiMessage</td>
105
- <td class="sub-vars__type">{lang_tag, text}</td>
106
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
107
- <td>後端機讀錯誤碼的本地化呈現;讓使用者用自身語言理解失敗原因(例如「邀請碼已被使用」)。</td>
108
- </tr>
109
- </tbody>
110
- </table>
111
- </section>
112
-
113
- <section class="sub-dataflow">
114
- <h2>內部資料流</h2>
115
- <ol>
116
- <li>使用者輸入 → <code>setField</code> 改寫 <code>FormValues</code>。</li>
117
- <li>送出時 <code>validateClientSide(FormValues)</code> 若回 <code>FieldErrors</code>,UI 設為 <code>error</code>,<strong>不</strong>觸發網路請求。</li>
118
- <li>通過驗證後 <code>submit(FormValues)</code>:UI 轉 <code>loading</code>,等待 <code>Result</code>。</li>
119
- <li><code>Result.ok</code> → UI 轉 <code>success</code>,呼叫導向;<code>Result.err</code> → 經 <code>mapApiError</code> 變 <code>UiMessage</code> 顯示。</li>
120
- <li>失敗後 <code>FormValues</code> 保留,使用者可修正並再次 <code>submit</code>。</li>
121
- </ol>
122
- </section>
123
-
124
- <section class="sub-errors">
125
- <h2>本子模組可暴露的錯誤</h2>
126
- <table>
127
- <thead><tr><th>類型</th><th>來源</th><th>UI 行為</th></tr></thead>
128
- <tbody>
129
- <tr><td><code>FieldErrors</code></td><td><code>validateClientSide</code></td><td>就地紅字;無網路請求。</td></tr>
130
- <tr><td><code>UiMessage</code></td><td><code>mapApiError</code></td><td>banner/toast;表單仍可編輯。</td></tr>
131
- </tbody>
132
- </table>
133
- </section>
134
-
135
- <footer class="atlas-meta" style="margin-top: 2rem">
136
- <p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
137
- </footer>
138
- </main>
139
- </body>
2
+ <html lang="en" data-atlas-page="submodule">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Invite-code registration · web-register-ui</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="submodule-header">
11
+ <nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Invite-code registration</a></nav>
12
+ <h1>web-register-ui <small class="submodule-kind submodule-kind--ui">UI</small></h1>
13
+ <p class="submodule-role">React page that captures email, password, and invite code.</p>
14
+ </header>
15
+ <main class="submodule-main">
16
+ <section class="sub-io" aria-label="Function I/O">
17
+ <h2>Function I/O</h2>
18
+ <table class="sub-table">
19
+ <thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
20
+ <tbody>
21
+ <tr><td>handleSubmit</td><td>FormEvent</td><td>void</td><td>io</td><td>POSTs the registration payload and routes the response.</td></tr>
22
+ </tbody>
23
+ </table>
24
+ </section>
25
+ <section class="sub-vars" aria-label="Variables">
26
+ <h2>Variables</h2>
27
+ <table class="sub-table">
28
+ <thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
29
+ <tbody>
30
+ <tr><td>email</td><td>string</td><td>call</td><td>Identity for the new account; required.</td></tr>
31
+ <tr><td>inviteCode</td><td>string</td><td>call</td><td>Token from the off-platform invite hand-off.</td></tr>
32
+ </tbody>
33
+ </table>
34
+ </section>
35
+ <section class="sub-dataflow" aria-label="Internal data flow">
36
+ <h2>Internal data flow</h2>
37
+ <div class="sub-dataflow__canvas" data-pan-zoom-container>
38
+ <div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
39
+ <button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
40
+ <button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
41
+ <button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
42
+ </div>
43
+ <div class="sub-dataflow__viewport" data-pan-zoom-viewport>
44
+ <svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 372" role="img" aria-label="Internal dataflow">
45
+ <defs>
46
+ <marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
47
+ </defs>
48
+ <g class="sub-dataflow__step">
49
+ <circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
50
+ <text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
51
+ <rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
52
+ <text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Collect form fields.</text>
53
+ </g>
54
+ <line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
55
+ <g class="sub-dataflow__step">
56
+ <circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
57
+ <text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
58
+ <rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
59
+ <text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">POST /api/register with payload.</text>
60
+ </g>
61
+ <line class="sub-dataflow__arrow" x1="340" y1="226" x2="340" y2="256" marker-end="url(#sub-arrow)" />
62
+ <g class="sub-dataflow__step">
63
+ <circle class="sub-dataflow__badge" cx="42" cy="302" r="18" />
64
+ <text class="sub-dataflow__badge-text" x="42" y="307" text-anchor="middle">3</text>
65
+ <rect class="sub-dataflow__box" x="80" y="264" width="520" height="76" rx="14" ry="14" />
66
+ <text class="sub-dataflow__text" x="340" y="298" text-anchor="middle">On 2xx redirect to /welcome; otherwise surface field-level</text>
67
+ <text class="sub-dataflow__text" x="340" y="318" text-anchor="middle">errors.</text>
68
+ </g>
69
+ </svg>
70
+ </div>
71
+ </div>
72
+ </section>
73
+ <section class="sub-errors" aria-label="Errors">
74
+ <h2>Errors</h2>
75
+ <table class="sub-table">
76
+ <thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
77
+ <tbody>
78
+ <tr><td>ErrInvalidCode</td><td>API returns 422 with `invite_code` reason.</td><td>Highlight the invite-code field with the API message.</td></tr>
79
+ </tbody>
80
+ </table>
81
+ </section>
82
+ </main>
83
+ <script src="../../assets/viewer.client.js" defer></script>
84
+ </body>
140
85
  </html>