@laitszkin/apollo-toolkit 3.9.6 → 3.10.0

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 (46) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +6 -0
  4. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  5. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  6. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  7. package/cjk-pdf/agents/openai.yaml +5 -0
  8. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  9. package/generate-spec/SKILL.md +24 -4
  10. package/generate-spec/agents/openai.yaml +1 -0
  11. package/generate-spec/references/TEMPLATE_SPEC.md +98 -0
  12. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  13. package/init-project-html/SKILL.md +181 -0
  14. package/init-project-html/agents/openai.yaml +13 -0
  15. package/init-project-html/references/TEMPLATE_SPEC.md +98 -0
  16. package/init-project-html/references/architecture-page.template.html +35 -0
  17. package/init-project-html/references/architecture.css +1059 -0
  18. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +1059 -0
  19. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +54 -0
  20. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +165 -0
  21. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +198 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +165 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +152 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +140 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +53 -0
  26. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +161 -0
  27. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +145 -0
  28. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +190 -0
  29. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +140 -0
  30. package/init-project-html/sample-demo/resources/project-architecture/index.html +337 -0
  31. package/init-project-html/scripts/architecture.js +496 -0
  32. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  33. package/lib/cli.js +2 -0
  34. package/lib/tool-runner.js +7 -0
  35. package/merge-changes-from-local-branches/SKILL.md +75 -128
  36. package/merge-changes-from-local-branches/agents/openai.yaml +1 -1
  37. package/merge-conflict-resolver/agents/openai.yaml +5 -0
  38. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  39. package/package.json +1 -1
  40. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  41. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  42. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  43. package/spec-to-project-html/SKILL.md +116 -0
  44. package/spec-to-project-html/agents/openai.yaml +12 -0
  45. package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -0
  46. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -0,0 +1,145 @@
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>子模組 · public-api — 邀請碼註冊</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> · <span>public-api</span>
19
+ </nav>
20
+
21
+ <header class="atlas-header">
22
+ <p class="atlas-kicker">Submodule · public-api</p>
23
+ <h1 class="atlas-title">public-api(reg)</h1>
24
+ <p class="atlas-meta">自身職責:HTTP 邊界。處理綁定、輸入 schema 檢查、把網域結果映射為狀態碼/錯誤 payload。</p>
25
+ </header>
26
+
27
+ <section class="sub-io">
28
+ <h2>函式 I/O</h2>
29
+ <table>
30
+ <thead><tr><th>函式</th><th>簽名</th><th>副作用</th><th>用途</th></tr></thead>
31
+ <tbody>
32
+ <tr>
33
+ <td><code>RegisterHandler</code></td>
34
+ <td class="sub-io__signature">
35
+ <strong>in:</strong> <code>http.Request</code><br>
36
+ <strong>out:</strong> <code>http.Response</code>
37
+ </td>
38
+ <td><span class="sub-io__side sub-io__side--io">io</span> 讀/寫 socket</td>
39
+ <td>路由綁定的對外入口。</td>
40
+ </tr>
41
+ <tr>
42
+ <td><code>decodeRegisterInput</code></td>
43
+ <td class="sub-io__signature">
44
+ <strong>in:</strong> <code>io.Reader</code><br>
45
+ <strong>out:</strong> <code>RegisterInput</code> | <code>ErrBadRequest</code>
46
+ </td>
47
+ <td><span class="sub-io__side sub-io__side--io">io</span> 讀 body</td>
48
+ <td>JSON 解碼 + 基本 schema 校驗。</td>
49
+ </tr>
50
+ <tr>
51
+ <td><code>mapDomainError</code></td>
52
+ <td class="sub-io__signature">
53
+ <strong>in:</strong> <code>error</code><br>
54
+ <strong>out:</strong> <code>(httpStatus, ErrorBody)</code>
55
+ </td>
56
+ <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
57
+ <td>網域 error → HTTP 對映表。</td>
58
+ </tr>
59
+ <tr>
60
+ <td><code>writeSuccess</code></td>
61
+ <td class="sub-io__signature">
62
+ <strong>in:</strong> <code>NewUserID</code>, <code>http.ResponseWriter</code><br>
63
+ <strong>out:</strong> —
64
+ </td>
65
+ <td><span class="sub-io__side sub-io__side--write">io</span> 寫 201 body</td>
66
+ <td>序列化 user id + 必要 header。</td>
67
+ </tr>
68
+ </tbody>
69
+ </table>
70
+ </section>
71
+
72
+ <section class="sub-vars">
73
+ <h2>變數與業務用途</h2>
74
+ <p class="sub-vars__intro">HTTP 邊界子模組持有的識別子,幾乎全在單次 handler 呼叫中存活。</p>
75
+ <table>
76
+ <thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
77
+ <tbody>
78
+ <tr>
79
+ <td class="sub-vars__name">http.Request</td>
80
+ <td class="sub-vars__type">request</td>
81
+ <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
82
+ <td>邊界輸入;只在 handler 期間有效,handler 結束後資源歸還,不可在背景任務繼續使用。</td>
83
+ </tr>
84
+ <tr>
85
+ <td class="sub-vars__name">RegisterInput</td>
86
+ <td class="sub-vars__type">{Email, Password, InviteCode}</td>
87
+ <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
88
+ <td>HTTP body 解碼後的網域層承載;schema 校驗通過代表「結構面可進入業務」,避免錯誤輸入觸發無謂的 DB 開銷。</td>
89
+ </tr>
90
+ <tr>
91
+ <td class="sub-vars__name">httpStatus</td>
92
+ <td class="sub-vars__type">int</td>
93
+ <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
94
+ <td><code>mapDomainError</code> 的輸出之一;業務面決定協議語意(409 與 503 不可混用,前者要使用者改、後者要使用者稍後再試)。</td>
95
+ </tr>
96
+ <tr>
97
+ <td class="sub-vars__name">ErrorBody</td>
98
+ <td class="sub-vars__type">{code, message}</td>
99
+ <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
100
+ <td>結構化錯誤體;<code>code</code> 給 UI 對映本地化文字,<code>message</code> 僅供開發者排錯。</td>
101
+ </tr>
102
+ <tr>
103
+ <td class="sub-vars__name">http.ResponseWriter</td>
104
+ <td class="sub-vars__type">writer</td>
105
+ <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
106
+ <td>對外副作用通道;本子模組保證一次回應只寫一次,避免重複寫入造成協議違例與使用者看到的不一致結果。</td>
107
+ </tr>
108
+ </tbody>
109
+ </table>
110
+ </section>
111
+
112
+ <section class="sub-dataflow">
113
+ <h2>內部資料流</h2>
114
+ <ol>
115
+ <li><code>http.Request.Body</code> → <code>decodeRegisterInput</code> → <code>RegisterInput</code>(失敗 → <code>ErrBadRequest</code>)。</li>
116
+ <li><code>RegisterInput</code> 作為傳入網域層的參數;本子模組<strong>不</strong>持有交易、密碼明文也只在這一函式內存在。</li>
117
+ <li>從網域層拿到 <code>NewUserID | error</code>:
118
+ <ul>
119
+ <li><code>NewUserID</code> → <code>writeSuccess</code>。</li>
120
+ <li><code>error</code> → <code>mapDomainError</code> → 對應 HTTP 狀態 + JSON。</li>
121
+ </ul>
122
+ </li>
123
+ <li>處理結束 / response 完成寫入後生命週期終止。</li>
124
+ </ol>
125
+ </section>
126
+
127
+ <section class="sub-errors">
128
+ <h2>本子模組會回應/轉化的錯誤</h2>
129
+ <table>
130
+ <thead><tr><th>條件</th><th>HTTP</th></tr></thead>
131
+ <tbody>
132
+ <tr><td>schema 失敗</td><td><code>400 BAD_REQUEST</code></td></tr>
133
+ <tr><td>網域回 <code>ErrInviteInvalid</code></td><td><code>409 INVITE_INVALID</code></td></tr>
134
+ <tr><td>網域回 <code>ErrTransient</code></td><td><code>503 SERVICE_UNAVAILABLE</code></td></tr>
135
+ <tr><td>panic 或未分類</td><td><code>500 INTERNAL</code></td></tr>
136
+ </tbody>
137
+ </table>
138
+ </section>
139
+
140
+ <footer class="atlas-meta" style="margin-top: 2rem">
141
+ <p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
142
+ </footer>
143
+ </main>
144
+ </body>
145
+ </html>
@@ -0,0 +1,190 @@
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>
190
+ </html>
@@ -0,0 +1,140 @@
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>
140
+ </html>