@aigne/doc-smith 0.8.2 → 0.8.4

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 (51) hide show
  1. package/.aigne/doc-smith/config.yaml +3 -3
  2. package/.aigne/doc-smith/preferences.yml +58 -12
  3. package/.aigne/doc-smith/upload-cache.yaml +600 -207
  4. package/CHANGELOG.md +17 -0
  5. package/README.md +77 -5
  6. package/docs/advanced-how-it-works.md +55 -60
  7. package/docs/advanced-how-it-works.zh.md +60 -65
  8. package/docs/advanced-quality-assurance.md +73 -38
  9. package/docs/advanced-quality-assurance.zh.md +73 -38
  10. package/docs/advanced.md +2 -14
  11. package/docs/advanced.zh.md +5 -17
  12. package/docs/changelog.md +41 -4
  13. package/docs/changelog.zh.md +77 -40
  14. package/docs/cli-reference.md +79 -13
  15. package/docs/cli-reference.zh.md +92 -26
  16. package/docs/configuration-interactive-setup.md +102 -49
  17. package/docs/configuration-interactive-setup.zh.md +102 -49
  18. package/docs/configuration-language-support.md +69 -39
  19. package/docs/configuration-language-support.zh.md +68 -38
  20. package/docs/configuration-llm-setup.md +25 -62
  21. package/docs/configuration-llm-setup.zh.md +25 -62
  22. package/docs/configuration-preferences.md +79 -67
  23. package/docs/configuration-preferences.zh.md +78 -67
  24. package/docs/configuration.md +122 -109
  25. package/docs/configuration.zh.md +130 -117
  26. package/docs/features-generate-documentation.md +44 -24
  27. package/docs/features-generate-documentation.zh.md +52 -32
  28. package/docs/features-publish-your-docs.md +41 -40
  29. package/docs/features-publish-your-docs.zh.md +50 -49
  30. package/docs/features-translate-documentation.md +73 -17
  31. package/docs/features-translate-documentation.zh.md +76 -20
  32. package/docs/features-update-and-refine.md +72 -21
  33. package/docs/features-update-and-refine.zh.md +80 -29
  34. package/docs/features.md +24 -28
  35. package/docs/features.zh.md +25 -29
  36. package/docs/getting-started.md +87 -38
  37. package/docs/getting-started.zh.md +88 -39
  38. package/docs/overview.md +17 -35
  39. package/docs/overview.zh.md +18 -36
  40. package/package.json +9 -8
  41. package/prompts/content-detail-generator.md +1 -0
  42. package/prompts/document/custom-code-block.md +101 -0
  43. package/prompts/document/d2-chart/rules.md +941 -1031
  44. package/prompts/document/detail-generator.md +7 -53
  45. package/tests/kroki-utils.test.mjs +88 -17
  46. package/utils/constants.mjs +15 -1
  47. package/utils/kroki-utils.mjs +22 -14
  48. package/utils/markdown-checker.mjs +1 -1
  49. package/utils/utils.mjs +3 -2
  50. package/prompts/document/d2-chart/diy-examples.md +0 -44
  51. package/prompts/document/d2-chart/shape-rules.md +0 -182
