@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.
- package/CHANGELOG.md +37 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/generate-spec/SKILL.md +17 -15
- package/generate-spec/agents/openai.yaml +1 -1
- package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/init-project-html/SKILL.md +117 -125
- package/init-project-html/agents/openai.yaml +18 -9
- package/init-project-html/lib/atlas/assets/architecture.css +161 -0
- package/init-project-html/lib/atlas/assets/viewer.client.js +136 -0
- package/init-project-html/lib/atlas/cli.js +1023 -0
- package/init-project-html/lib/atlas/layout.js +330 -0
- package/init-project-html/lib/atlas/render.js +583 -0
- package/init-project-html/lib/atlas/schema.js +347 -0
- package/init-project-html/lib/atlas/state.js +402 -0
- package/init-project-html/references/TEMPLATE_SPEC.md +140 -83
- package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +160 -1058
- package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +136 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +172 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +64 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +102 -196
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +82 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +88 -150
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +83 -138
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +84 -159
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +81 -143
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +98 -188
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +83 -138
- package/init-project-html/sample-demo/resources/project-architecture/index.html +256 -335
- package/init-project-html/scripts/architecture.js +65 -247
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/package.json +6 -2
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/spec-to-project-html/SKILL.md +74 -67
- package/spec-to-project-html/agents/openai.yaml +14 -8
- package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -83
- 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="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
</
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
</
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</
|
|
71
|
-
</
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<tr>
|
|
86
|
-
<td class="sub-vars__name">FieldErrors</td>
|
|
87
|
-
<td class="sub-vars__type">Record<field,msg> | 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>
|