hd-garden 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/garden-ui/SKILL.md +27 -21
- package/docs/handbook/components/compose.md +70 -0
- package/docs/handbook/components/interactive.md +4 -0
- package/docs/handbook/en/components/compose.md +68 -0
- package/docs/handbook/en/reference/compose.md +5 -0
- package/docs/handbook/reference/compose.md +5 -0
- package/editors/dist/typora/main.js +146 -146
- package/editors/dist/typora/style.css +82 -31
- package/editors/dist/vscode/{garden-ui-0.1.0.vsix → garden-ui-0.1.1.vsix} +0 -0
- package/package.json +2 -2
- package/templates/garden-blocks.md +1 -1
- package/templates/skills-en/garden-ui/SKILL.md +45 -39
- package/ui/README.md +1 -1
- package/ui/compose/dil.mjs +2 -1
- package/ui/compose/prompt-catalog.mjs +99 -0
- package/ui/core/catalog.mjs +38 -2
- package/ui/core/chart-color.mjs +11 -0
- package/ui/core/chart-ticks.mjs +83 -0
- package/ui/core/cli-locale.mjs +4 -4
- package/ui/core/compile.mjs +1 -0
- package/ui/core/garden-fence.mjs +1 -1
- package/ui/core/scan.mjs +1 -1
- package/ui/core/validate.mjs +7 -1
- package/ui/garden/catalog.mjs +2 -2
- package/ui/preview/dist/garden-ui.css +82 -31
- package/ui/preview/dist/garden-ui.esm.js +143 -143
- package/ui/preview/dist/garden-ui.iife.js +143 -143
- package/ui/preview/index.tsx +1 -1
- package/ui/prompt/build.mjs +12 -3
- package/ui/prompt/recipes/compose.md +52 -13
- package/ui/prompt/rules.md +2 -2
- package/ui/react/components/chart.tsx +73 -44
- package/ui/react/components/layout.tsx +9 -1
- package/ui/react/index.tsx +2 -1
- package/ui/react/types.ts +2 -0
- package/ui/react/ui.css +82 -31
|
@@ -2,16 +2,20 @@
|
|
|
2
2
|
name: garden-ui
|
|
3
3
|
description: >-
|
|
4
4
|
Compiles the `garden` idea blocks in one Garden post into garden components
|
|
5
|
-
(garden compose, or card / stat / compare / ... , or prose, or garden
|
|
5
|
+
(garden compose DIL, or card / stat / compare / ... , or prose, or garden
|
|
6
6
|
html), fixes broken garden compose blocks, and suggests where a post could
|
|
7
|
-
use interaction. Older intent / ui blocks are handled the same
|
|
8
|
-
the user says 编译这篇 / 编译想法 / 重编译 <id> / 这篇哪里适合交互,
|
|
9
|
-
garden ui check fix prompt.
|
|
7
|
+
use interaction. Older intent / ui / interactive blocks are handled the same
|
|
8
|
+
way. Use when the user says 编译这篇 / 编译想法 / 重编译 <id> / 这篇哪里适合交互,
|
|
9
|
+
or pastes a garden ui check fix prompt.
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# 编译 Garden 交互块
|
|
13
13
|
|
|
14
|
-
作者在稿里用不带名字的 `garden` 围栏写一句想法,这个 skill 把它变成 `garden compose
|
|
14
|
+
作者在稿里用不带名字的 `garden` 围栏写一句想法,这个 skill 把它变成 `garden compose`、别的 `garden` 组件,或写回正文。
|
|
15
|
+
|
|
16
|
+
**`garden compose` 写的是 DIL,不是 YAML:** 叶子是 Markdown,结构是 compose 目录里的**小写标签** + 设计 token。作者原话放进 `<intent>`(或块头 `intent:`)。围栏类型始终是 `garden compose`——静态说明图与可调 play 都是它。不要发明 YAML 槽,不要用 interactive 的 PascalCase 当作者输入。
|
|
17
|
+
|
|
18
|
+
交互组件、compose 目录和设计规则在 `npx garden ui prompt` 的输出里;各 `garden` 组件的用途和示例在 `node_modules/hd-garden/templates/garden-blocks.md`(在引擎仓库里是 `templates/garden-blocks.md`)。以这两份为准,不凭记忆写组件。改完引擎后若本 skill 或 prompt 过期,先 `npx garden ui prompt` 再写。
|
|
15
19
|
|
|
16
20
|
作者提交时,提交前钩子跑的是同一个 `npx garden ui check`。你用来找块、写回、自查的命令,和拦住作者提交的命令是同一套。
|
|
17
21
|
|
|
@@ -42,22 +46,23 @@ description: >-
|
|
|
42
46
|
npx garden ui prompt
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
里面是 compose 目录(小写标签 + token)、interactive PascalCase 参考(兼容)、配方和设计规则。按配方的骨架写,不发明目录里没有的组件或属性。
|
|
46
50
|
|
|
47
51
|
3. 每块读原话和所在小节,三选一:
|
|
48
52
|
- **写回正文**:一句话、一张表或一张 Mermaid 就能说清时。
|
|
49
|
-
- **写 `garden compose
|
|
53
|
+
- **写 `garden compose`(说明图)**:读者需要看见结构、关系、重点时。Markdown 叶子 + 小写目录标签;不要 YAML。
|
|
54
|
+
- **写 `garden compose`(play)**:读者需要拖、点、翻才能看懂时。仍用 `garden compose`;可用 `{@body}`、`{expr}`、`{#if}`/`{#each}` 与小写控件(`slider`/`choice`/`button`/`stat`…)。遗留 `garden interactive` + PascalCase 仅作兼容。
|
|
50
55
|
- **写一段 `diff`**:想法紧跟在一段完整代码后面,说的是「和上一版比改了什么」时。按原话写出 diff,用一段普通 Markdown 写回(只有这一个 `diff` 围栏)。写回后它紧挨着那段代码,页面会合成「代码 / 对比上一版」切换;diff 里的新行要和上面的完整代码一致。
|
|
51
|
-
- **写别的 `garden` 组件**:一张或多张资源卡(`card` / `cards`)、几个指标(`stat`)、两种做法并排或好坏对照(`compare` / `verdict: good|bad`)、一条时间线(`timeline`)、一张静态图表(`chart
|
|
52
|
-
-
|
|
56
|
+
- **写别的 `garden` 组件**:一张或多张资源卡(`card` / `cards`)、几个指标(`stat`)、两种做法并排或好坏对照(`compare` / `verdict: good|bad`)、一条时间线(`timeline`)、一张静态图表(`chart`)、一份下载文件(`file`)、3~6 个要素的关系图(`relations`)、紧凑图标列表或分步列表(`list`)、两三个选项的选择卡(`choice`)。字段与校验约束见 `npx garden ui prompt` 与 `templates/garden-blocks.md`。
|
|
57
|
+
- **编译已有 `garden compose`**:`lang` 是 `garden-compose` 且 `status` 是 `uncompiled` 时,围栏头保持 `garden compose`。把扫描到的原话原样放进 `<intent>`(或块头 `intent:`),正文写成 DIL(Markdown + 小写标签;play 时加 `{@body}` 等)。不要改成 interactive,不要写任意 HTML、SVG 或脚本。配方在 `npx garden ui prompt` 的说明图一节。
|
|
53
58
|
- **写 `garden html`**:目录表达不了的画布动画、音频、物理模拟等。按 [garden-interactive-chart](../garden-interactive-chart/SKILL.md) 在同名目录写 HTML,用 `title` 写下判断。
|
|
54
59
|
|
|
55
60
|
4. 写 `garden compose` 时:
|
|
56
|
-
- 格式:围栏头写 `garden compose
|
|
57
|
-
- 原话原样放进
|
|
58
|
-
- `
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
+
- 格式:围栏头写 `garden compose`(可选 `#<id>`);作者原话进 `<intent>…</intent>` 或块头 `intent:`;静态说明图直接写 Markdown + 标签树;play 可在 `---` 块头写 `caption` / `intent`,正文用 `{@body}` 与小写控件。
|
|
62
|
+
- 原话原样放进 intent,一个字都不改。
|
|
63
|
+
- 芯片内部 `gap={0}`/`gap={1}`;章节堆叠 `gap={2}`/`gap={3}`;日横幅等宽;phrasing 叶子保留前导 `*`。
|
|
64
|
+
- play 时:`caption` 摘原话里的判断;原话有已知点可写 `expect`(若引擎/校验支持)。
|
|
65
|
+
- 不要写色值、宽度、任意 HTML/SVG/脚本;图标用 `<icon name="…"/>`(名字须在目录里)。
|
|
61
66
|
- 块太大就拆成两块,各自就近放在要讲的段落旁边,不挪到别的文件。
|
|
62
67
|
|
|
63
68
|
5. 每块用 `apply` 写回,新内容从标准输入给:
|
|
@@ -65,17 +70,16 @@ description: >-
|
|
|
65
70
|
````bash
|
|
66
71
|
npx garden ui apply content/posts/路径.md --id <id> --base <hash> <<'GARDEN_UI'
|
|
67
72
|
```garden compose #id
|
|
68
|
-
|
|
69
|
-
intent: |
|
|
70
|
-
...
|
|
71
|
-
---
|
|
73
|
+
<intent>
|
|
72
74
|
...
|
|
75
|
+
</intent>
|
|
76
|
+
<row gap={2}>...</row>
|
|
73
77
|
```
|
|
74
78
|
GARDEN_UI
|
|
75
79
|
````
|
|
76
80
|
|
|
77
81
|
- 想法块没有 id,用 `--intent-hash <intentHash>` 定位;原话重复时改用 `--at <startLine>`。
|
|
78
|
-
- 新内容可以是一个 `garden` 组件围栏(interactive、card、stat……),或一段普通 Markdown(写回正文时)。旧稿里的 `ui` 块也可以按原格式写回。
|
|
82
|
+
- 新内容可以是一个 `garden` 组件围栏(compose、interactive、card、stat……),或一段普通 Markdown(写回正文时)。旧稿里的 `ui` 块也可以按原格式写回。
|
|
79
83
|
- 新内容里不能再有没编译的想法。
|
|
80
84
|
- 成功时 stdout 是 `{"ok":true,...}`。失败时 stderr 是 JSON,按 `code` 处理:
|
|
81
85
|
- `apply-invalid`:新内容没通过校验,按 `diagnostics` 改了再写。
|
|
@@ -96,7 +100,7 @@ description: >-
|
|
|
96
100
|
|
|
97
101
|
| id | 选择 | 配方 | 假设 | expect |
|
|
98
102
|
| --- | --- | --- | --- | --- |
|
|
99
|
-
| kv-memory | garden compose | 参数台 | 默认序列取 4K | batch 1 时 512 MiB |
|
|
103
|
+
| kv-memory | garden compose play | 参数台 | 默认序列取 4K | batch 1 时 512 MiB |
|
|
100
104
|
|
|
101
105
|
"假设"写原话没说、你替作者定下的东西(默认值、范围、补的 caption)。没有就写"无"。
|
|
102
106
|
|
|
@@ -106,14 +110,16 @@ description: >-
|
|
|
106
110
|
- 编造原话里没有的数字。需要数字但原话没给时,用原话里的公式算,或者问作者。
|
|
107
111
|
- 使用远程地址(图片、脚本、数据)。
|
|
108
112
|
- 改动目标块之外的正文。
|
|
113
|
+
- 把 compose 写成 YAML 槽,或禁止作者/模型使用合法的 `{@body}` / `{expr}` / `{#if}` / `{#each}`(play 需要时)。
|
|
109
114
|
|
|
110
115
|
## 建议模式:这篇哪里适合交互
|
|
111
116
|
|
|
112
117
|
只读,不改稿。先运行 `npx garden ui prompt` 读设计规则,再通读全文,挑出纯文字或贴图说不清的段落:
|
|
113
118
|
|
|
114
|
-
-
|
|
119
|
+
- 公式或数字随参数变化(compose play / 参数台)
|
|
115
120
|
- 多步过程(步进器、流程)
|
|
116
121
|
- 两种做法的差别(对比)
|
|
117
122
|
- 一组格子、字节或内存布局(格子图)
|
|
123
|
+
- 结构关系说明图(compose 静态 DIL)
|
|
118
124
|
|
|
119
125
|
每处给出:所在小节、为什么文字不够、一段可以直接粘贴到该段落后面的 `garden` 想法块草稿。草稿只写读者要做什么、看到什么,以及原文里已有的数字,不写组件名。最多 5 处;没有合适的就说没有。
|
|
@@ -132,3 +132,73 @@ intent: |
|
|
|
132
132
|
<slider min={1} max={12} value={seats} onChange={setSeats} label="席位" />
|
|
133
133
|
<stat label="月费" value={total} unit="USD" />
|
|
134
134
|
```
|
|
135
|
+
|
|
136
|
+
Play 图表:`palette`(series|categorical)、`yStep`、`overflow`(expand|clip);块头可选 `frame: none`。布局头栏用 `<stack direction="row" justify="between">`。
|
|
137
|
+
|
|
138
|
+
图例:`legend`(auto|show|hide)、`legendPosition`(top|bottom)。
|
|
139
|
+
|
|
140
|
+
## 图表示例(play)
|
|
141
|
+
|
|
142
|
+
下面两例是官方 compose 图表样板:静态八柱(系列蓝 + `yStep`)与可调权重五类柱(`palette="categorical"` + 底图例)。示意数据。
|
|
143
|
+
|
|
144
|
+
### 静态:活跃时段分布
|
|
145
|
+
|
|
146
|
+
```garden compose #compose-chart-active-hours
|
|
147
|
+
caption: 示意:最近 30 天活跃记录归一化后的分布。峰在 20:00。
|
|
148
|
+
frame: none
|
|
149
|
+
intent: |
|
|
150
|
+
静态分布:系列蓝柱、显式 8% 刻度、stack 头栏左标题右徽章。
|
|
151
|
+
---
|
|
152
|
+
{@body const rows = [
|
|
153
|
+
{ h: "08:00", p: 0.05 },
|
|
154
|
+
{ h: "10:00", p: 0.07 },
|
|
155
|
+
{ h: "12:00", p: 0.15 },
|
|
156
|
+
{ h: "14:00", p: 0.06 },
|
|
157
|
+
{ h: "16:00", p: 0.09 },
|
|
158
|
+
{ h: "18:00", p: 0.14 },
|
|
159
|
+
{ h: "20:00", p: 0.28 },
|
|
160
|
+
{ h: "22:00", p: 0.16 }
|
|
161
|
+
]}
|
|
162
|
+
|
|
163
|
+
<stack direction="row" justify="between" align="center" gap={2}>
|
|
164
|
+
<stack gap={1}>
|
|
165
|
+
<caption>用户活跃时间分布(示例)</caption>
|
|
166
|
+
<caption>假设用户最近 30 天的活跃记录经过归一化后的分布</caption>
|
|
167
|
+
</stack>
|
|
168
|
+
<badge tone="new">推荐 20:00</badge>
|
|
169
|
+
</stack>
|
|
170
|
+
|
|
171
|
+
<chart data={rows} x={h} yLabel="活跃概率" yFormat="percent" yMin={0} yMax={0.32} yStep={0.08} overflow="expand" height={280}>
|
|
172
|
+
<bar y={p} label="活跃概率" tone="accent" />
|
|
173
|
+
</chart>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 交互:个人与整体融合权重
|
|
177
|
+
|
|
178
|
+
默认权重 **45%**。拧滑杆或快切 0 / 45 / 100,五根分类色柱与「预测最佳时间」跟着变。
|
|
179
|
+
|
|
180
|
+
```garden compose #compose-chart-fusion-weights
|
|
181
|
+
caption: 交互示例:拧个人权重,融合柱按公式重算。示意数据。
|
|
182
|
+
frame: none
|
|
183
|
+
intent: |
|
|
184
|
+
融合滑杆:categorical 逐柱色、legend 底栏单系列、overflow expand。
|
|
185
|
+
---
|
|
186
|
+
{@body const [w, setW] = DIL.useState(45)}
|
|
187
|
+
{@body const a = w / 100}
|
|
188
|
+
{@body const p08 = a * 0.05 + (1 - a) * 0.12}
|
|
189
|
+
{@body const p12 = a * 0.12 + (1 - a) * 0.22}
|
|
190
|
+
{@body const p18 = a * 0.20 + (1 - a) * 0.26}
|
|
191
|
+
{@body const p20 = a * 0.45 + (1 - a) * 0.28}
|
|
192
|
+
{@body const p22 = a * 0.18 + (1 - a) * 0.12}
|
|
193
|
+
{@body const rows = [{ h: "08:00", p: p08 }, { h: "12:00", p: p12 }, { h: "18:00", p: p18 }, { h: "20:00", p: p20 }, { h: "22:00", p: p22 }]}
|
|
194
|
+
{@body const peak = max(p08, p12, p18, p20, p22)}
|
|
195
|
+
{@body const best = p20 == peak ? "20:00" : (p18 == peak ? "18:00" : (p12 == peak ? "12:00" : (p22 == peak ? "22:00" : "08:00")))}
|
|
196
|
+
|
|
197
|
+
<choice value={w} onChange={setW} label="权重快切" options={[{value:0,label:"0%"},{value:45,label:"45%"},{value:100,label:"100%"}]} />
|
|
198
|
+
<slider min={0} max={100} value={w} onChange={setW} label="个人活跃数据权重" unit="%" />
|
|
199
|
+
<chart legend="show" legendPosition="bottom" data={rows} x={h} palette="categorical" yLabel="融合后活跃概率" yFormat="percent" yMin={0} yMax={0.4} overflow="expand" height={280}>
|
|
200
|
+
<bar y={p} label="融合后活跃概率" tone="accent" />
|
|
201
|
+
</chart>
|
|
202
|
+
<stat label="预测最佳时间" value={best} />
|
|
203
|
+
```
|
|
204
|
+
|
|
@@ -42,3 +42,7 @@ caption: 示例:按字数、阅读速度和配图数估算一篇文章的阅
|
|
|
42
42
|
## 字段说明
|
|
43
43
|
|
|
44
44
|
全部字段、默认值、取值限制和报错代码见 [组件参考 · interactive](/garden/reference/interactive)。
|
|
45
|
+
|
|
46
|
+
Play 图表:`palette`(series|categorical)、`yStep`、`overflow`(expand|clip);块头可选 `frame: none`。布局头栏用 `<stack direction="row" justify="between">`。
|
|
47
|
+
|
|
48
|
+
图例:`legend`(auto|show|hide)、`legendPosition`(top|bottom)。
|
|
@@ -121,3 +121,71 @@ intent: |
|
|
|
121
121
|
<slider min={1} max={12} value={seats} onChange={setSeats} label="Seats" />
|
|
122
122
|
<stat label="Monthly" value={total} unit="USD" />
|
|
123
123
|
```
|
|
124
|
+
|
|
125
|
+
Play charts: `palette` (`series`|`categorical`), `yStep`, `overflow` (`expand`|`clip`); block header may set `frame: none`. Title/badge rows use `<stack direction="row" justify="between">`. Legend: `legend` (`auto`|`show`|`hide`), `legendPosition` (`top`|`bottom`).
|
|
126
|
+
|
|
127
|
+
## Chart examples (play)
|
|
128
|
+
|
|
129
|
+
Two official compose chart samples: eight static series bars with `yStep`, and a five-category weighted fusion chart (`palette="categorical"` + bottom legend). Illustrative data.
|
|
130
|
+
|
|
131
|
+
### Static: active hours
|
|
132
|
+
|
|
133
|
+
```garden compose #compose-chart-active-hours
|
|
134
|
+
caption: "Illustrative: normalized activity over 30 days. Peak at 20:00."
|
|
135
|
+
frame: none
|
|
136
|
+
intent: |
|
|
137
|
+
Static distribution: series blue bars, explicit 8% ticks, stack header.
|
|
138
|
+
---
|
|
139
|
+
{@body const rows = [
|
|
140
|
+
{ h: "08:00", p: 0.05 },
|
|
141
|
+
{ h: "10:00", p: 0.07 },
|
|
142
|
+
{ h: "12:00", p: 0.15 },
|
|
143
|
+
{ h: "14:00", p: 0.06 },
|
|
144
|
+
{ h: "16:00", p: 0.09 },
|
|
145
|
+
{ h: "18:00", p: 0.14 },
|
|
146
|
+
{ h: "20:00", p: 0.28 },
|
|
147
|
+
{ h: "22:00", p: 0.16 }
|
|
148
|
+
]}
|
|
149
|
+
|
|
150
|
+
<stack direction="row" justify="between" align="center" gap={2}>
|
|
151
|
+
<stack gap={1}>
|
|
152
|
+
<caption>User activity by hour (sample)</caption>
|
|
153
|
+
<caption>Normalized activity over the last 30 days (illustrative)</caption>
|
|
154
|
+
</stack>
|
|
155
|
+
<badge tone="new">Peak 20:00</badge>
|
|
156
|
+
</stack>
|
|
157
|
+
|
|
158
|
+
<chart data={rows} x={h} yLabel="Activity" yFormat="percent" yMin={0} yMax={0.32} yStep={0.08} overflow="expand" height={280}>
|
|
159
|
+
<bar y={p} label="Activity" tone="accent" />
|
|
160
|
+
</chart>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Interactive: personal vs global fusion weight
|
|
164
|
+
|
|
165
|
+
Default weight **45%**. Twist the slider or jump 0 / 45 / 100 — five categorical bars and “best time” update together.
|
|
166
|
+
|
|
167
|
+
```garden compose #compose-chart-fusion-weights
|
|
168
|
+
caption: Twist personal weight; fused bars recompute. Illustrative priors.
|
|
169
|
+
frame: none
|
|
170
|
+
intent: |
|
|
171
|
+
Fusion slider: categorical bars, bottom one-entry legend, overflow expand.
|
|
172
|
+
---
|
|
173
|
+
{@body const [w, setW] = DIL.useState(45)}
|
|
174
|
+
{@body const a = w / 100}
|
|
175
|
+
{@body const p08 = a * 0.05 + (1 - a) * 0.12}
|
|
176
|
+
{@body const p12 = a * 0.12 + (1 - a) * 0.22}
|
|
177
|
+
{@body const p18 = a * 0.20 + (1 - a) * 0.26}
|
|
178
|
+
{@body const p20 = a * 0.45 + (1 - a) * 0.28}
|
|
179
|
+
{@body const p22 = a * 0.18 + (1 - a) * 0.12}
|
|
180
|
+
{@body const rows = [{ h: "08:00", p: p08 }, { h: "12:00", p: p12 }, { h: "18:00", p: p18 }, { h: "20:00", p: p20 }, { h: "22:00", p: p22 }]}
|
|
181
|
+
{@body const peak = max(p08, p12, p18, p20, p22)}
|
|
182
|
+
{@body const best = p20 == peak ? "20:00" : (p18 == peak ? "18:00" : (p12 == peak ? "12:00" : (p22 == peak ? "22:00" : "08:00")))}
|
|
183
|
+
|
|
184
|
+
<choice value={w} onChange={setW} label="Weight presets" options={[{value:0,label:"0%"},{value:45,label:"45%"},{value:100,label:"100%"}]} />
|
|
185
|
+
<slider min={0} max={100} value={w} onChange={setW} label="Personal activity weight" unit="%" />
|
|
186
|
+
<chart legend="show" legendPosition="bottom" data={rows} x={h} palette="categorical" yLabel="Fused activity" yFormat="percent" yMin={0} yMax={0.4} overflow="expand" height={280}>
|
|
187
|
+
<bar y={p} label="Fused activity" tone="accent" />
|
|
188
|
+
</chart>
|
|
189
|
+
<stat label="Predicted best time" value={best} />
|
|
190
|
+
```
|
|
191
|
+
|
|
@@ -170,6 +170,11 @@ One mini sample per group, then a one-line table for every tag. No per-tag essay
|
|
|
170
170
|
| `code` | Code snapshot |
|
|
171
171
|
| `pre` | Preformatted text |
|
|
172
172
|
|
|
173
|
+
|
|
174
|
+
## Chart examples
|
|
175
|
+
|
|
176
|
+
Play-level chart samples (static eight bars, fusion weight slider) live on [Diagrams · compose](/garden/en/components/compose#compose-chart-active-hours).
|
|
177
|
+
|
|
173
178
|
## Limits
|
|
174
179
|
|
|
175
180
|
- Nesting ~10 levels / ~220 nodes.
|