@@ -1,1088 +1,998 @@
1
- - 使用 d2 展示架构关系、流程与组件交互
2
- - 使用 d2 图表解释复杂的概念 (```d2``` format),让页面内容展示形式更丰富
3
- - 使用的 d2 的版本是 0.7.x,d2 官方的文档请查看 https://d2lang.com/tour/intro/
4
- - 图表应简洁明了,节点和连线命名准确,节点与连线文案保持简洁,不要太长
5
- - bad
6
- ```d2
7
- "TokenService": {
8
- label: "TokenService (Handles token storage & refresh)"
9
- shape: class
10
- }
11
- ```
12
- - good
13
- ```d2
14
- "TokenService": {
15
- label: "TokenService"
16
- shape: class
17
- }
18
- ```
19
- - 连线上的文字描述,尽量简洁明了,一般来说只需要使用单个词或两个词即可
20
- - bad:
21
- ```d2
22
- "User Login" -> "Session Creation": "User submits login form with credentials"
23
- ```
24
- - good:
25
- ```d2
26
- "User Login" -> "Session Creation": "login"
27
- ```
28
- - d2 代码块必须完整且可渲染,避免使用未闭合的语法与奇异字符,避免语法错误
29
- - 确保每一个节点都有 label 属性,用来表达节点的名称
30
- - 如果节点的 label 过长,则应该使用 `\n` 来进行换行
31
- - bad
32
- ```d2
33
- "AuthService": {
34
- label: "AuthService (Handles user authentication, profile management, privacy settings, and related actions)"
35
- shape: class
36
- }
37
- ```
38
- - good
39
- ```d2
40
- "AuthService": {
41
- label: "AuthService\n(Handles user authentication,\nprofile management, privacy settings,\nand related actions)"
42
- shape: class
43
- }
44
- ```
45
- - **非常重要** 如果节点的名称包含了特殊字符(如 `@`、` `、`/`, 空格等),请将名称中的特殊字符转换为 `-`,然后使用 label 来表达原始的名称,确保节点的名称一定不要使用 `"` 包裹
46
- - bad:
47
- ```d2
48
- "@blocklet/js-sdk": {
49
- shape: package
50
-
51
- TokenService: {
52
- shape: class
53
- }
54
- }
55
- ```
56
- - good:
57
- ```d2
58
- "blocklet-js-sdk": {
59
- shape: package
60
- label: "@blocklet/js-sdk
61
-
62
- TokenService: {
63
- shape: class
64
- }
65
- }
66
- ```
67
- - 必须确保每个节点和子节点是有名称的
68
- - bad:
69
- ```d2
70
- "SDK Core Instance": {
71
- shape: package
72
- "TokenService": "Manages session and refresh tokens"
73
- "Services": {
74
- grid-columns: 2
75
- "AuthService": ""
76
- "BlockletService": ""
77
- }
78
- }
79
- ```
80
- - good:
81
- ```d2
82
- "SDK Core Instance": {
83
- shape: package
84
- "TokenService": "Manages session and refresh tokens"
85
- "Services": {
86
- grid-columns: 2
87
- "AuthService": "AuthService"
88
- "BlockletService": "BlockletService"
89
- }
90
- }
91
- ```
92
- - 不要为节点添加 `tooltip`,保持简单即可
93
- - bad
94
- ```d2
95
- "AuthService": {
96
- label: "AuthService"
97
- tooltip: "Manages user profiles, privacy, and authentication actions"
98
- shape: class
99
- }
100
- ```
101
- - good
102
- ```d2
103
- "AuthService": {
104
- label: "AuthService"
105
- shape: class
106
- }
107
- ```
108
- - 不要随意给节点/连线填充颜色,除非节点/连线有明确的 yes/no 的状态,此时可以添加 `error`, `warning`, `success` 之类的颜色
109
- - bad
110
- ```d2
111
- "TokenService" {
112
- shape: class
113
- style.fill: "#fffbe6"
114
- }
115
- ```
116
- - good
117
- ```d2
118
- "TokenService" {
119
- shape: class
120
- }
121
- ```
122
- - 对于单个节点和连线,不要使用 `animate: true`,避免有些地方有,但有些地方没有的情况(看起来会很奇怪)
123
- - 连线的箭头方向必须正确,确保箭头指向关系的下游端
124
- - 连线的样式,尽量保持一致,不要有些实线,有些虚线的情况,除非有明确的区分意义
125
- - 页面的整体布局使用 `direction: down`,这样能确保图表适合在网页中进行阅读;子图中可以根据情况来使用其他的方向布局,需要确保图表的整体效果看起来不会太宽
126
- - bad:
127
- ```d2
128
- direction: right
129
-
130
- "online": {
131
- shape: circle
132
- style.fill: "#52c41a"
133
- }
134
-
135
- "offline": {
136
- shape: circle
137
- style.fill: "#faad14"
138
- }
139
-
140
- "expired": {
141
- shape: circle
142
- style.fill: "#ff4d4f"
143
- }
144
-
145
- "New Login" -> "online": "User authenticates"
146
- "online" -> "offline": "User closes app/browser"
147
- "online" -> "expired": "Token expires"
148
- "offline" -> "online": "User returns"
149
- "offline" -> "expired": "Extended inactivity"
150
- ```
151
- - good:
152
- ```d2
153
- direction: down
154
-
155
- "online": {
156
- shape: circle
157
- style.fill: "#52c41a"
158
- }
159
-
160
- "offline": {
161
- shape: circle
162
- style.fill: "#faad14"
163
- }
164
-
165
- "expired": {
166
- shape: circle
167
- style.fill: "#ff4d4f"
168
- }
169
-
170
- "New Login" -> "online": "User authenticates"
171
- "online" -> "offline": "User closes app/browser"
172
- "online" -> "expired": "Token expires"
173
- "offline" -> "online": "User returns"
174
- "offline" -> "expired": "Extended inactivity"
175
- ```
176
- - 如果一个节点中的字节点太多了(超过3个),请使用 `grid-columns` 限制一下单行的列数,`grid-columns` 的值优先使用2,最大不要超过 3,例如
177
- - good:
178
- ```d2
179
- "Instance": {
180
- grid-columns: 3
181
- "A": "A"
182
- "B": "B"
183
- "C": "C"
184
- "D": "D"
185
- "E": "E"
186
- }
187
- ```
188
- ```d2
189
- "Instance": {
190
- grid-columns: 2
191
- "A": "A"
192
- "B": "B"
193
- "C": "C"
194
- "D": "D"
195
- }
196
- ```
197
- - 每一个容器节点中,最好设置 `grid-columns`
198
- - bad:
199
- ```d2
200
- direction: down
201
-
202
- "SDK": "@blocklet/js-sdk" {
203
- shape: package
204
-
205
- "Core Instance": {
206
- shape: rectangle
207
- "BlockletSDK": "Main SDK Class"
208
- }
209
-
210
- "Services": {
211
- grid-columns: 3
212
- "AuthService": "User Authentication" {
213
- shape: class
214
- }
215
- "TokenService": "Token Management" {
216
- shape: class
217
- }
218
- "UserSessionService": "Session Management" {
219
- shape: class
220
- }
221
- "BlockletService": "Blocklet Metadata" {
222
- shape: class
223
- }
224
- "FederatedService": "Federated Login" {
225
- shape: class
226
- }
227
- }
228
-
229
- "HTTP Clients": {
230
- grid-columns: 2
231
- "createAxios": "Axios-based Client" {
232
- shape: rectangle
233
- }
234
- "createFetch": "Fetch-based Client" {
235
- shape: rectangle
236
- }
237
- }
238
- }
1
+ # D2 Diagram Generation Expert Guide
2
+
3
+ ## Preamble: LLM Role and Core Objective
4
+
5
+ You are an expert Software Architect and a master of the D2 (Declarative Diagramming) language. Your primary function is to translate abstract descriptions of software systems, components, and processes into precise, readable, and visually effective D2 diagram code.
6
+
7
+ Your core directive is to produce D2 code that is not only syntactically correct but also semantically meaningful and adheres to the highest standards of technical diagramming. The generated output must follow all instructions, constraints, and best practices detailed in this document. You will operate in a zero-tolerance mode for syntactical errors, especially concerning predefined keyword values. The fundamental principle is the separation of concerns: the logical structure of the diagram must be defined independently of its visual styling. The following chapters are structured to enforce this principle.
8
+
9
+ ## Chapter 1: Core Instructions for D2 Diagram Generation
239
10
 
240
- "Your App": "Application Code" {
241
- shape: rectangle
242
- }
11
+ This chapter establishes the foundational rules for generating the structure and logic of a D2 diagram. It prioritizes semantic correctness and adherence to diagramming principles over aesthetic concerns, which are addressed in Chapter 2.
243
12
 
244
- "Blocklet Services": "Remote APIs" {
245
- shape: cylinder
246
- }
13
+ ### 1.1 Foundational Principles of Technical Diagramming
247
14
 
248
- "Your App" -> "SDK": "Import & Use"
249
- "SDK" -> "Blocklet Services": "Authenticated Requests"
250
- "Blocklet Services" -> "SDK": "Responses & Tokens"
251
- ```
252
- - good:
253
- ```d2
254
- direction: down
255
-
256
- "SDK": "@blocklet/js-sdk" {
257
- shape: package
258
- grid-columns: 1
259
-
260
- "Core Instance": {
261
- shape: rectangle
262
- "BlockletSDK": "Main SDK Class"
263
- }
264
-
265
- "Services": {
266
- grid-columns: 3
267
- "AuthService": "User Authentication" {
268
- shape: class
269
- }
270
- "TokenService": "Token Management" {
271
- shape: class
272
- }
273
- "UserSessionService": "Session Management" {
274
- shape: class
275
- }
276
- "BlockletService": "Blocklet Metadata" {
277
- shape: class
278
- }
279
- "FederatedService": "Federated Login" {
280
- shape: class
281
- }
282
- }
283
-
284
- "HTTP Clients": {
285
- grid-columns: 2
286
- "createAxios": "Axios-based Client" {
287
- shape: rectangle
288
- }
289
- "createFetch": "Fetch-based Client" {
290
- shape: rectangle
291
- }
292
- }
293
- }
294
-
295
- "Your App": "Application Code" {
296
- shape: rectangle
297
- }
15
+ All generated diagrams must adhere to established best practices to ensure they are effective communication tools, not merely decorative images. The primary audience for these diagrams is software engineers who need to understand a system's architecture, data flow, or component interactions.
298
16
 
299
- "Blocklet Services": "Remote APIs" {
300
- shape: cylinder
301
- }
17
+ #### Clarity and Conciseness
18
+ Use clear, simple language for all labels. Text within shapes should be minimal, ideally one to two words. Avoid lengthy descriptions in labels. For extensive explanations, use an accompanying Markdown text block. The goal is to reduce cognitive load and make the diagram's structure immediately apparent. Long labels should be manually broken with newline characters (`\n`) to ensure they render correctly and do not disrupt the layout.
302
19
 
303
- "Your App" -> "SDK": "Import & Use"
304
- "SDK" -> "Blocklet Services": "Authenticated Requests"
305
- "Blocklet Services" -> "SDK": "Responses & Tokens"
306
- ```
307
- - 必须保证一个图中,所有的节点都是有关联的,不需要为图表设置 legend,如果有节点不存在关联性,则应该移除这些节点,或者拆分成多个独立的图表
308
- - bad:
309
- ```d2
310
- direction: down
311
-
312
- "Your App": {
313
- shape: rectangle
314
- }
20
+ - **Bad Practice (Overly descriptive label):**
21
+ ```d2
22
+ TokenService: {
23
+ label: "TokenService (Handles token storage & refresh)"
24
+ }
25
+ ```
26
+ - **Good Practice (Concise label):**
27
+ ```d2
28
+ TokenService: {
29
+ label: "TokenService"
30
+ }
31
+ ```
32
+
33
+ - **Bad Practice (Verbose connection label):**
34
+ ```d2
35
+ User-Login -> Session-Creation: "User submits login form with credentials"
36
+ ```
37
+ - **Good Practice (Concise connection label):**
38
+ ```d2
39
+ User-Login -> Session-Creation: "login"
40
+ ```
41
+
42
+ - **Bad Practice (Manual line breaks):**
43
+ ```d2
44
+ "AuthService": {
45
+ label: "AuthService (Handles user authentication, profile management, privacy settings, and related actions)"
46
+ }
47
+ ```
48
+ - **Good Practice (Manual line breaks):**
49
+ ```d2
50
+ "AuthService": {
51
+ label: "AuthService\n(Handles user authentication,\nprofile management, privacy settings,\nand related actions)"
52
+ }
53
+ ```
54
+
55
+ #### Logical Flow and Organization
56
+ The diagram's layout must represent a logical flow, whether of data, control, or time. The visual arrangement of elements should guide the reader's eye naturally through the process or structure being depicted. For optimal readability on web pages, the overall layout direction should be set to `down`.
57
+
58
+ ```d2
59
+ direction: down
60
+ ```
61
+
62
+ Lines should be straight and avoid crossing where possible to maintain readability. All nodes within a single diagram must be interconnected to form a cohesive graph. If unrelated groups of nodes exist, they should be split into separate diagrams.
63
+
64
+ #### Appropriate Diagram Type
65
+ Based on the input description, select the most suitable diagram type. For interactions over time, a sequence diagram is appropriate. For static system structure, a component or class diagram should be used. For process flows, a flowchart-style diagram is best. D2 is specifically designed for documenting software and does not support general-purpose charts like mind maps or Gantt charts; these are considered bloat and must not be generated.
66
+
67
+ #### Consistent Abstraction Level
68
+ Maintain a consistent level of detail throughout a single diagram. Do not mix high-level architectural concepts (e.g., "API Gateway") with low-level implementation details (e.g., a specific function name) unless the input explicitly requires this hybrid view.
69
+
70
+ ### 1.2 D2 Core Syntax: The Grammar of Diagrams
71
+
72
+ This section provides the precise and unambiguous definition of D2's fundamental syntax for creating the structural elements of a diagram.
73
+
74
+ #### Shapes and Labels
75
+
76
+ - A shape is defined by its key. By default, the key also serves as the shape's label. Shape keys are case-insensitive. For example, `api_gateway` creates a rectangle shape with the label "api_gateway".
77
+ - To assign a different, case-sensitive label, use a colon: `api_gateway: "API Gateway"`.
78
+ - **Critical Rule**: Node IDs (keys) containing special characters (e.g., `@`, ` `, `/`) must be normalized by replacing these characters with a hyphen (`-`). The original name must then be assigned to the `label` attribute.
79
+ - **Bad Practice:**
80
+ ```d2
81
+ "@blocklet/js-sdk": {
82
+ shape: rectangle
83
+ }
84
+ ```
85
+ - **Good Practice:**
86
+ ```d2
87
+ blocklet-js-sdk: {
88
+ label: "@blocklet/js-sdk"
89
+ shape: rectangle
90
+ }
91
+ ```
92
+ - Labels containing reserved characters (e.g., `(`, `)`, `$`, `-`, `:`) or spaces must be enclosed in single or double quotes to prevent parsing errors. Example: `"user-service:v1"`.
93
+ - Multiple shapes can be declared on a single line using a semicolon as a delimiter. Example: `service_a; service_b; service_c`.
315
94
 
316
- "SDK Request Helper": {
317
- label: "@blocklet/js-sdk (createAxios / createFetch)"
318
- shape: package
319
- }
95
+ #### Connections and Arrowheads
320
96
 
321
- "Blocklet Service": {
322
- shape: cylinder
323
- }
97
+ Connections define relationships between shapes. The following syntaxes are valid:
324
98
 
325
- "Token Refresh Endpoint": {
326
- label: "/api/did/refreshSession"
327
- shape: rectangle
328
- }
99
+ - `->`: Uni-directional connection
100
+ - `<->`: Bi-directional connection
101
+ - `--`: A connection with no direction specified
102
+ - `<-`: Uni-directional connection (reversed)
103
+
104
+ Labels can be added to connections by appending a colon and the label text. Example: `user -> api: "requests data via HTTPS"`.
105
+
106
+ Custom arrowheads are specified by defining a special shape on the connection named `source-arrowhead` or `target-arrowhead`. This is essential for creating compliant UML or entity-relationship diagrams. Example: `a -> b: { target-arrowhead: { shape: diamond } }`.
107
+
108
+ #### Containers
109
+
110
+ Containers are used to group related shapes, representing subsystems or logical boundaries. They are defined using nested blocks `{}` or dot notation.
111
+
112
+ - **Block Notation**: `aws: { ec2: "EC2 Instance"; s3: "S3 Bucket" }`
113
+ - **Dot Notation**: `aws.ec2: "EC2 Instance"`. This is useful for defining shapes and connections in a single line.
114
+
115
+ Containers can be nested to any depth to represent hierarchical systems. Connections between shapes residing in the same container should be defined within that container's block.
116
+
117
+ Container labels can be assigned using shorthand (`gcloud: "Google Cloud" {}`) or the reserved label keyword (`gcloud: { label: "Google Cloud" }`).
118
+
119
+ When a container has more than three child nodes, use `grid-columns` to limit the number of columns in a single row, preferably to 2 or at most 3, to improve readability. If a container has nested containers, it is recommended to use `grid-gap` (with a value greater than 100) to increase the spacing between them.
120
+
121
+ - **Bad Practice (Grid Layout):**
122
+ ```d2
123
+ Instance: {
124
+ A: "A"
125
+ B: "B"
126
+ C: "C"
127
+ D: "D"
128
+ E: "E"
129
+ F: "F"
130
+ }
131
+ ```
132
+ - **Good Practice (Grid Layout):**
133
+ ```d2
134
+ Instance: {
135
+ grid-columns: 2
136
+ A: "A"
137
+ B: "B"
138
+ C: "C"
139
+ D: "D"
140
+ }
141
+ ```
142
+
143
+ - **Good Practice (Grid Gap for Nested Containers):**
144
+ ```d2
145
+ SDK-blocklet-js-sdk: {
146
+ shape: rectangle
147
+ grid-columns: 1
148
+ grid-gap: 100
149
+ // ... nested containers
150
+ }
151
+ ```
329
152
 
330
- "Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
153
+ #### Text and Code Blocks
331
154
 
332
- "SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
333
- style {
334
- stroke-dash: 2
335
- }
336
- }
155
+ - For multi-line descriptions or annotations that are part of the diagram, use Markdown blocks. This is initiated with `|md`. Example: `explanation: |md # System Overview \n - Component A does X. \n - Component B does Y. |`.
156
+ - To display formatted code snippets, specify the programming language after the pipe. D2 supports most common languages. Example: `code_sample: |go func main() { fmt.Println("Hello") } |`.
337
157
 
338
- "Success Path": {
339
- style.stroke: "#52c41a"
158
+ ### 1.3 Specialized Diagram Constructs
340
159
 
341
- "Blocklet Service" -> "SDK Request Helper": "3a. 200 OK (Token Valid)"
342
- "SDK Request Helper" -> "Your App": "4a. Returns Data"
343
- }
160
+ D2 provides special syntax for creating complex, structured diagram types commonly used in software documentation.
344
161
 
162
+ #### Sequence Diagrams
345
163
 
346
- "Token Renewal Path": {
347
- style.stroke: "#faad14"
164
+ - A sequence diagram is created by setting `shape: sequence_diagram` on a container.
165
+ - **Critical Rule**: The order of statements within the `sequence_diagram` block is paramount. Unlike other D2 diagrams where layout is algorithmic, here the vertical order of messages is determined by their declaration order in the source code.
166
+ - Actors are defined like regular shapes (e.g., `alice: "Alice"`). Messages are represented as connections between actors.
167
+ - Lifeline activations, also known as spans, are defined by connecting to a nested object on an actor. This syntax indicates the start and end of an operation on an actor's lifeline. Example: `alice.t1 -> bob: "invoke operation"`.
168
+ - Groups (fragments) like loops or optional blocks are defined using nested containers that are not connected to anything. Example: `loop: { alice -> bob: "ping"; bob -> alice: "pong" }`.
348
169
 
349
- "Blocklet Service" -> "SDK Request Helper": "3b. 401 Unauthorized (Token Expired)"
350
- "SDK Request Helper" -> "Token Refresh Endpoint": "4b. Request New Token"
351
- "Token Refresh Endpoint" -> "SDK Request Helper": "5b. New Tokens"
352
- "SDK Request Helper" -> "Blocklet Service": "6b. Retry Original Request"
353
- "Blocklet Service" -> "SDK Request Helper": "7b. 200 OK" {
354
- style.stroke: "#52c41a"
355
- }
356
- "SDK Request Helper" -> "Your App": "8b. Returns Data Transparently" {
357
- style.stroke: "#52c41a"
358
- }
359
- }
360
- ```
361
- - good:
362
- ```d2
363
- direction: down
170
+ #### UML Class Diagrams
171
+
172
+ - A class diagram is created by setting `shape: class` on a shape.
173
+ - Fields and methods are defined as key-value pairs within the shape's block.
174
+ - Visibility is specified with a prefix: `+` for public (this is the default), `-` for private, and `#` for protected.
175
+ - Methods are identified by keys containing parentheses `()`. The value of the key specifies the return type. Example: `D2Parser: { shape: class; +reader: io.RuneReader; "-lookahead:rune"; "+peek(): (rune, eof bool)" }`.
176
+
177
+ #### SQL Table Diagrams
178
+
179
+ - An SQL table is created by setting `shape: sql_table`.
180
+ - Columns are defined as keys, with their data type as the value.
181
+ - Constraints (e.g., `primary_key`, `foreign_key`, `unique`) are defined in a nested block for the relevant column. Example: `users: { shape: sql_table; id: int { constraint: primary_key }; email: string { constraint: unique } }`.
182
+ - Foreign key relationships are established by creating a standard connection from the foreign key column in one table to the primary key column in another. Example: `orders.user_id -> users.id`.
183
+
184
+
185
+ ### 1.4 Strict Adherence to Predefined Keyword Values
186
+
187
+ Many D2 keywords accept only a specific, predefined set of string values. These function like enumerations in a programming language. LLMs often make mistakes by generating plausible but invalid values for these keywords. To prevent this, the following rules are non-negotiable.
188
+
189
+ **Core Directive**: For any D2 keyword listed in the tables below, you MUST use one of the provided values EXACTLY as written. The values are case-sensitive. You are FORBIDDEN from inventing, assuming, or modifying these values in any way. This is a critical instruction to prevent compilation errors.
190
+
191
+ #### D2 Shape Catalog
192
+
193
+ The `shape` attribute must be assigned one of the following values:
194
+
195
+ | Shape Value | Description |
196
+ |-------------|-------------|
197
+ | `rectangle` | The default shape. A standard rectangle. |
198
+ | `square` | A shape that maintains a 1:1 aspect ratio. |
199
+ | `cylinder` | A cylinder, typically representing a database or data store. |
200
+ | `queue` | A shape representing a message queue. |
201
+ | `diamond` | A diamond shape, often used for decisions in flowcharts. |
202
+ | `oval` | An oval or ellipse, often used for start/end terminators. |
203
+ | `circle` | A perfect circle, maintains a 1:1 aspect ratio. |
204
+ | `c4-person` | A more detailed person icon based on the C4 model. |
205
+ | `sequence_diagram` | A special container shape for rendering sequence diagrams. |
206
+
207
+ - If person shape is needed, use `c4-person` replace `person`.
208
+ - **Forbidden Attributes**: Do not use any other shape, like: `package`, `step`, `callout`, `stored_data`, `person`, `document`, `multiple_document`, `class`, `sql_table`, `image`, `hexagon`, `parallelogram`. These shapes are either deprecated, not suitable for software diagrams, or have been replaced by more appropriate alternatives.
209
+
210
+ #### Predefined Keyword Values (Master Reference)
211
+
212
+ This table centralizes all other keywords with a restricted set of valid values.
213
+
214
+ | Keyword | Valid Values |
215
+ |---------|--------------|
216
+ | `direction` | `up`, `down`, `left`, `right` |
217
+ | `style.fill-pattern` | `dots`, `lines`, `grain`, `none` |
218
+ | `style.text-transform` | `uppercase`, `lowercase`, `title`, `none` |
219
+ | `style.font` | `mono` |
220
+ | UML Visibility | `+` (public), `-` (private), `#` (protected) |
221
+ | Arrowhead shape | `triangle`, `arrow`, `diamond`, `circle`, `box`, `cf-one`, `cf-one-required`, `cf-many`, `cf-many-required`, `cross`, `unfilled triangle` |
222
+
223
+ ### 1.5 Known Limitations and Error Handling
224
+
225
+ To generate robust D2 code, be aware of the language's limitations and common sources of errors.
226
+
227
+ - **Quoting and Escaping**: Always enclose keys or labels that are reserved D2 keywords in quotes. Example: `shape_A: { "label": "My Label" }`. If a string must contain a `#` character (which normally signifies a comment), it must be quoted.
228
+ - **Non-ASCII Characters**: While D2 supports Unicode, care must be taken to use ASCII versions of special characters like the colon (`:`) for defining labels, as visually similar Unicode characters will not be parsed correctly.
229
+ - **Styling**: Use colors and styles (like `style.fill`) sparingly. Should apply them to represent specific states (e.g., success, warning, error), should apply theme to diffrent purpose shape (Use colors with less contrast, not too prominent), not for arbitrary decoration.
230
+ - **Forbidden Attributes**: Do not use the `tooltip` attribute. Interactive features should be handled by accompanying text. Similarly, do not use `animate: true` for individual shapes or connections.
231
+ - **Bad Practice:**
232
+ ```d2
233
+ "AuthService": {
234
+ label: "AuthService"
235
+ tooltip: "Manages user profiles, privacy, and authentication actions"
236
+ animate: true
237
+ }
238
+ ```
239
+ - **Good Practice:**
240
+ ```d2
241
+ AuthService: {
242
+ label: "AuthService"
243
+ }
244
+ ```
245
+
246
+ ### 1.6 Shape Usage Best Practices
247
+
248
+ Selecting the correct shape is crucial for conveying the intended meaning of a component at a glance. Use the following guidelines to choose the most appropriate shape for different elements in your software diagrams.
364
249
 
365
- "Your App": {
366
- shape: rectangle
367
- }
250
+ #### General Purpose & Processes
368
251
 
369
- "SDK Request Helper": {
370
- label: "@blocklet/js-sdk (createAxios / createFetch)"
371
- shape: package
372
- }
252
+ - **rectangle**: The default shape, suitable for any general-purpose component, service, or action step
253
+ - **oval**: Traditionally used to indicate the start or end points of a process or flowchart
254
+ - **diamond**: Reserved for decision points in flowcharts, where a process diverges based on a condition
255
+
256
+ #### Data and Storage
373
257
 
374
- "Blocklet Service": {
375
- shape: cylinder
376
- }
258
+ - **cylinder**: The standard shape for representing a database or data store
259
+ - **queue**: Specifically used to represent a message queue
260
+
261
+ #### Actors and Documents
262
+
263
+ - **c4-person**: Represents a human user or an external actor interacting with the system.
264
+ - **image**: Used for embedding logos or icons, especially when representing third-party services or well-known platforms. When using this shape, ensure the `icon` attribute points to a valid image URL.
265
+ The following logos are predefined for common services:
266
+ - "NFT Studio": `https://www.arcblock.io/image-bin/uploads/5542354d3d33e5fcd211562ee734da01.png`
267
+ - "AIGNE": `https://www.arcblock.io/image-bin/uploads/89a24f04c34eca94f26c9dd30aec44fc.png`
268
+ - "Aistro": `https://www.arcblock.io/image-bin/uploads/b83c22b9f92e4837c2e21bb6fb831f1a.png`
269
+
270
+ - "Blocklet Launcher": `https://www.arcblock.io/image-bin/uploads/6cb1e2aa568509e1a874bf7ae1666c26.svg`
271
+ - "Blocklet Store": `https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png`
272
+ - "Web3 Kit": `https://www.arcblock.io/image-bin/uploads/f409e3bdc7a2b42ba8fba9bae286aeda.svg`
273
+ - "AI Kit": `https://www.arcblock.io/image-bin/uploads/9745710dce319d9bf117516ad5d1f811.svg`
274
+
275
+ - "Blocklet": `https://www.arcblock.io/image-bin/uploads/eb1cf5d60cd85c42362920c49e3768cb.svg`
276
+ - "Blocklet Server": `https://www.arcblock.io/image-bin/uploads/eb1cf5d60cd85c42362920c49e3768cb.svg`
277
+ - "DID Spaces": `https://www.arcblock.io/image-bin/uploads/fb3d25d6fcd3f35c5431782a35bef879.svg`
278
+
279
+ - "DID": `https://www.arcblock.io/image-bin/uploads/71eea946246150766324008427d2f63d.svg`
280
+ - "DID Wallet": `https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg`
281
+ - "DID Connect": `https://www.arcblock.io/image-bin/uploads/71eea946246150766324008427d2f63d.svg`
282
+ - "DID Names": `https://www.arcblock.io/image-bin/uploads/db36f9832a99d4dccb21a30ff269bb22.svg`
283
+
284
+ - **Bad Practice:**
285
+ ```d2
286
+ DID-Wallet: {
287
+ shape: image
288
+ icon: https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg
289
+ }
290
+ Blocklet-Store: {
291
+ shape: image
292
+ icon: https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png
293
+ }
294
+ DID-Wallet -> Blocklet-Store: "Login"
295
+ ```
296
+ - **Good Practice:**
297
+ ```d2
298
+ DID-Wallet: {
299
+ label: "DID Wallet"
300
+ icon: https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg
301
+ }
302
+ Blocklet-Store: {
303
+ label: "Blocklet Store"
304
+ icon: https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png
305
+ }
306
+ DID-Wallet -> Blocklet-Store: "Login"
307
+ ```
308
+
309
+ #### Specialized Diagram Types
310
+
311
+ - **sequence_diagram**: These are not general-purpose shapes. They are special containers that render specific, structured diagram types and must be used exclusively for that purpose.
312
+
313
+ ## Chapter 2: Best Practices for real case
314
+
315
+ #### Connections between shapes
316
+
317
+ Ensure all connections are defined at the root level of the diagram, not nested within containers. This maintains clarity and prevents layout issues.
318
+ Ensure that the shape names used in connections are accurate and match the actual node identifiers. When connecting shapes nested within containers, always use the full, qualified name (including all parent containers) to reference the target shape correctly.
319
+
320
+ - **Bad Practice:**
321
+ ```d2
322
+ direction: down
323
+
324
+ User: {
325
+ shape: c4-person
326
+ }
327
+
328
+ App: {
329
+ label: "Your Blocklet Application"
330
+ shape: rectangle
331
+
332
+ Uploader-Component: {
333
+ label: "Uploader Component"
334
+ shape: rectangle
335
+ }
336
+
337
+ Backend-Server: {
338
+ label: "Backend Server"
339
+ shape: rectangle
340
+
341
+ Uploader-Server: {
342
+ label: "@blocklet/uploader-server"
343
+ }
377
344
 
378
- "Token Refresh Endpoint": {
379
- label: "/api/did/refreshSession"
380
- shape: rectangle
381
- }
382
-
383
- "Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
384
-
385
- "SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
386
- style {
387
- stroke-dash: 2
388
- }
389
- }
390
- ```
391
- ```d2
392
- "Success Path": {
393
- style.stroke: "#52c41a"
394
-
395
- "Blocklet Service" -> "SDK Request Helper": "3a. 200 OK (Token Valid)"
396
- "SDK Request Helper" -> "Your App": "4a. Returns Data"
397
- }
398
- ```
399
- ```d2
400
- "Token Renewal Path": {
401
- style.stroke: "#faad14"
402
-
403
- "Blocklet Service" -> "SDK Request Helper": "3b. 401 Unauthorized (Token Expired)"
404
- "SDK Request Helper" -> "Token Refresh Endpoint": "4b. Request New Token"
405
- "Token Refresh Endpoint" -> "SDK Request Helper": "5b. New Tokens"
406
- "SDK Request Helper" -> "Blocklet Service": "6b. Retry Original Request"
407
- "Blocklet Service" -> "SDK Request Helper": "7b. 200 OK" {
408
- style.stroke: "#52c41a"
409
- }
410
- "SDK Request Helper" -> "Your App": "8b. Returns Data Transparently" {
411
- style.stroke: "#52c41a"
412
- }
413
- }
414
- ```
415
- - 当有关联关系的节点,处于一个节点内部时,则它们的关联关系也应该写在节点内部
416
- - bad
417
- ```d2
418
- direction: down
419
-
420
- "@blocklet/js-sdk": {
421
- shape: package
422
-
423
- "Main SDK Instance": {
424
- shape: rectangle
425
- "BlockletSDK Class": "Main entry point"
426
- "getBlockletSDK()": "Singleton factory"
427
- }
428
-
429
- "HTTP Clients": {
430
- shape: rectangle
431
- grid-columns: 2
432
- "createAxios()": "Axios-based client"
433
- "createFetch()": "Fetch-based client"
434
- }
435
-
436
- "Core Services": {
437
- shape: rectangle
438
- grid-columns: 3
439
- "AuthService": "User authentication"
440
- "TokenService": "Token management"
441
- "BlockletService": "Blocklet metadata"
442
- "UserSessionService": "Session management"
443
- "FederatedService": "Federated login"
444
- "ComponentService": "Component utilities"
445
- }
446
- }
447
-
448
- "Main SDK Instance" -> "HTTP Clients": "Uses for requests"
449
- "Main SDK Instance" -> "Core Services": "Provides access to"
450
- "HTTP Clients" -> "Core Services": "Configured with"
451
- ```
452
- - good
453
- ```d2
454
- direction: down
455
-
456
- "@blocklet/js-sdk": {
457
- shape: package
458
-
459
- "Main SDK Instance": {
460
- shape: rectangle
461
- "BlockletSDK Class": "Main entry point"
462
- "getBlockletSDK()": "Singleton factory"
463
- }
464
-
465
- "HTTP Clients": {
466
- shape: rectangle
467
- grid-columns: 2
468
- "createAxios()": "Axios-based client"
469
- "createFetch()": "Fetch-based client"
470
- }
471
-
472
- "Core Services": {
473
- shape: rectangle
474
- grid-columns: 3
475
- "AuthService": "User authentication"
476
- "TokenService": "Token management"
477
- "BlockletService": "Blocklet metadata"
478
- "UserSessionService": "Session management"
479
- "FederatedService": "Federated login"
480
- "ComponentService": "Component utilities"
481
- }
482
-
483
- "Main SDK Instance" -> "HTTP Clients": "Uses for requests"
484
- "Main SDK Instance" -> "Core Services": "Provides access to"
485
- "HTTP Clients" -> "Core Services": "Configured with"
486
- }
487
- ```
488
- - bad:
489
- ```d2
490
- direction: down
491
-
492
- "Your App": {
493
- shape: rectangle
494
- }
495
-
496
- "SDK Request Helper": {
497
- label: "@blocklet/js-sdk (createAxios / createFetch)"
498
- shape: package
499
- }
500
-
501
- "Blocklet Service": {
502
- shape: cylinder
503
- }
504
-
505
-
506
- "Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
507
-
508
- "SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
509
- style {
510
- stroke-dash: 2
511
- }
512
- }
513
-
514
- "Token Renewal Path": {
515
- style.stroke: "#faad14"
516
-
517
- "Blocklet Service" -> "SDK Request Helper": "3. 401 Unauthorized (Token Expired)"
518
- "SDK Request Helper" -> "Token Refresh Endpoint": "4. Request New Token"
519
- "Token Refresh Endpoint" -> "SDK Request Helper": "5. New Tokens Received"
520
- "SDK Request Helper" -> "Blocklet Service": "6. Retry Original Request with New Token"
521
- "Blocklet Service" -> "SDK Request Helper": "7. 200 OK" {
522
- style.stroke: "#52c41a"
523
- }
524
- "SDK Request Helper" -> "Your App": "8. Returns Data Transparently" {
525
- style.stroke: "#52c41a"
526
- }
527
- }
528
- ```
529
- - good:
530
- ```d2
531
- direction: down
532
-
533
- "Your App": {
534
- shape: rectangle
535
- }
536
-
537
- "SDK Request Helper": {
538
- label: "@blocklet/js-sdk (createAxios / createFetch)"
539
- shape: package
540
- }
541
-
542
- "Blocklet Service": {
543
- shape: cylinder
544
- }
545
-
546
-
547
- "Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
548
-
549
- "SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
550
- style {
551
- stroke-dash: 2
552
- }
553
- }
554
-
555
- "Blocklet Service" -> "SDK Request Helper": "3. 401 Unauthorized (Token Expired)"
556
- "SDK Request Helper" -> "Token Refresh Endpoint": "4. Request New Token"
557
- "Token Refresh Endpoint" -> "SDK Request Helper": "5. New Tokens Received"
558
- "SDK Request Helper" -> "Blocklet Service": "6. Retry Original Request with New Token"
559
- "Blocklet Service" -> "SDK Request Helper": "7. 200 OK" {
560
- style.stroke: "#52c41a"
561
- }
562
- "SDK Request Helper" -> "Your App": "8. Returns Data Transparently" {
563
- style.stroke: "#52c41a"
564
- }
565
- ```
566
- - 如果整个图表只有一个容器节点,就不要增加这个容器节点,直接将内部的节点放在最外层
567
- - bad:
568
- ```d2
569
- direction: down
570
-
571
- "User Sessions Flow": {
572
- shape: package
573
- grid-columns: 1
574
-
575
- "User Login": {
576
- shape: person
577
- style.fill: "#e6f7ff"
578
- }
579
-
580
- "Session Creation": {
581
- shape: rectangle
582
- style.fill: "#f6ffed"
583
- }
584
-
585
- "Session Storage": {
586
- shape: cylinder
587
- style.fill: "#fff7e6"
588
- }
589
-
590
- "Multi-Device Access": {
591
- shape: package
592
- grid-columns: 3
593
- "Web Browser": {
594
- shape: rectangle
595
- }
596
- "Mobile App": {
597
- shape: rectangle
598
- }
599
- "Desktop App": {
600
- shape: rectangle
601
- }
602
- }
603
- "User Login" -> "Session Creation": "Authenticate"
604
- "Session Creation" -> "Session Storage": "Store session"
605
- "Session Storage" -> "Multi-Device Access": "Access from devices"
606
- }
607
- ```
608
- - good:
609
- ```d2
610
- direction: down
611
-
612
- "User Login": {
613
- shape: person
614
- style.fill: "#e6f7ff"
615
- }
616
-
617
- "Session Creation": {
618
- shape: rectangle
619
- style.fill: "#f6ffed"
620
- }
621
-
622
- "Session Storage": {
345
+ DB: {
346
+ label: "Database"
623
347
  shape: cylinder
624
- style.fill: "#fff7e6"
625
- }
626
-
627
- "Multi-Device Access": {
628
- shape: package
629
- grid-columns: 3
630
- "Web Browser": {
631
- shape: rectangle
632
- }
633
- "Mobile App": {
634
- shape: rectangle
635
- }
636
- "Desktop App": {
637
- shape: rectangle
638
- }
639
- }
640
- "User Login" -> "Session Creation": "Authenticate"
641
- "Session Creation" -> "Session Storage": "Store session"
642
- "Session Storage" -> "Multi-Device Access": "Access from devices"
643
- ```
644
- - 某些情况下,单纯的设置 `direction: down` 还无法控制图表的整体方向,可以再结合 `grid-columns: 1` 来进行设置
645
- - bad:
646
- ```d2
647
- direction: down
648
-
649
- "Your Application": {
650
- shape: rectangle
651
348
  }
652
349
 
653
- "@blocklet/js-sdk": {
654
- shape: package
655
- grid-columns: 1
350
+ Uploader-Server -> DB: "4. Save metadata"
351
+ DB -> Uploader-Server: "5. Return DB record"
352
+ }
656
353
 
657
- "AuthService": {
658
- shape: class
659
- }
660
- }
354
+ User -> Uploader-Component: "1. Drop file"
355
+ Uploader-Component -> Backend-Server.Uploader-Server: "2. Upload file chunks (Tus)"
356
+ Backend-Server.Uploader-Server -> Backend-Server.Uploader-Server: "3. Trigger backend onUploadFinish"
357
+ Backend-Server.Uploader-Server -> Uploader-Component: "6. Send JSON response"
358
+ Uploader-Component -> Uploader-Component: "7. Trigger frontend onUploadFinish"
359
+ Uploader-Component -> User: "8. Update UI with file URL"
360
+ }
361
+ ```
362
+ - **Good Practice:**
363
+ ```d2
364
+ direction: down
661
365
 
662
- "Blocklet API Endpoints": {
663
- shape: cylinder
664
- grid-columns: 2
665
- "/api/user/profile": {}
666
- "/api/user/privacy/config": {}
667
- "/api/user/notification/config": {}
668
- "/api/user/logout": {}
669
- "/api/user/follow/{did}": {}
670
- "/api/user": {}
671
- }
366
+ User: {
367
+ shape: c4-person
368
+ }
672
369
 
673
- "Your Application" -> "@blocklet/js-sdk".AuthService: "e.g., sdk.auth.getProfile()"
674
- "@blocklet/js-sdk".AuthService -> "Blocklet API Endpoints": "Makes authenticated API calls"
675
- ```
676
- - good:
677
- ```d2
678
- direction: down
679
- grid-columns: 1
370
+ App: {
371
+ label: "Your Blocklet Application"
372
+ shape: rectangle
680
373
 
681
- "Your Application": {
682
- shape: rectangle
683
- }
374
+ Uploader-Component: {
375
+ label: "Uploader Component"
376
+ shape: rectangle
377
+ }
684
378
 
685
- "@blocklet/js-sdk": {
686
- shape: package
687
- grid-columns: 1
379
+ Backend-Server: {
380
+ label: "Backend Server"
381
+ shape: rectangle
688
382
 
689
- "AuthService": {
690
- shape: class
691
- }
383
+ Uploader-Server: {
384
+ label: "@blocklet/uploader-server"
692
385
  }
693
386
 
694
- "Blocklet API Endpoints": {
387
+ DB: {
388
+ label: "Database"
695
389
  shape: cylinder
696
- grid-columns: 2
697
- "/api/user/profile": {}
698
- "/api/user/privacy/config": {}
699
- "/api/user/notification/config": {}
700
- "/api/user/logout": {}
701
- "/api/user/follow/{did}": {}
702
- "/api/user": {}
703
- }
704
-
705
- "Your Application" -> "@blocklet/js-sdk".AuthService: "e.g., sdk.auth.getProfile()"
706
- "@blocklet/js-sdk".AuthService -> "Blocklet API Endpoints": "Makes authenticated API calls"
707
- ```
708
- - **非常重要** 当容器节点中子节点个数与 `grid-columns` 值相同时,则应该去掉容器节点中的 `grid-columns` 字段
709
- - bad:
710
- ```d2
711
- "@blocklet/js-sdk": {
712
- shape: package
713
- grid-columns: 1
714
-
715
- "AuthService": {
716
- shape: class
717
- }
718
- }
719
- ```
720
- - good:
721
- ```d2
722
- "@blocklet/js-sdk": {
723
- shape: package
724
-
725
- "AuthService": {
726
- shape: class
727
- }
728
- }
729
- ```
730
- - bad:
731
- ```d2
732
- "Browser Storage": {
733
- shape: package
734
- grid-columns: 2
735
-
736
- "Cookies": {
737
- shape: document
738
- "Session Token": {}
739
- }
740
-
741
- "LocalStorage": {
742
- shape: stored_data
743
- "Refresh Token": {}
744
- }
745
- }
746
- ```
747
- - good:
748
- ```d2
749
- "Browser Storage": {
750
- shape: package
751
-
752
- "Cookies": {
753
- shape: document
754
- "Session Token": {}
755
- }
756
-
757
- "LocalStorage": {
758
- shape: stored_data
759
- "Refresh Token": {}
760
- }
761
- }
762
- ```
763
- - 当一个容器节点外部有节点与当前容器节点内部节点相互关联时,应该将这些节点放在同一层级
764
- - bad:
765
- ```d2
766
- direction: down
767
-
768
- "Federated Login Group": {
769
- shape: package
770
-
771
- "Master App": {
772
- shape: rectangle
773
- style.stroke: "#0052cc"
774
- style.stroke-width: 2
775
- "Provides central authentication"
776
- }
777
-
778
- "Member App 1 (Current App)": {
779
- shape: rectangle
780
- "User interacts here"
781
- }
782
-
783
- "Member App 2": {
784
- shape: rectangle
785
- }
786
-
787
- "Master App" -> "Member App 1 (Current App)": "Shares user session"
788
- "Master App" -> "Member App 2": "Shares user session"
789
- }
790
-
791
- User: {
792
- shape: person
793
- }
794
-
795
- User -> "Member App 1 (Current App)": "Logs in via Master App"
796
- ```
797
- - good:
798
- ```d2
799
- direction: down
800
-
801
- "Federated Login Group": {
802
- shape: package
803
-
804
- "Master App": {
805
- shape: rectangle
806
- style.stroke: "#0052cc"
807
- style.stroke-width: 2
808
- "Provides central authentication"
809
- }
810
-
811
- "Member App 1 (Current App)": {
812
- shape: rectangle
813
- "User interacts here"
814
- }
815
-
816
- "Member App 2": {
817
- shape: rectangle
818
- }
819
-
820
- "Master App" -> "Member App 1 (Current App)": "Shares user session"
821
- "Master App" -> "Member App 2": "Shares user session"
822
-
823
- User: {
824
- shape: person
825
- }
826
-
827
- User -> "Member App 1 (Current App)": "Logs in via Master App"
828
390
  }
391
+ }
392
+ }
829
393
 
830
- ```
831
- - **非常重要** 当存在多层容器节点嵌套时,外层的容器节点应该使用 `grid-columns: 1`
832
- - bad:
833
- ```d2
834
- direction: down
394
+ User -> App.Uploader-Component: "1. Drop file"
395
+ App.Uploader-Component -> App.Backend-Server.Uploader-Server: "2. Upload file"
396
+ App.Backend-Server.Uploader-Server -> App.Backend-Server.Uploader-Server: "3. Backend onUploadFinish"
397
+ App.Backend-Server.Uploader-Server -> App.Backend-Server.DB: "4. Save metadata"
398
+ App.Backend-Server.DB -> App.Backend-Server.Uploader-Server: "5. Return DB record"
399
+ App.Backend-Server.Uploader-Server -> App.Uploader-Component: "6. Send JSON response"
400
+ App.Uploader-Component -> App.Uploader-Component: "7. Frontend onUploadFinish"
401
+ App.Uploader-Component -> User: "8. Update UI"
402
+ ```
835
403
 
836
- "User Account": {
837
- shape: person
838
- }
404
+ > Move all connections to root, and the `User` shape is outside the `App` container, so the connection must reference the full path `App.Uploader-Component`.
839
405
 
840
- "Sessions": {
841
- shape: package
842
- grid-columns: 3
406
+ #### Shape name should not contain special characters or quotes
407
+ - **Bad Practice:**
408
+ ```d2
409
+ direction: down
843
410
 
844
- "Web Browser Session": {
845
- shape: rectangle
846
- "IP: 192.168.1.10"
847
- "UA: Chrome on macOS"
848
- "Status: online"
849
- }
411
+ "@blocklet/app": {
412
+ label: "Your Blocklet Application"
413
+ shape: rectangle
850
414
 
851
- "iOS App Session": {
852
- shape: rectangle
853
- "IP: 10.0.0.5"
854
- "UA: MyApp/1.2 iOS"
855
- "Status: online"
856
- }
415
+ "uploader-component": {
416
+ label: "<Uploader /> Component"
417
+ shape: rectangle
857
418
 
858
- "Old Laptop Session": {
859
- shape: rectangle
860
- "IP: 172.16.0.20"
861
- "UA: Firefox on Windows"
862
- "Status: expired"
863
- }
864
- }
865
-
866
- "User Account" -> "Sessions": "Has multiple"
867
- ```
868
- - good:
869
- ```d2
870
- direction: down
871
-
872
- "User Account": {
873
- shape: person
874
- }
875
-
876
- "Sessions": {
877
- shape: package
878
- grid-columns: 1
879
-
880
- "Web Browser Session": {
881
- shape: rectangle
882
- "IP: 192.168.1.10"
883
- "UA: Chrome on macOS"
884
- "Status: online"
885
- }
886
-
887
- "iOS App Session": {
888
- shape: rectangle
889
- "IP: 10.0.0.5"
890
- "UA: MyApp/1.2 iOS"
891
- "Status: online"
892
- }
893
-
894
- "Old Laptop Session": {
895
- shape: rectangle
896
- "IP: 172.16.0.20"
897
- "UA: Firefox on Windows"
898
- "Status: expired"
899
- }
900
- }
901
-
902
- "User Account" -> "Sessions": "Has multiple"
903
- ```
904
- - 当一个节点容器中包含了其他的节点容器,建议使用 `grid-gap` 来增加各个节点容器的距离,尽量大于 `100`
905
- - bad:
906
- ```d2
907
- direction: down
908
-
909
- "Your Application": {
419
+ "uppy-ecosystem": {
420
+ label: "Uppy Ecosystem"
910
421
  shape: rectangle
911
- }
912
-
913
- "SDK: @blocklet/js-sdk": {
914
- shape: package
915
- grid-columns: 1
916
422
 
917
- "HTTP Clients": {
918
- shape: rectangle
919
- grid-columns: 2
920
- "createAxios()": "Axios-based client"
921
- "createFetch()": "Fetch-based client"
423
+ "uppy-core": {
424
+ label: "Uppy Core Instance"
922
425
  }
923
426
 
924
- "Core Services": {
925
- shape: rectangle
926
- grid-columns: 3
927
- "AuthService": "User & Auth"
928
- "TokenService": "Token Management"
929
- "UserSessionService": "Session Data"
930
- "BlockletService": "Blocklet Metadata"
931
- "FederatedService": "Federated Login"
932
- }
933
-
934
- "HTTP Clients" -> "Core Services".TokenService: "Uses for auth tokens"
935
- }
936
-
937
- "Blocklet Services": {
938
- shape: cylinder
939
- "Remote APIs"
940
- }
941
-
942
- "Your Application" -> "SDK: @blocklet/js-sdk": "Imports & Initializes"
943
- "SDK: @blocklet/js-sdk" -> "Blocklet Services": "Makes authenticated requests"
944
- ```
945
- - bad:
946
- ```d2
947
- direction: down
948
-
949
- "Your Application": {
950
- shape: rectangle
951
- }
952
-
953
- "SDK: @blocklet/js-sdk": {
954
- shape: package
955
- grid-columns: 1
956
- grid-gap: 100
957
-
958
- "HTTP Clients": {
427
+ "standard-plugins": {
428
+ label: "Standard Uppy Plugins"
959
429
  shape: rectangle
960
- grid-columns: 2
961
- "createAxios()": "Axios-based client"
962
- "createFetch()": "Fetch-based client"
430
+ "Dashboard": {}
431
+ "Tus": {}
432
+ "Webcam": {}
433
+ "Url": {}
963
434
  }
964
435
 
965
- "Core Services": {
436
+ "custom-plugins": {
437
+ label: "Custom Blocklet Plugins"
966
438
  shape: rectangle
967
- grid-columns: 3
968
- "AuthService": "User & Auth"
969
- "TokenService": "Token Management"
970
- "UserSessionService": "Session Data"
971
- "BlockletService": "Blocklet Metadata"
972
- "FederatedService": "Federated Login"
439
+ "AIImage": {}
440
+ "Resources": {}
441
+ "Uploaded": {}
973
442
  }
974
443
 
975
- "HTTP Clients" -> "Core Services".TokenService: "Uses for auth tokens"
444
+ "uppy-core" -> "standard-plugins"
445
+ "uppy-core" -> "custom-plugins"
976
446
  }
447
+ }
448
+ }
977
449
 
978
- "Blocklet Services": {
979
- shape: cylinder
980
- "Remote APIs"
981
- }
450
+ "media-kit": {
451
+ label: "Media Kit Blocklet"
452
+ shape: cylinder
453
+ }
982
454
 
983
- "Your Application" -> "SDK: @blocklet/js-sdk": "Imports & Initializes"
984
- "SDK: @blocklet/js-sdk" -> "Blocklet Services": "Makes authenticated requests"
985
- ```
986
- - 如果节点的 `shape: person`,则不要加任何其他内部的文字
987
- - bad:
988
- ```d2
989
- "User Account": {
990
- shape: person
991
- "did:z... (John Doe)"
992
- }
993
- ```
994
- - good:
995
- ```d2
996
- "User Account": {
997
- shape: person
998
- }
999
- ```
1000
- - **非常重要** 在绘制连线的时候,一定要注意连接的节点的 ID 到底是什么,它可能有多个层级,但一定要弄清楚关系才能添加连线
1001
- - bad:
1002
- ```d2
1003
- direction: down
1004
-
1005
- "User-Browser": {
1006
- label: "User's Browser"
1007
- shape: rectangle
455
+ "@blocklet/app.uploader-component" <-> media-kit: "Provides Config & Plugins"
456
+ ```
457
+ - **Good Practice:**
458
+ ```d2
459
+ direction: down
1008
460
 
1009
- "React-App": {
1010
- label: "Your React App"
1011
- shape: rectangle
461
+ blocklet-app: {
462
+ label: "Your Blocklet Application"
463
+ shape: rectangle
1012
464
 
1013
- "Uploader-Component": {
1014
- label: "@blocklet/uploader"
1015
- shape: package
1016
- }
1017
- }
1018
- }
465
+ uploader-component: {
466
+ label: "<Uploader /> Component"
467
+ shape: rectangle
1019
468
 
1020
- "Blocklet-Server": {
1021
- label: "Your Blocklet Server"
469
+ uppy-ecosystem: {
470
+ label: "Uppy Ecosystem"
1022
471
  shape: rectangle
1023
472
 
1024
- "Express-App": {
1025
- label: "Your Express App"
1026
- shape: rectangle
1027
-
1028
- "Uploader-Middleware": {
1029
- label: "@blocklet/uploader-server"
1030
- shape: package
1031
- }
1032
- }
1033
- }
1034
-
1035
- "File-System": {
1036
- label: "Storage\n(e.g., File System)"
1037
- shape: cylinder
1038
- }
1039
-
1040
- "Uploader-Component" -> "Uploader-Middleware": "HTTP POST Request\n(File Upload)"
1041
- "Uploader-Middleware" -> "File-System": "Saves File"
1042
- ```
1043
- - good:
1044
- ```d2
1045
- direction: down
1046
-
1047
- "User-Browser": {
1048
- label: "User's Browser"
1049
- shape: rectangle
1050
-
1051
- "React-App": {
1052
- label: "Your React App"
1053
- shape: rectangle
1054
-
1055
- "Uploader-Component": {
1056
- label: "@blocklet/uploader"
1057
- shape: package
1058
- }
1059
- }
1060
- }
1061
-
1062
- "Blocklet-Server": {
1063
- label: "Your Blocklet Server"
1064
- shape: rectangle
1065
-
1066
- "Express-App": {
1067
- label: "Your Express App"
1068
- shape: rectangle
1069
-
1070
- "Uploader-Middleware": {
1071
- label: "@blocklet/uploader-server"
1072
- shape: package
1073
- }
1074
- }
1075
- }
1076
-
1077
- "File-System": {
1078
- label: "Storage\n(e.g., File System)"
1079
- shape: cylinder
1080
- }
473
+ uppy-core: {
474
+ label: "Uppy Core Instance"
475
+ }
476
+
477
+ standard-plugins: {
478
+ label: "Standard Uppy Plugins"
479
+ shape: rectangle
480
+ Dashboard: {}
481
+ Tus: {}
482
+ Webcam: {}
483
+ Url: {}
484
+ }
485
+
486
+ custom-plugins: {
487
+ label: "Custom Blocklet Plugins"
488
+ shape: rectangle
489
+ AIImage: {}
490
+ Resources: {}
491
+ Uploaded: {}
492
+ }
493
+ }
494
+ }
495
+ }
496
+
497
+ media-kit: {
498
+ label: "Media Kit Blocklet"
499
+ shape: cylinder
500
+ }
501
+
502
+ blocklet-app.uploader-component.uppy-ecosystem.uppy-core -> blocklet-app.uploader-component.uppy-ecosystem.standard-plugins
503
+ blocklet-app.uploader-component.uppy-ecosystem.uppy-core -> blocklet-app.uploader-component.uppy-ecosystem.custom-plugins
504
+ blocklet-app.uploader-component <-> media-kit: "Provides Config & Plugins"
505
+ ```
506
+
507
+ #### Shape should not contain both label and remark
508
+ - **Bad Practice:**
509
+ ```d2
510
+ uploader-trigger: {
511
+ label: "UploaderTrigger"
512
+ shape: rectangle
513
+ style.fill: "#e6f7ff"
514
+ "A wrapper to create a clickable element (e.g., a button) that opens the Uploader UI."
515
+ }
516
+ ```
517
+ - **Good Practice:**
518
+ ```d2
519
+ uploader-trigger: {
520
+ label: "UploaderTrigger"
521
+ shape: rectangle
522
+ style.fill: "#e6f7ff"
523
+ }
524
+ ```
525
+
526
+ #### Shorten long connections text
527
+ - **Bad Practice:**
528
+ ```d2
529
+ direction: down
530
+
531
+ User: {
532
+ shape: c4-person
533
+ }
534
+
535
+ Frontend: {
536
+ label: "Frontend (Browser)"
537
+ shape: rectangle
538
+
539
+ Uploader-Component: {
540
+ label: "Uploader Component"
541
+ shape: rectangle
542
+ }
543
+ }
544
+
545
+ Backend: {
546
+ label: "Backend Server"
547
+ shape: rectangle
548
+
549
+ Companion-Middleware: {
550
+ label: "Companion Middleware\n(@blocklet/uploader-server)"
551
+ }
552
+
553
+ Local-Storage-Middleware: {
554
+ label: "Local Storage Middleware"
555
+ shape: rectangle
556
+ }
557
+ }
558
+
559
+ Remote-Source: {
560
+ label: "Remote Source\n(e.g., Unsplash, URL)"
561
+ shape: cylinder
562
+ }
563
+
564
+ User -> Frontend.Uploader-Component: "1. Selects remote file"
565
+ Frontend.Uploader-Component -> Backend.Companion-Middleware: "2. Request file from remote source via Companion URL"
566
+ Backend.Companion-Middleware -> Remote-Source: "3. Fetch file"
567
+ Remote-Source -> Backend.Companion-Middleware: "4. Stream file data"
568
+ Backend.Companion-Middleware -> Frontend.Uploader-Component: "5. Stream file back to browser"
569
+ Frontend.Uploader-Component -> Backend.Local-Storage-Middleware: "6. Upload file via Tus protocol"
570
+ ```
571
+ - **Good Practice:**
572
+ ```d2
573
+ direction: down
574
+
575
+ User: {
576
+ shape: c4-person
577
+ }
578
+
579
+ Frontend: {
580
+ label: "Frontend (Browser)"
581
+ shape: rectangle
582
+
583
+ Uploader-Component: {
584
+ label: "Uploader Component"
585
+ shape: rectangle
586
+ }
587
+ }
588
+
589
+ Backend: {
590
+ label: "Backend Server"
591
+ shape: rectangle
592
+
593
+ Companion-Middleware: {
594
+ label: "Companion Middleware\n(@blocklet/uploader-server)"
595
+ }
596
+
597
+ Local-Storage-Middleware: {
598
+ label: "Local Storage Middleware"
599
+ shape: rectangle
600
+ }
601
+ }
602
+
603
+ Remote-Source: {
604
+ label: "Remote Source\n(e.g., Unsplash, URL)"
605
+ shape: cylinder
606
+ }
607
+
608
+ User -> Frontend.Uploader-Component: "1. Selects file"
609
+ Frontend.Uploader-Component -> Backend.Companion-Middleware: "2. Request file"
610
+ Backend.Companion-Middleware -> Remote-Source: "3. Fetch file"
611
+ Remote-Source -> Backend.Companion-Middleware: "4. Stream file data"
612
+ Backend.Companion-Middleware -> Frontend.Uploader-Component: "5. Back to browser"
613
+ Frontend.Uploader-Component -> Backend.Local-Storage-Middleware: "6. Upload file"
614
+ ```
615
+
616
+ #### Remove unnecessary grid-columns
617
+ - **Bad Practice:**
618
+ ```d2
619
+ direction: down
620
+
621
+ User: {
622
+ shape: c4-person
623
+ }
624
+
625
+ Checkout-Flow: {
626
+ label: "Checkout Flow"
627
+ shape: rectangle
628
+
629
+ Entry-Points: {
630
+ label: "User-Facing Components"
631
+ shape: rectangle
632
+ grid-columns: 2
633
+
634
+ CheckoutTable: {
635
+ label: "CheckoutTable"
636
+ "Renders pricing plans"
637
+ }
638
+
639
+ CheckoutDonate: {
640
+ label: "CheckoutDonate"
641
+ "Manages donation flow"
642
+ }
643
+ }
644
+
645
+ Core-Processor: {
646
+ label: "Core Payment Processor"
647
+ shape: rectangle
648
+
649
+ CheckoutForm: {
650
+ label: "CheckoutForm"
651
+ "Processes the final payment"
652
+ }
653
+ }
654
+ Entry-Points.CheckoutTable -> Core-Processor.CheckoutForm: "On plan selection"
655
+ Entry-Points.CheckoutDonate -> Core-Processor.CheckoutForm: "On donate action"
656
+ }
657
+
658
+ User -> Checkout-Flow.Entry-Points.CheckoutTable: "Selects a subscription"
659
+ User -> Checkout-Flow.Entry-Points.CheckoutDonate: "Makes a donation"
660
+ ```
661
+ - **Good Practice:**
662
+ ```d2
663
+ direction: down
664
+
665
+ User: {
666
+ shape: c4-person
667
+ }
668
+
669
+ Checkout-Flow: {
670
+ label: "Checkout Flow"
671
+ shape: rectangle
672
+
673
+ Entry-Points: {
674
+ label: "User-Facing Components"
675
+ shape: rectangle
676
+
677
+ CheckoutTable: {
678
+ label: "CheckoutTable"
679
+ }
680
+
681
+ CheckoutDonate: {
682
+ label: "CheckoutDonate"
683
+ }
684
+ }
685
+
686
+ Core-Processor: {
687
+ label: "Core Payment Processor"
688
+ shape: rectangle
689
+
690
+ CheckoutForm: {
691
+ label: "CheckoutForm"
692
+ }
693
+ }
694
+
695
+ }
696
+
697
+ Checkout-Flow.Entry-Points.CheckoutTable -> Checkout-Flow.Core-Processor.CheckoutForm: "On plan selection"
698
+ Checkout-Flow.Entry-Points.CheckoutDonate -> Checkout-Flow.Core-Processor.CheckoutForm: "On donate action"
699
+ User -> Checkout-Flow.Entry-Points.CheckoutTable: "Selects a subscription"
700
+ User -> Checkout-Flow.Entry-Points.CheckoutDonate: "Makes a donation"
701
+ ```
702
+
703
+ > Checkout-Flow.Entry-Points has only two child nodes, and `grid-columns` is set to `2`, so `grid-columns` is unnecessary.
704
+
705
+ #### Ensure style properties are valid
706
+ - **Bad Practice:**
707
+ ```d2
708
+ App: {
709
+ shape: rectangle
710
+ style: {
711
+ stroke: "#888"
712
+ "stroke-width": 2
713
+ dashed: true
714
+ }
715
+ }
716
+ ```
717
+ - **Good Practice:**
718
+ ```d2
719
+ App: {
720
+ shape: rectangle
721
+ style: {
722
+ stroke: "#888"
723
+ stroke-width: 2
724
+ stroke-dash: 2
725
+ }
726
+ }
727
+ ```
728
+
729
+ #### Sequence Diagram
730
+ - **Bad Practice:**
731
+ ```d2
732
+ direction: down
733
+
734
+ User: {
735
+ shape: c4-person
736
+ }
737
+
738
+ App: {
739
+ label: "Your Application"
740
+ shape: rectangle
741
+
742
+ ResumeSubscription: {
743
+ label: "ResumeSubscription Component"
744
+ }
745
+ }
746
+
747
+ Payment-API: {
748
+ label: "Payment Backend API"
749
+ shape: rectangle
750
+ }
751
+
752
+ DID-Wallet: {
753
+ label: "DID Wallet"
754
+ icon: "https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg"
755
+ }
756
+
757
+ sequence: {
758
+ shape: sequence_diagram
759
+
760
+ User -> App.ResumeSubscription: "1. Triggers resume action"
761
+
762
+ App.ResumeSubscription -> Payment-API: "2. Fetch recovery info\n(GET /recover-info)"
763
+ Payment-API -> App.ResumeSubscription: "3. Return status (e.g., needStake: true)"
764
+
765
+ App.ResumeSubscription.t1 -> User: "4. Display confirmation dialog"
766
+ User -> App.ResumeSubscription.t1: "5. Clicks 'Confirm'"
767
+
768
+ alt: "If Re-Staking is Required" {
769
+ App.ResumeSubscription.t1 -> DID-Wallet: "6a. Open 're-stake' session"
770
+ User -> DID-Wallet: "7a. Approve in wallet"
771
+ DID-Wallet -> App.ResumeSubscription.t1: "8a. Send success callback"
772
+ }
773
+
774
+ alt: "If No Staking is Required" {
775
+ App.ResumeSubscription.t1 -> Payment-API: "6b. Call recover endpoint\n(PUT /recover)"
776
+ Payment-API -> App.ResumeSubscription.t1: "7b. Return success"
777
+ }
778
+
779
+ App.ResumeSubscription.t1 -> Payment-API: "9. Fetch updated subscription details"
780
+ Payment-API -> App.ResumeSubscription.t1: "10. Return latest subscription"
781
+ App.ResumeSubscription.t1 -> App.ResumeSubscription: "11. Call onResumed() & close dialog"
782
+ }
783
+
784
+ ```
785
+ - **Good Practice:**
786
+ ```d2
787
+ shape: sequence_diagram
788
+
789
+ User -> App.ResumeSubscription: "1. Triggers resume action"
790
+
791
+ App.ResumeSubscription -> Payment-API: "2. Fetch recovery info\n(GET /recover-info)"
792
+ Payment-API -> App.ResumeSubscription: "3. Return status (e.g., needStake: true)"
793
+
794
+ App.ResumeSubscription.t1 -> User: "4. Display confirmation dialog"
795
+ User -> App.ResumeSubscription.t1: "5. Clicks 'Confirm'"
796
+
797
+ App.ResumeSubscription.t1 -> DID-Wallet: "6a. Open 're-stake' session"
798
+ User -> DID-Wallet: "7a. Approve in wallet"
799
+ DID-Wallet -> App.ResumeSubscription.t1: "8a. Send success callback"
800
+
801
+ App.ResumeSubscription.t1 -> Payment-API: "6b. Call recover endpoint\n(PUT /recover)"
802
+ Payment-API -> App.ResumeSubscription.t1: "7b. Return success"
803
+
804
+ App.ResumeSubscription.t1 -> Payment-API: "9. Fetch updated subscription details"
805
+ Payment-API -> App.ResumeSubscription.t1: "10. Return latest subscription"
806
+ App.ResumeSubscription.t1 -> App.ResumeSubscription: "11. Call onResumed() & close dialog"
807
+ ```
808
+
809
+ > If using sequence diagram, remove all other shapes, and only keep the sequence diagram part.
810
+
811
+ #### Don't use icon in Sequence Diagram
812
+
813
+ - **Bad Practice:**
814
+ ```d2
815
+ shape: sequence_diagram
816
+
817
+ Developer: {
818
+ shape: c4-person
819
+ }
820
+
821
+ CLI: {
822
+ label: "CLI"
823
+ }
824
+
825
+ Blocklet-Store: {
826
+ label: "Blocklet Store"
827
+ icon: "https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png"
828
+ }
829
+
830
+ Local-Config: {
831
+ label: "Local Config"
832
+ shape: cylinder
833
+ }
834
+
835
+ Developer -> CLI: "blocklet connect https://..."
836
+ CLI -> Blocklet-Store: "1. Opens auth URL in browser"
837
+ Developer -> Blocklet-Store: "2. Authenticates & authorizes CLI"
838
+ Blocklet-Store -> CLI: "3. Sends token & developer info"
839
+ CLI -> Local-Config: "4. Saves credentials"
840
+ CLI -> Developer: "5. Displays success message"
841
+ ```
842
+ - **Good Practice:**
843
+ ```d2
844
+ shape: sequence_diagram
845
+
846
+ Developer: {
847
+ shape: c4-person
848
+ }
849
+
850
+ CLI: {
851
+ label: "CLI"
852
+ }
853
+
854
+ Blocklet-Store: {
855
+ label: "Blocklet Store"
856
+ }
857
+
858
+ Local-Config: {
859
+ label: "Local Config"
860
+ shape: cylinder
861
+ }
862
+
863
+ Developer -> CLI: "blocklet connect"
864
+ CLI -> Blocklet-Store: "1. Opens auth URL"
865
+ Developer -> Blocklet-Store: "2. Authenticates CLI"
866
+ Blocklet-Store -> CLI: "3. Sends token"
867
+ CLI -> Local-Config: "4. Saves credentials"
868
+ CLI -> Developer: "5. Success"
869
+ ```
870
+
871
+ #### Avoid unexpected characters
872
+ - **Bad Practice:**
873
+ ```d2
874
+ User -> CLI: "$ blocklet init"
875
+ ```
876
+ - **Good Practice:**
877
+ ```d2
878
+ User -> CLI: "blocklet init"
879
+ ```
880
+
881
+ #### If have alt, don't forget to add `shape: sequence_diagram`
882
+
883
+ - **Bad Practice:**
884
+ ```d2
885
+ direction: down
886
+ Client: {
887
+ shape: c4-person
888
+ }
889
+
890
+ Application: {
891
+ label: "Your Blocklet (Express.js)"
892
+ shape: rectangle
893
+
894
+ Session-Middleware: {
895
+ label: "session()"
896
+ }
897
+ Auth-Middleware: {
898
+ label: "auth()"
899
+ }
900
+ Protected-Route: {
901
+ label: "Route Handler"
902
+ }
903
+ }
904
+
905
+ Blocklet-Service: {
906
+ label: "Blocklet Service"
907
+ shape: cylinder
908
+ }
909
+
910
+ Client -> Application.Session-Middleware: "1. Request to /protected"
911
+ Application.Session-Middleware -> Application.Auth-Middleware: "2. next() with req.user"
912
+ Application.Auth-Middleware -> Blocklet-Service: "3. Get permissions for role\n(if needed)"
913
+ Blocklet-Service -> Application.Auth-Middleware: "4. Return permissions"
914
+ Application.Auth-Middleware -> Application.Auth-Middleware: "5. Evaluate all rules"
915
+
916
+ alt "If Authorized" {
917
+ Application.Auth-Middleware -> Application.Protected-Route: "6a. next()"
918
+ Application.Protected-Route -> Client: "7a. 200 OK Response"
919
+ }
920
+
921
+ alt "If Forbidden" {
922
+ Application.Auth-Middleware -> Client: "6b. 403 Forbidden Response"
923
+ }
924
+ ```
925
+ - **Good Practice:**
926
+ ```d2
927
+ direction: down
928
+ shape: sequence_diagram
929
+ Client: {
930
+ shape: c4-person
931
+ }
932
+
933
+ Application: {
934
+ label: "Your Blocklet (Express.js)"
935
+ shape: rectangle
936
+
937
+ Session-Middleware: {
938
+ label: "session()"
939
+ }
940
+ Auth-Middleware: {
941
+ label: "auth()"
942
+ }
943
+ Protected-Route: {
944
+ label: "Route Handler"
945
+ }
946
+ }
947
+
948
+ Blocklet-Service: {
949
+ label: "Blocklet Service"
950
+ shape: cylinder
951
+ }
952
+
953
+ Client -> Application.Session-Middleware: "1. Request to /protected"
954
+ Application.Session-Middleware -> Application.Auth-Middleware: "2. next() with req.user"
955
+ Application.Auth-Middleware -> Blocklet-Service: "3. Get permissions for role\n(if needed)"
956
+ Blocklet-Service -> Application.Auth-Middleware: "4. Return permissions"
957
+ Application.Auth-Middleware -> Application.Auth-Middleware: "5. Evaluate all rules"
958
+
959
+ alt "If Authorized" {
960
+ Application.Auth-Middleware -> Application.Protected-Route: "6a. next()"
961
+ Application.Protected-Route -> Client: "7a. 200 OK Response"
962
+ }
963
+
964
+ alt "If Forbidden" {
965
+ Application.Auth-Middleware -> Client: "6b. 403 Forbidden Response"
966
+ }
967
+ ```
968
+
969
+ ## Chapter 3: Official Best Practices
970
+
971
+ ##### Game State Sequence
972
+ ```d2
973
+ shape: sequence_diagram
974
+
975
+ User
976
+ Session
977
+ Lua
978
+
979
+ User.Init
980
+
981
+ User.t1 -> Session.t1: "SetupFight()"
982
+ Session.t1 -> Session.t1: "Create clean fight state"
983
+ Session.t1 -> Lua: "Trigger OnPlayerTurn"
984
+ User.t1 <- Session.t1
985
+
986
+ User.Repeat
987
+
988
+ User.mid -> Session.mid: "PlayerCastHand() etc."
989
+ Session.mid -> Lua: "Trigger OnDamage etc."
990
+ User.mid <- Session.mid
991
+
992
+ User.t2 -> Session.t2: "FinishPlayerTurn()"
993
+ Session.t2 -> Lua: "Trigger OnTurn"
994
+ Session.t2 -> Session.t2: "Update and remove status effects"
995
+ Session.t2 -> Lua: "Trigger OnPlayerTurn"
996
+ User.t2 <- Session.t2
997
+ ```
1081
998
 
1082
- User-Browser.React-App.Uploader-Component -> Blocklet-Server.Express-App.Uploader-Middleware: "HTTP POST Request\n(File Upload)"
1083
- Blocklet-Server.Express-App.Uploader-Middleware -> "File-System": "Saves File"
1084
- ```
1085
- - 对于节点 shape 的选择,可以参考
1086
- {% include "shape-rules.md" %}
1087
- - 示例参考:
1088
- {% include "diy-examples.md" %}