hd-garden 0.14.1 → 0.14.3
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-compose/SKILL.md +14 -17
- package/.agents/skills/garden-compose/references/engine-gaps.md +8 -0
- package/.agents/skills/garden-compose/references/frames.md +6 -0
- package/.agents/skills/garden-compose/references/layout-recipes.md +34 -3
- package/.agents/skills/garden-compose/references/mobile-contract.md +23 -0
- package/.agents/skills/garden-compose/references/rendering-model.md +7 -0
- package/.agents/skills/garden-compose/references/visual-selection.md +22 -4
- package/.agents/skills/garden-compose/references/workflow.md +19 -0
- package/.agents/skills/garden-compose/scripts/lint-layout.mjs +87 -0
- package/app/admin/preview/[...path]/page.tsx +1 -1
- package/docs/handbook/en/workflow.md +1 -1
- package/docs/handbook/en/writing.md +1 -1
- package/docs/handbook/workflow.md +1 -1
- package/docs/handbook/writing.md +1 -1
- package/editors/dist/vscode/{garden-ui-0.1.1.vsix → garden-ui-0.14.3.vsix} +0 -0
- package/package.json +3 -2
- package/scripts/build-content.mjs +3 -1
- package/scripts/check-post.mjs +2 -2
- package/scripts/pack-editors.mjs +12 -1
- package/scripts/sync-editor-version.mjs +28 -0
- package/templates/skills-en/garden-compose/SKILL.md +15 -17
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: garden-compose
|
|
3
|
-
description: "
|
|
3
|
+
description: "将原文与一句效果或参考图,编译为 Garden Compose 内保真、可验证的说明布局;用于创作、微调及修复。"
|
|
4
4
|
---
|
|
5
5
|
# Garden Compose
|
|
6
|
+
只处理授权的文章/块;建议请求不写文件。不自动提交、发布、安装依赖或在文章任务中修改技能。
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## 短决策链:问题 → 约束 → 推荐路线 → 原文 → 验证
|
|
9
|
+
1. 作者只需原文+一句效果/参考图+可选不要什么。分开事实、效果与修改范围;保留措辞、顺序、完整段落和资源,不补摘要、数据、因果。普通 Markdown 足够就不加组件。
|
|
10
|
+
2. 选型必读 `references/visual-selection.md`。AI 内部先判断移动阅读任务(扫读/顺序/对照/空间关系),必读 `references/mobile-contract.md`,写桌面+窄屏约束卡,再取**唯一推荐路线**;不是限定合法组件的白名单。只在事实关系、隐藏内容、阅读顺序等重大取舍不明时询问;其余按已记录默认收敛,不让作者填技术表单。
|
|
11
|
+
3. 按路线只读 `references/layout-recipes.md` 对应小节;陌生组合/参考图再读 `references/rendering-model.md`。需要新组合时读 `references/composition-patterns.md`,仍先建立共享容器。frame/tone/size是正交选择,不因微调换组件家族。
|
|
12
|
+
4. 作者组件只用 `garden compose`;内部静态/语义/交互能力按需读 `references/static-play.md`。语义字段、图标、允许子项以 `npx --no-install garden ui schema` 为准,按需存到 `.garden`,不预读整本目录。Markdown/安全HTML岛保存完整块内容;受限表达式不等于任意JS。
|
|
8
13
|
|
|
9
|
-
##
|
|
10
|
-
1.
|
|
11
|
-
2.
|
|
12
|
-
3.
|
|
13
|
-
4.
|
|
14
|
+
## 写入与验证
|
|
15
|
+
1. 必读 `references/workflow.md`:scan,保存id/行号、完整围栏base hash与原文。首次插入意图块和已有块apply不同;未编译意图不是完成品。
|
|
16
|
+
2. 按 `references/rendering-model.md` 的集中禁令与配方生成候选。正文默认继承文章动态字体/字号/行高;不缩字凑版。显式size只响应明确要求或真实层级。框读frames、颜色读colors;不要把场景框、内部边框和底色混为一谈。
|
|
17
|
+
3. 属性级微调优先,但当前apply仍替换完整围栏:候选文件+稳定id(没有才用scan行号)+原完整base hash;冲突即重读,不绕过保护。不误伤邻块或改写intent原话。
|
|
18
|
+
4. `scripts/check.mjs` 与 `garden check`保持原契约;有内部约束卡可另跑 `scripts/lint-layout.mjs`(workflow说明)。风险提示不等于语法错误;静态属性可能被丢弃,解析通过不等于生效。
|
|
19
|
+
5. 所有新布局都默认承担移动端责任,不等作者指出手机问题。按mobile-contract先验最窄屏和放大正文,再在完整主题与真实CSS顺序下检查桌面、computed样式、几何及阅读密度;交互另测。分别报告:结构/解析、渲染、浏览器几何/交互、独立AI生成试验。没有对应证据就写未验证。
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
1. 在站点根目录读 `references/workflow.md`。运行 `node .agents/skills/garden-compose/scripts/scan.mjs content/posts/路径.md`,保存目标 id/行号、原始完整围栏 hash、原文和 diagnostics。对已有正确块不重画。
|
|
17
|
-
2. 属性、默认值、允许子项与图标以 **`npx --no-install garden ui schema`** 为准;需要配方再运行 `npx --no-install garden ui prompt`。把 JSON 存到 `.garden` 后只读取当前需要的组件/图标,不预加载整本手册。静态布局部分属性可能被忽略,须检查归一化结果与真实样式,不能把「解析接受」当作「生效」。
|
|
18
|
-
3. 原文没有围栏时,仅在授权位置首次插入一个 `garden compose` 意图块,保留原文与效果要求,再 scan。该插入不是 apply API;未编译意图不是完成品。候选始终是完整 `garden compose`;编译后原话保留在 intent,不重复展示。
|
|
19
|
-
4. `text/title/caption` 用行内 Markdown 与安全行内 HTML;完整段落、列表、表格、代码块等放 `markdown/html` 内容岛。岛内允许实际白名单 HTML/CSS,不允许脚本、事件与危险 URL。需要执行的独立演示用同一 Compose 的 `sandbox`;DIL 表达式和岛内字面内容的边界见能力参考。不要把受限语言误称任意 HTML/JS,也不要把它误称完全不支持 Markdown/HTML。
|
|
20
|
-
5. 正文默认随**实际文章动态配置**继承字号/字体,不设通用像素基线、不缩字凑版。需要强调才用支持的 size/tone/weight 机制;标题、数值、图标签、caption 和控件保留语义层级。当前其他语义尺寸与上下文看 rendering-model,不把某张截图的 computed 值当全局常量。按需读 colors/frames/examples。
|
|
21
|
-
6. 候选存临时 Markdown,再用 `scripts/apply.mjs` 调权威 CLI 写回:有 id 用 `--id`,否则 scan 的 `--at`;总是加原完整围栏 `--base` hash 和候选 `--file`。原文变了就重新 scan,不移除保护强行覆盖;不使用空 intentHash 定位。
|
|
22
|
-
7. 运行 `scripts/check.mjs`;本地文件另做 `npx --no-install garden check 文章.md`。陌生布局/指定视觉效果还要在完整主题及真实 CSS 顺序下验证桌面、窄屏、几何与实际交互。分别报告「解析通过」「渲染成功」「几何/交互通过」与「独立模型生成可靠性」;手写场景的通过不能证明短提示词可靠。没有浏览器或独立试验就如实写未验证。
|
|
23
|
-
|
|
24
|
-
脚本只委托本地 CLI,不复制验证器、不下载包、不默认调用模型或改剪贴板。本地 garden 缺失就报告依赖问题,不另装版本。可复用的新经验只提出技能维护候选,文章处理时不自行改技能。
|
|
21
|
+
脚本仅使用已安装本地引擎,不下载包、不默认调用模型或导出全目录。缺依赖就报告;可复用维护经验只提出候选,不在文章任务里自行改技能。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Engine follow-ups · 本轮只记录
|
|
2
|
+
按预期收益而非虚构测量排序;skill规则不能替代这些运行时能力,修改需另行授权。
|
|
3
|
+
1. 高:静态未知/无效属性显式诊断。现归一化会丢弃,技能无法完整恢复作者意图。
|
|
4
|
+
2. 高:独立主轴/交叉轴/文字对齐,以及可靠的内容宽图标标题组合;centered-chip候选先跑完整主题几何再决定最小引擎修复。
|
|
5
|
+
3. 中:保留base冲突保护的属性patch/稳定块定位,减少微调必须整围栏替换的负担。
|
|
6
|
+
4. 中:完整主题的几何断言、真实列表间距和动态正文对照;先取证,不假定所有CSS默认错误。默认wrap的row与right箭头已有主题级横→纵同步回退,不能再列为缺失API;nowrap在手机仍横排而箭头转向的上下文需单独核查。
|
|
7
|
+
5. 后续:glyph光学齐平;行盒相等不保证视觉齐平。
|
|
8
|
+
未运行浏览器或独立模型实验时不得把以上能力标成已解决。
|
|
@@ -5,3 +5,9 @@
|
|
|
5
5
|
- `border="none"`, `background="transparent"`, and `backgroundColor="transparent"` affect different aspects of a supported node. Transparent background is not a border/frame removal API.
|
|
6
6
|
|
|
7
7
|
Don't add raw CSS to emulate unsupported framing. Don't promise frame:none for every rendering path; confirm the actual generated wrapper.
|
|
8
|
+
|
|
9
|
+
## 先选择层级,再动旋钮
|
|
10
|
+
1. 静态整个场景:唯一最外层`<figure frame="none">…</figure>`去场景边界;默认card。它不删除内部卡片,也不是Play头语法。
|
|
11
|
+
2. 内部容器:支持的card/box使用border或frame;card frame="text"是无表面的内容壳,不等于删除外场景。
|
|
12
|
+
3. 底色:background token或支持的backgroundColor;transparent不删除border。
|
|
13
|
+
“去外框”默认只改第1层;“无底色留内框”分别改第1、3层并保留第2层。编辑性说明偏向融入文章,独立实验保留必要边界;不因去框更换内容家族。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Layout recipes · 布局组合要点
|
|
2
2
|
|
|
3
|
-
Read only the section matching the requested layout. /
|
|
3
|
+
Read only the section matching the requested layout. / 按需读取对应布局小节;所有配方先读mobile-contract.md决定手机阅读任务,再选择局部结构。
|
|
4
4
|
|
|
5
5
|
## Horizontal equal-width cards · 横向等宽卡片
|
|
6
6
|
|
|
@@ -28,6 +28,7 @@ Use `<grid cols={3} gap={3}>` with three direct `<card>` children. `cols` accept
|
|
|
28
28
|
- **产品**:一款真正的好产品本身长什么样,有哪些绝不能碰的死穴。
|
|
29
29
|
使用 garden compose 来呈现横向等宽卡片。
|
|
30
30
|
</intent>
|
|
31
|
+
<figure frame="none">
|
|
31
32
|
<grid cols={3} gap={3}>
|
|
32
33
|
<card padding={3} radius="md" border="subtle" background="surface">
|
|
33
34
|
<title>人员</title>
|
|
@@ -42,6 +43,7 @@ Use `<grid cols={3} gap={3}>` with three direct `<card>` children. `cols` accept
|
|
|
42
43
|
<text>一款真正的好产品本身长什么样,有哪些绝不能碰的死穴。</text>
|
|
43
44
|
</card>
|
|
44
45
|
</grid>
|
|
46
|
+
</figure>
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
Follow workflow.md for id/line/hash safeguards. No default prompt export, clipboard or model call. Structural validation does not prove browser appearance. / 扫描、带 hash 单块写回和复检由 AI 内部完成;不默认导出提示词或调用模型,未看浏览器不声称视觉验收。
|
|
@@ -56,6 +58,7 @@ Follow workflow.md for id/line/hash safeguards. No default prompt export, clipbo
|
|
|
56
58
|
最小语法示意(实施时使用原文;占位文字不是正文素材):
|
|
57
59
|
```garden compose
|
|
58
60
|
<intent>语法示意:固定图标列,共用右侧文字列。</intent>
|
|
61
|
+
<figure frame="none">
|
|
59
62
|
<stack gap={3}>
|
|
60
63
|
<row wrap="nowrap" gap={3}>
|
|
61
64
|
<icon name="book-open" size="xl"/>
|
|
@@ -74,11 +77,39 @@ Follow workflow.md for id/line/hash safeguards. No default prompt export, clipbo
|
|
|
74
77
|
</group>
|
|
75
78
|
</row>
|
|
76
79
|
</stack>
|
|
80
|
+
</figure>
|
|
77
81
|
```
|
|
78
82
|
|
|
79
|
-
标题用 text + 加粗保证与正文同字号;需要不同标题层级才改用 title 并核对实际字号。图标选真实目录中的合适名字,不要求作者指定。divider 是外层 stack 的子项,不是 list
|
|
83
|
+
标题用 text + 加粗保证与正文同字号;需要不同标题层级才改用 title 并核对实际字号。图标选真实目录中的合适名字,不要求作者指定。divider 是外层 stack 的子项,不是 list 的直接子项。编辑性段落默认采用外层 figure frame="none" 融入文章;作者明确要独立场景边界时保留。不要误用内部 box 的 border。
|
|
80
84
|
|
|
81
85
|
上下文、默认值、prose 与 group 的差别及窄屏规则读取 rendering-model.md。检查后的视觉验收:图标列对齐;标题及所有正文段落共用右侧左边界;标题不居中;段落不回到图标列;每两个单元仅一条细线;没有卡片背景、箭头或窄屏横向溢出。
|
|
82
86
|
|
|
83
87
|
### Icon-led editorial list: operational summary
|
|
84
|
-
Use this for independent explanations, not a workflow. Derive the alignment hierarchy before selecting leaves: an outer `stack` repeats `row wrap="nowrap"` with a fixed-size direct `icon` and one flexible `group`. Put the bold heading and **every** original paragraph into separate `text` children of that same group. Place `divider` between rows in the outer stack. Do not place the body outside the row, center the ancestor, use a direct list divider, or substitute a nested stack without checking its contextual padding. The tested syntax above is illustrative; substitute real source text and catalog icons.
|
|
88
|
+
Use this for independent explanations, not a workflow. Derive the alignment hierarchy before selecting leaves: an outer `stack` repeats `row wrap="nowrap"` with a fixed-size direct `icon` and one flexible `group`. Put the bold heading and **every** original paragraph into separate `text` children of that same group. Place `divider` between rows in the outer stack. Do not place the body outside the row, center the ancestor, use a direct list divider, or substitute a nested stack without checking its contextual padding. The tested syntax above is illustrative; substitute real source text and catalog icons. For editorial prose prefer an outer frameless figure unless a scene boundary was requested; an explicit outer scene `figure frame="none"` removes only the scene frame. Verify shared heading/body left edges, fixed icon alignment, retained paragraphs, separators and no narrow-screen overflow under the complete article theme.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## flow-row / flow-stack · 短阶段与长阶段
|
|
92
|
+
只有原文明确顺序才用箭头。短阶段默认用普通row(保持默认wrap)中的text与right箭头;普通正文基线,不加粗、不缩字凑横排。当前完整主题在≤800px把这种row改成column,并取消right箭头的旋转;底层SVG本来向下,因此实际箭头随之朝下。不能只读data-dir="right"就误报移动端方向错误,要核对computed transform或SVG屏幕向量。
|
|
93
|
+
本轮完整主题1200/500浏览器已验证默认row的横→纵回退。长阶段或明确纵向请求仍用stack与down箭头。不要加nowrap:手机保持横排时主题仍可能把right箭头画向下;只有显式要求、实际验证方向与溢出均正确时才偏离默认。没有必要为默认响应式回退询问作者,也不发明响应式属性。
|
|
94
|
+
作者明确“略小”时仅在文字叶子设sm,勿父子重复缩放;编辑性流程默认外figure frame="none"。不把配方验证扩大到所有主题/字体或未知嵌套。
|
|
95
|
+
|
|
96
|
+
## centered-chip · 整体居中、图标贴标题(候选,待浏览器验证)
|
|
97
|
+
编辑性说明默认用外figure frame="none"包裹;这与内部frame="text"标题壳不是同一层。关系是“图标+标题”为一个内容宽整体,正文另占一行;它是局部标题效果,不能默认把整份长清单变成重复居中的大展示单元;不是图标与一个flex:1标题平分整格。
|
|
98
|
+
候选沿用真实文章的结构:`stack align="center" → card frame="text" padding={0} → row align="center" gap={0} wrap="nowrap" → icon inline + text`;正文作为外stack的下一text。frame=text标题壳不是信息卡片。不得把此候选称作跨主题已验证;检查标题壳实际宽度、图文距离、图标与标题行盒、整体中心、长标题、窄屏溢出。icon inline当前另带6px右距,gap=0不等于零距离;row>text仍有flex规则。若失败,记录引擎缺口,不用换字号掩盖。
|
|
99
|
+
|
|
100
|
+
## prose-list · 正文列表手感
|
|
101
|
+
不需要场景时保留普通Markdown。需要嵌入icon-rail时,把真实Markdown无序/有序列表放右侧group内的markdown岛,保留全部子项和层级。title/text是行内内容,不能用“•”拼出列表。结构上核对ul/li;完整主题另验悬挂缩进、段距和换行,不由AST宣称观感相同。
|
|
102
|
+
|
|
103
|
+
## frameless-scene · 正交的框选项
|
|
104
|
+
编辑性说明默认外`figure frame="none"`,内card是否有border、background分别决定;独立实验或明确要求场景边界时保留外框。详见frames.md。默认不加size/tone来补救布局,不删事实、不裁正文。
|
|
105
|
+
|
|
106
|
+
## 内部验收卡(不是作者表单)
|
|
107
|
+
选定路线后在工作笔记记:读者问题、源条目/段落数、共享容器与悬挂、居中对象、相对正文的字号、三层框选择、桌面/窄屏回退、禁止项。唯一推荐不等于凭经验宣称已验证:centered-chip与响应式flow尚须实际浏览器证据;已有icon-rail/equal-cards也须在本次上下文核对。
|
|
108
|
+
|
|
109
|
+
## 各路线共用的移动责任
|
|
110
|
+
- flow-row:响应式阅读任务声明为reflow时,不用nowrap硬撑阶段标签。row方向和实际绘制箭头必须一起改变;换行/纵排/横滚各自不同,不能只看无外层溢出就判通过。
|
|
111
|
+
- equal-cards:单列后再次核对每项扫读节奏;若原本只是短标签/短问句,优先选择紧凑列表/分组而不是机械重复卡片。显式指定卡片时保留要求,自然段长则自然增长,不删字、不设固定高。
|
|
112
|
+
- icon-rail:nowrap只固定图标+可收缩右栏的局部关系,不是让整组条目横排;长段落/真列表在右group内自然换行。
|
|
113
|
+
- centered-chip:只承诺局部贴标。完整问句、长标题、双语标签和放大字体必须实测;不自动把所有正文居中。长扫描清单优先开始对齐的连续阅读,必要时用局部重点居中,不通过sm来压高度。
|
|
114
|
+
- prose-list / frameless:保持真实标记、顺序、正文基线和自然段落;去掉无意义的多层场景表面不是删掉所有必要分组。
|
|
115
|
+
同一树能响应式重排时使用已有机制;若需要尚未支持的断点条件对齐/拓扑,不复制两套文字隐藏一套、不发明媒体属性。采用兼容两端且保真的最小方案并注明视觉取舍;无法满足作者明确硬约束时标未完成,而非让作者做设备调参。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Mobile contract · 阅读优先的内部编译契约
|
|
2
|
+
作者只提供原文与效果;手机不是额外选项,也不是作者要填写的规格。先识别读者任务,再做视觉配方,最后在真实小屏验证。
|
|
3
|
+
|
|
4
|
+
## 生成前:两个尺度、一份原文
|
|
5
|
+
AI内部记录:读者扫读/顺序/对照/空间关系;条目数及长短分布;标题与正文的共享边界;整体/局部居中;桌面与窄屏的流向;正文相对字号;框层级;应保留的顺序、完整段落和重点。不让作者选择断点、标签和font-size。
|
|
6
|
+
先从最窄阅读宽度推导可持续结构,再扩展桌面。优先同一内容树重排,不复制两份文字,不删事实、不缩字凑版、不把长内容藏进交互。作者明确的局部效果不自动等于整个场景的阅读模式;居中图标标题不能推导全部长正文居中。
|
|
7
|
+
|
|
8
|
+
## 三道门,不能互相替代
|
|
9
|
+
1. **结构/源保真**:权威parser/check;原文各叶子及顺序、真实列表、共享容器、未知属性限制。内部约束卡可选mobile,见workflow;不是围栏属性或作者新API。
|
|
10
|
+
2. **浏览器几何**:真实CSS viewport与available content width;当前主题/完整CSS顺序;正文默认及放大字号;每个滚动容器scrollWidth/clientWidth,不只页面外框;最窄叶子的换行与文字宽度;实际SVG屏幕向量和父布局轴;不裁切、遮挡、重叠或隐藏事实。data-dir、DOM存在和无页面横滚都不是充分证据。
|
|
11
|
+
3. **阅读节奏人工审查**:把整组而非一个漂亮单元看完。能否迅速找下一项?有没有多余重复标题、层层表面、每条都成为独立海报、过量空白或居中长段让眼睛反复找起点?很长的真实材料可以跨多屏;禁止用“必须一屏”迫使删字、缩字或隐藏。
|
|
12
|
+
|
|
13
|
+
## 通用测试矩阵,不是设计硬阈值
|
|
14
|
+
起步样本可用CSS宽375/430/768/1200,以及正常正文和1.25倍正文;这些只是回归采样,不是作者批准的断点或科学最优值。必须记录浏览器实际innerWidth,不把截图物理像素或Chrome窗口参数当CSS viewport。主题已有≤800px机制要按实际CSS理解。
|
|
15
|
+
记录sceneHeight/availableHeight、每项行数、字号相对正文、内部横滚、每项起点及序号可见性;这些是证据,不是把某个比值硬判为不美观。可疑高空白/重复表面/多行短标签要求人工复核;任何“最多几项/最多几屏/字号下限”的新阈值都先是待校准建议,不可伪称用户规则或测量结论。
|
|
16
|
+
|
|
17
|
+
## 默认修复顺序由AI承担
|
|
18
|
+
错误阅读拓扑/容器 → 不必要nowrap或宽度约束 → 重复框与间距 → 局部对齐。字号最后且只用于真实层级/明确需求,不为修布局来回sm/lg。局部图标栏的nowrap可以合理,不能全局禁用。
|
|
19
|
+
默认wrap的短阶段row已有手机纵排+箭头转向;不要要求作者决定技术回退。硬nowrap阶段链会保留横向却可能出现向下箭头,并把文字挤成多行;即使没有scroll也失败。
|
|
20
|
+
当只靠现有语法不能同时满足显式硬约束时,交付中记录准确能力缺口与保真取舍,不能假称视觉完成。几何通过后仍需阅读审查,缺浏览器就标移动端待验,不把QA任务退给作者。
|
|
21
|
+
|
|
22
|
+
## 防止只记住这次两张图
|
|
23
|
+
冻结首稿,先评估再修。回归覆盖短/长顺序、短并列与长扫描清单、多段解释、嵌套真列表、长中英混排/链接和放大字体。独立AI只读原文+一句效果/参考图,不能看到标准输出。区分同一上下文多例与多次隔离试验,不用一次通过宣称可靠率。
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
- icon 名查 schema/icons 实际目录;不存在报错。SVG width 属性不等于最终像素:当前 CSS sm13、md15、lg18、xl22px,小卡上下文另有覆盖,必须测 computed/rect。
|
|
11
11
|
- 多段正文分别放 text,或用 markdown/html 岛保存真实 p。text 内空行是 inline break,不是独立段落。图标签有截断/行钳制时不能塞完整正文去丢字。
|
|
12
12
|
|
|
13
|
+
## 集中禁令与风险分级
|
|
14
|
+
- **必选结构(已选icon-rail)**:icon和一个group同属row,标题与全部正文/列表同属group;分隔线在row之间。不要正文逃出右栏,不用row>stack替代group来碰运气。
|
|
15
|
+
- **无效属性**:stack不因direction="row"变横向;用真实row。未知静态属性可能归一化丢失,当前lint不能从归一化树找回它们;对照源属性与schema,缺少未知属性诊断是引擎需求,不谎称已机器覆盖。
|
|
16
|
+
- **风险而非全局错误**:row>stack的12px顶距、居中row直接title/text的flex:1、center同时改变主轴/交叉轴/文字、nowrap长链、显式size和叠框。这些在其他语境可能有意,必须结合内部约束卡判断,不能一律禁用。
|
|
17
|
+
- **箭头看实际绘制方向**:right是源意图,不必等于当前屏幕方向。真实SVG默认向下;桌面旋转为right,≤800px取消旋转配合默认row转column。先看computed transform/屏幕向量,不把data-dir当几何证据。nowrap让row保持横向却不自动保留箭头旋转,须专项检查。
|
|
18
|
+
- **必须浏览器**:icon glyph光学齐平不是节点矩形相等;内容宽居中、完整主题字号/列表间距、溢出与手机回退不能从AST推断。用computed与rect及截图分别取证。
|
|
19
|
+
|
|
13
20
|
## 默认与显式样式
|
|
14
21
|
普通正文默认继承实际文章动态字体/字号;不再使用固定正文像素基线。text/title 的 sm=.875em、lg=1.125em、xl=1.25em,md 保持基线;title 默认 600/1.4。外层 figure 标题18px、caption14px及控件/图标签等是语义角色,不应全局拉成正文。其他语义尺寸及实际上下文见下表与完整主题。显式字体变化必须有信息层级依据,不缩字替代排版。
|
|
15
22
|
|
|
@@ -2,12 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Load when choosing how to explain source content, not for every repair. Use the current lowercase Compose surface. / 需要选图时读取,不是每次修复都读。
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
5
|
+
## Effect compiler · 效果先收敛
|
|
6
|
+
作者给原文+一句效果/参考图即可;以下是AI内部动作,不要求作者懂标签。
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
1. 确定读者问题、移动阅读任务和原文真实关系;尊重明确效果。没有顺序不画流程,没有因果不加因果箭头,没有数据不造图表。
|
|
9
|
+
2. 写一张内部几何约束卡(工作笔记/临时JSON即可,非新作者语法):读者问题;共享容器;悬挂与正文范围;居中的是文字还是图标标题整体;字号相对正文;场景框/内部边框/底色;窄屏回退;不要什么。参考图也先提取这些约束,不能只识别“图标+文字”。
|
|
10
|
+
3. 全局阅读结构先于局部效果:长清单的任务是连续扫读,不是把每项放大为一个居中展示单元。按mobile-contract检查内容量、长短差异和小屏阅读节奏,再按下表取一条推荐路线,填原文叶子。默认正文基线、开始对齐、自然换行,不新增强调色;只在有意强调时加样式。未知但不影响事实的细节用默认,不把问题推给作者;可能改变事实关系、隐藏内容或阅读顺序时才问。
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
| 效果/读者问题 | 唯一推荐路线 | whenNot / 不这样用 |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| 图标窄栏、标题正文共左缘 | icon-rail:stack重复row(icon+group),divider在row间 | 不把正文放row外;不当流程 |
|
|
15
|
+
| 短阶段从左到右 | flow-row:row+真实顺序箭头;窄屏/长内容回退flow-stack | 不靠缩字/nowrap强撑手机 |
|
|
16
|
+
| 长阶段或纵向顺序 | flow-stack:stack+下箭头 | 无顺序的条目不加箭头 |
|
|
17
|
+
| 并列等宽卡片 | equal-cards:grid+直接card | 非真实共同维度不走compare |
|
|
18
|
+
| 图标贴标题,两者一起居中 | centered-chip:内容宽标题壳+内部图标标题行,下方正文独立 | 不只给全宽row设center;候选须浏览器验收 |
|
|
19
|
+
| 跟正文一样的列表 | prose-list:普通Markdown;场景内用markdown岛 | 不写假圆点;不为列表手感换到compare |
|
|
20
|
+
| 去外框/留内框 | 原路线+frameless-scene选项:外figure frame=none | 不把透明底色当去边框,不改错层级 |
|
|
21
|
+
|
|
22
|
+
这些是**唯一推荐默认**,不是组件白名单;先使用最接近的已验局部结构,确有关系需求再按composition-patterns组合新形式,并保留同样约束和验证。frame/tone是正交选项,不衍生新的配方家族。
|
|
23
|
+
|
|
24
|
+
边界:普通Markdown优先;compare仅真实共同维度对照。当前只有garden compose围栏,旧garden list不再推荐。Compose内部icon-list仅在需要其原生语义/宿主行为且接受其样式契约时用;普通编辑性图标段落默认icon-rail,不能仅为调字号来回迁移。需要语义/交互时再读static-play,不靠标签同名猜兼容。
|
|
11
25
|
|
|
12
26
|
## Capability map · 典型形式与边界
|
|
13
27
|
|
|
@@ -36,3 +50,7 @@ This is a selection map, not a new syntax catalog. For chart attributes use the
|
|
|
36
50
|
No arbitrary SVG/canvas, custom CSS/JS, general node-id edge routing, automatic graph/tree placement, simulated execution, arbitrary nested rich `compare` items, or full parity between static and Play. A small drawn branch does not imply support for a full flowchart language. `steps` detail toggles and `tabs` are not numerical Play state.
|
|
37
51
|
|
|
38
52
|
If the requested relation needs unsupported routing/interaction, state the gap and suggest a simpler faithful Compose scene or an existing appropriate article format; do not silently switch engines or invent tags. Validation proves syntax/semantics only; inspect the browser before claiming visual/responsive behavior was verified.
|
|
53
|
+
|
|
54
|
+
## Mobile is part of compilation · 不是交给作者的QA
|
|
55
|
+
断点自动单列只证明重排,不证明好读。centered-chip是局部贴标结构,不是长清单的全局默认;equal-cards也是按信息关系选择,不是见到N项就生成N张大卡。普通短清单优先prose-list;有独立标题/多段解释才用共享右栏。明确要求的卡片/居中仍须保留其意图,但不能以缩字、硬横排、折叠事实来达成。
|
|
56
|
+
长内容自然跨屏不算失败;要识别的是重复装饰、冗余表面、过量容器间距、窄列拆词和反复居中造成的阅读负担。移动约束由AI内部收敛与验证,不询问作者要断点、字号或方向细节。当前能力确实无法同时满足硬约束时如实标未完成/引擎缺口,不静默改事实或发明属性。
|
|
@@ -25,3 +25,22 @@ node .agents/skills/garden-compose/scripts/check.mjs content/posts/post.md
|
|
|
25
25
|
## Prose outside a fence · 围栏外的原文
|
|
26
26
|
|
|
27
27
|
Separate the source paragraphs/list items from the requested effect. At the authorized location, create one `garden compose` intent block preserving both verbatim, then scan it. Initial insertion is distinct from replacing a block: `apply` requires a scanned existing target. For existing blocks, use scan → candidate → apply with the original base hash → check. Preserve unrelated prose and render the source once. / 分离原文与效果要求,在授权位置首次创建 `garden compose` 意图块后扫描;已有块始终带原 base hash 写回并复检,不把 apply 当作插入 API,不重复展示原文。
|
|
28
|
+
|
|
29
|
+
## Local micro-edit · 微调不重画
|
|
30
|
+
scan → 保存稳定id(没有则目标行)与完整围栏base → 仅改候选中的必要属性/叶子 → 审查diff → 带原base apply → rescan/check。去粗先区分源Markdown加粗与title默认600;略小仅在明确要求时改文字叶子size;紧一点改对应容器gap;去外框只改最外figure。不要为了旋钮变化换标签家族或清空intent。稳定id在获授权编辑该块时建立,不全篇加id。并发修改造成base冲突必须重新读取。
|
|
31
|
+
当前没有属性patch或dry-run;apply始终写回完整围栏。最小候选diff不等于局部写入API;保留围栏外字节及完整原文核对。
|
|
32
|
+
|
|
33
|
+
## Opt-in layout check · 独立的内部约束检查
|
|
34
|
+
原check.mjs的参数/退出状态不变。AI内部约束卡可保存为临时JSON(不是围栏属性/作者新语法):
|
|
35
|
+
```json
|
|
36
|
+
{"at":90,"base":"扫描所得完整围栏hash","layout":"icon-rail","units":3,"outerFrame":"none"}
|
|
37
|
+
```
|
|
38
|
+
有稳定id用id替代at;base必填。运行:
|
|
39
|
+
```sh
|
|
40
|
+
node .agents/skills/garden-compose/scripts/lint-layout.mjs content/posts/post.md --constraints .garden/layout.json
|
|
41
|
+
```
|
|
42
|
+
仅支持当前静态Compose;复用实际本地hd-garden的scanner和parser,不下载包、不复制解析器。错误退出1,工具/约束输入错误退出2,风险/浏览器待验不使退出失败。只对显式声明的icon-rail/equal-cards和outerFrame做有限结构断言;其他路线仍需人工结构清单。不能从归一化树还原未知属性、推断自然语言效果或验证几何;保真核对另做,不把机器支持范围夸大。
|
|
43
|
+
|
|
44
|
+
## Mobile acceptance · AI内部,不要求作者补规格
|
|
45
|
+
几何卡补可选`"mobile":{"readingMode":"sequence","reflow":true}`;readingMode可选sequence/scan-list/comparison/spatial,reflow为布尔。lint只在显式reflow的flow-row中拒绝场景顶层流向row的nowrap(不误禁内部贴标row);icon-rail局部nowrap不被误禁。scan-list的成组居中只给风险提示,不凭AST断言过高或难读。
|
|
46
|
+
浏览器验证遵守mobile-contract:记录实际CSS viewport、内容宽、正常/放大正文、每层滚动与实际箭头向量,并人工审查整组阅读节奏。保留首稿和源hash,失败后的修订单独编号,不能把修后结果算首轮成功。默认check参数/退出状态不变,mobile字段不是runtime DIL属性。
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {createRequire} from 'node:module';
|
|
4
|
+
import {pathToFileURL} from 'node:url';
|
|
5
|
+
const layouts = new Set(['icon-rail','equal-cards','flow-row','flow-stack','centered-chip','prose-list']);
|
|
6
|
+
export function validateCard(card) {
|
|
7
|
+
const keys = new Set(['id','at','base','layout','units','outerFrame','mobile']);
|
|
8
|
+
if (!card || typeof card !== 'object' || Array.isArray(card) || Object.keys(card).some(k=>!keys.has(k))) throw new Error('Unknown constraint field/object');
|
|
9
|
+
if ((typeof card.id==='string' && card.id.length>0) === (Number.isInteger(card.at) && card.at>0)) throw new Error('Choose exactly one nonempty id or positive at line');
|
|
10
|
+
if (card.id!==undefined && (typeof card.id!=='string'||!card.id)) throw new Error('Invalid id');
|
|
11
|
+
if (card.at!==undefined && (!Number.isInteger(card.at)||card.at<1)) throw new Error('Invalid at');
|
|
12
|
+
if (!/^[a-f0-9]{8}$/.test(card.base??'')) throw new Error('base must be the scanned complete fence hash');
|
|
13
|
+
if (!layouts.has(card.layout)) throw new Error('Unsupported layout recommendation');
|
|
14
|
+
if (card.units!==undefined && (!Number.isInteger(card.units)||card.units<1)) throw new Error('units must be positive');
|
|
15
|
+
if (card.outerFrame!==undefined && !['none','card'].includes(card.outerFrame)) throw new Error('Invalid outerFrame');
|
|
16
|
+
if (card.mobile!==undefined) {
|
|
17
|
+
const m=card.mobile;
|
|
18
|
+
if (!m || typeof m!=='object' || Array.isArray(m) || Object.keys(m).some(k=>!['readingMode','reflow'].includes(k))) throw new Error('Invalid mobile constraint');
|
|
19
|
+
if (m.readingMode!==undefined && !['sequence','scan-list','comparison','spatial'].includes(m.readingMode)) throw new Error('Invalid mobile readingMode');
|
|
20
|
+
if (m.reflow!==undefined && typeof m.reflow!=='boolean') throw new Error('Invalid mobile reflow');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function inspectLayout(doc, card) {
|
|
24
|
+
validateCard(card);
|
|
25
|
+
const diagnostics=[];
|
|
26
|
+
const add=(severity,code,message)=>diagnostics.push({severity,code,message});
|
|
27
|
+
if (!Array.isArray(doc?.nodes)) throw new Error('Only normalized static Compose is supported');
|
|
28
|
+
const nodes=doc.nodes.length===1 && doc.nodes[0].type==='figure' ? doc.nodes[0].children : doc.nodes;
|
|
29
|
+
if (card.outerFrame && (doc.frame??'card')!==card.outerFrame) add('error','outer-frame','Declared scene frame does not match normalized scene');
|
|
30
|
+
if (card.layout==='icon-rail') {
|
|
31
|
+
const stack=nodes.length===1 && nodes[0].type==='stack' ? nodes[0] : null;
|
|
32
|
+
if (!stack) add('error','rail-container','Recommended rail needs one outer stack');
|
|
33
|
+
else {
|
|
34
|
+
const rows=stack.children.filter(n=>n.type==='row');
|
|
35
|
+
if (!rows.length || stack.children.some(n=>!['row','divider'].includes(n.type))) add('error','rail-outside-body','Only rows and between-row dividers belong in the outer rail');
|
|
36
|
+
if (card.units!==undefined && rows.length!==card.units) add('error','unit-count','Row count differs from declared source units');
|
|
37
|
+
for (const [i,n] of stack.children.entries()) if (n.type==='divider' && (stack.children[i-1]?.type!=='row'||stack.children[i+1]?.type!=='row')) add('error','rail-divider','Divider must separate two rows');
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
const icons=row.children.filter(n=>n.type==='icon'), groups=row.children.filter(n=>n.type==='group');
|
|
40
|
+
if (row.children.length!==2 || icons.length!==1 || groups.length!==1 || row.children[0].type!=='icon') add('error','rail-shared-container','Row needs icon then exactly one shared right-hand group');
|
|
41
|
+
const group=groups[0];
|
|
42
|
+
if (group && (group.children.length<2 || !['text','title'].includes(group.children[0].type))) add('error','rail-heading-body','Group needs heading and body/content leaves');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (card.layout==='equal-cards') {
|
|
47
|
+
const grid=nodes.length===1 && nodes[0].type==='grid' ? nodes[0] : null;
|
|
48
|
+
if (!grid || !grid.children.length || grid.children.some(n=>n.type!=='card')) add('error','cards-grid','Recommended equal cards need one grid with direct cards');
|
|
49
|
+
else if (card.units!==undefined && (grid.children.length!==card.units || grid.cols!==Math.min(card.units,6))) add('error','unit-count','Grid cards/columns differ from declared source units');
|
|
50
|
+
}
|
|
51
|
+
const visit=(n,parent)=>{
|
|
52
|
+
if (card.mobile?.reflow===true && card.layout==='flow-row' && nodes.includes(n) && n.type==='row' && n.wrap==='nowrap') add('error','mobile-flow-nowrap','Declared responsive sequence cannot hard-lock its row; inspect painted arrows and narrow label wrapping');
|
|
53
|
+
if (card.mobile?.readingMode==='scan-list' && ['stack','grid'].includes(n.type) && n.align==='center' && (n.children??[]).filter(c=>['card','group','stack'].includes(c.type)).length>1) add('warning','mobile-scan-centering','Repeated centered scan units require whole-scene density and reading review; AST does not prove a failure');
|
|
54
|
+
if (parent?.type==='row' && n.type==='stack') add('warning','row-stack-padding','Current theme adds 12px top padding');
|
|
55
|
+
if (n.type==='row' && n.align==='center' && n.children?.some(c=>['title','text'].includes(c.type))) add('warning','center-flex','Center couples axes/text; direct title/text keep flex:1');
|
|
56
|
+
if (n.type==='row' && n.wrap==='nowrap') add('warning','nowrap','Browser must verify wrapping and overflow');
|
|
57
|
+
if (n.size && n.size!=='md') add('warning','normalized-size','Non-baseline normalized size: compare with source intent; normalization does not prove an authored attribute');
|
|
58
|
+
if (n.type==='card' && parent?.type==='card') add('warning','nested-surface','Check whether nested surfaces are intentional');
|
|
59
|
+
for (const child of n.children??[]) visit(child,n);
|
|
60
|
+
};
|
|
61
|
+
doc.nodes.forEach(n=>visit(n,null));
|
|
62
|
+
return {ok:!diagnostics.some(d=>d.severity==='error'),diagnostics,coverage:{structural:[...(card.outerFrame?['declared outerFrame']:[]),...(['icon-rail','equal-cards'].includes(card.layout)?[card.layout+' shape']:[]) ,...(card.mobile?.reflow===true&&card.layout==='flow-row'?['declared mobile top-level flow nowrap guard']:[])],notChecked:['source fidelity','unknown source attributes','other route structures','DOM geometry','responsive layout','optical alignment','AI reliability']},browserRequired:true};
|
|
63
|
+
}
|
|
64
|
+
async function main() {
|
|
65
|
+
const args=process.argv.slice(2);
|
|
66
|
+
if (args.length!==3 || args[1]!=='--constraints') throw new Error('Usage: lint-layout.mjs post.md --constraints card.json');
|
|
67
|
+
const card=JSON.parse(fs.readFileSync(args[2],'utf8')); validateCard(card);
|
|
68
|
+
const cwd=process.cwd();
|
|
69
|
+
const localManifest=path.join(cwd,'package.json');
|
|
70
|
+
const local=JSON.parse(fs.readFileSync(localManifest,'utf8'));
|
|
71
|
+
const root=local.name==='hd-garden'?cwd:path.dirname(createRequire(localManifest).resolve('hd-garden/package.json'));
|
|
72
|
+
// These exported functions in the installed package are authoritative. No parser copy/fallback/download.
|
|
73
|
+
const {scanMarkdown,checkComposeFence}=await import(pathToFileURL(path.join(root,'ui/core/scan.mjs')).href);
|
|
74
|
+
const {scanFences}=await import(pathToFileURL(path.join(root,'ui/core/fence.mjs')).href);
|
|
75
|
+
const file=path.resolve(args[0]), source=fs.readFileSync(file,'utf8');
|
|
76
|
+
const blocks=scanMarkdown(source,{markdownPath:file});
|
|
77
|
+
const matches=blocks.filter(b=>card.id?b.id===card.id:b.startLine===card.at);
|
|
78
|
+
if (matches.length!==1) throw new Error('Constraint locator must match exactly one block');
|
|
79
|
+
const block=matches[0];
|
|
80
|
+
if (block.hash!==card.base) throw new Error('Stale constraint base; rescan before checking');
|
|
81
|
+
const fence=scanFences(source).find(f=>f.startLine===block.startLine);
|
|
82
|
+
const parsed=checkComposeFence(fence,{markdownPath:file});
|
|
83
|
+
if (!parsed.ok) { console.log(JSON.stringify({ok:false,diagnostics:parsed.diagnostics,browserRequired:true},null,2)); process.exitCode=1; return; }
|
|
84
|
+
const result=inspectLayout(parsed.doc,card);
|
|
85
|
+
console.log(JSON.stringify(result,null,2)); process.exitCode=result.ok?0:1;
|
|
86
|
+
}
|
|
87
|
+
if (process.argv[1] && import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href) main().catch(error=>{console.error(JSON.stringify({ok:false,code:'layout-input',message:error.message}));process.exitCode=2;});
|
|
@@ -75,7 +75,7 @@ export default async function DraftPreviewPage({ params }: { params: Promise<{ p
|
|
|
75
75
|
草稿预览 · {post}{draft ? " · 草稿" : ""} · <Link href="/admin/ui/blocks">回到交互块</Link>
|
|
76
76
|
</p>
|
|
77
77
|
<h1 className="ui-admin-preview-title">{typeof data.title === "string" ? data.title : post}</h1>
|
|
78
|
-
{typeof data.description === "string" ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
78
|
+
{typeof data.description === "string" && data.description.trim() ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
79
79
|
<article className="article-body">
|
|
80
80
|
<MarkdownArticle content={proxyAssets(content, post)} numbering={data.numbering === false ? false : undefined} />
|
|
81
81
|
</article>
|
|
@@ -38,7 +38,7 @@ Three commands check your content at different scopes:
|
|
|
38
38
|
|
|
39
39
|
| Code | Severity | Cause and Fix |
|
|
40
40
|
| :--- | :--- | :--- |
|
|
41
|
-
| `missing-title` / `
|
|
41
|
+
| `missing-title` / `invalid-description` | Error | The title is missing or empty, or description is neither a string nor null; a blank description is valid |
|
|
42
42
|
| `unexpected-slug` | Error | Front Matter includes a manual `slug` field (remove `slug`; the filename is the slug) |
|
|
43
43
|
| `date-format` | Error | `date` is not a valid `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or ISO timestamp with timezone |
|
|
44
44
|
| `missing-cover` / `missing-asset` | Error | A local file referenced by `cover`, a Markdown image, or Compose `file` does not exist |
|
|
@@ -36,7 +36,7 @@ references:
|
|
|
36
36
|
| :--- | :--- | :--- | :--- |
|
|
37
37
|
| `title` | Yes | String | Post title. Write ` \| ` (with spaces on both sides) to split the heading into two lines and scale the font size to fit |
|
|
38
38
|
| `date` | Yes | Date string | Publication date (such as `2026-10-18` or an ISO timestamp with timezone) |
|
|
39
|
-
| `description` |
|
|
39
|
+
| `description` | No | String | Summary shown in the article deck, archive lists, and search engine descriptions. Empty (`description: ""`), omitted, null, or whitespace-only values hide the summary; Garden does not substitute an excerpt from the body |
|
|
40
40
|
| `updated` | No | Date string | Last updated date, shown in the article meta bar and used for RSS sorting |
|
|
41
41
|
| `publishAt` | No | Date string | Scheduled publish time; included only when earlier than the build time (or when `CONTENT_INCLUDE_DRAFTS=1`) |
|
|
42
42
|
| `tags` | No | String array | Tags for the post; clicking a tag filters `/blog?t=<tag>` |
|
|
@@ -38,7 +38,7 @@ order: 40
|
|
|
38
38
|
|
|
39
39
|
| 诊断码 | 级别 | 触发原因与修复方法 |
|
|
40
40
|
| :--- | :--- | :--- |
|
|
41
|
-
| `missing-title` / `
|
|
41
|
+
| `missing-title` / `invalid-description` | 错误 | `title` 缺失或为空,或 `description` 不是字符串/空值;摘要可以留空 |
|
|
42
42
|
| `unexpected-slug` | 错误 | Front Matter 中手写了 `slug` 字段(文件名即标识,删掉 `slug`) |
|
|
43
43
|
| `date-format` | 错误 | `date` 不是合法的 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm:ss` 或带时区的 ISO 时间 |
|
|
44
44
|
| `missing-cover` / `missing-asset` | 错误 | `cover` 封面图、正文图片或 Compose `file` 引用的本地文件不存在 |
|
package/docs/handbook/writing.md
CHANGED
|
@@ -36,7 +36,7 @@ references:
|
|
|
36
36
|
| :--- | :--- | :--- | :--- |
|
|
37
37
|
| `title` | 是 | 字符串 | 文章标题。中间写 `丨`、`|` 或 ` \| ` 会分成两行并按字数缩放字号 |
|
|
38
38
|
| `date` | 是 | 日期字符串 | 发布日期(如 `2026-10-18` 或带时区的 ISO 时间) |
|
|
39
|
-
| `description` |
|
|
39
|
+
| `description` | 否 | 字符串 | 文章摘要,显示在正文开头导语、列表页与搜索引擎摘要中;留空(如 `description: ""`)、省略、YAML `null` 或只写空白时,不渲染摘要,也不会自动截取正文替代 |
|
|
40
40
|
| `updated` | 否 | 日期字符串 | 最后更新日期,显示在文章元信息栏并用于 RSS 排序 |
|
|
41
41
|
| `publishAt` | 否 | 日期字符串 | 定时发布时间;早于当前构建时间(或设置 `CONTENT_INCLUDE_DRAFTS=1`)才会发布 |
|
|
42
42
|
| `tags` | 否 | 字符串数组 | 文章标签列表,点击标签会跳转到 `/blog?t=<标签>` 筛选 |
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-garden",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"editors:build": "node editors/vscode/build.mjs && node editors/obsidian/build.mjs && node editors/typora/build.mjs",
|
|
72
72
|
"editors:pack": "node scripts/pack-editors.mjs",
|
|
73
73
|
"prepack": "node scripts/pack-editors.mjs",
|
|
74
|
-
"lint": "node bin/garden.mjs sync && eslint . --ignore-pattern dist --ignore-pattern .next --ignore-pattern .garden --ignore-pattern templates"
|
|
74
|
+
"lint": "node bin/garden.mjs sync && eslint . --ignore-pattern dist --ignore-pattern .next --ignore-pattern .garden --ignore-pattern templates",
|
|
75
|
+
"version": "node scripts/sync-editor-version.mjs && git add editors/vscode/package.json editors/vscode/package-lock.json"
|
|
75
76
|
},
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@cloudflare/vite-plugin": "1.37.1",
|
|
@@ -409,7 +409,9 @@ function validateAndNormalize(data, content, markdownPath, kind, sourcePath, lin
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
if (kind === "post") {
|
|
412
|
-
|
|
412
|
+
const description = optionalString(data, "description", sourcePath);
|
|
413
|
+
// An explicit blank is intentional: never replace it with an excerpt from the body.
|
|
414
|
+
if (!description) data.description = "";
|
|
413
415
|
}
|
|
414
416
|
if (kind === "thought" && data.mediaType) {
|
|
415
417
|
if (!["image", "audio", "video", "link", "text"].includes(data.mediaType)) {
|
package/scripts/check-post.mjs
CHANGED
|
@@ -215,8 +215,8 @@ const checkFile = (filePath) => {
|
|
|
215
215
|
if (typeof data.title !== "string" || !data.title.trim()) {
|
|
216
216
|
error(errors, "missing-title", 1, `${sourcePath}: front matter 缺少有效的 title`);
|
|
217
217
|
}
|
|
218
|
-
if (typeof data.description !== "string"
|
|
219
|
-
error(errors, "
|
|
218
|
+
if (data.description != null && typeof data.description !== "string") {
|
|
219
|
+
error(errors, "invalid-description", 1, `${sourcePath}: description 必须是字符串或留空`);
|
|
220
220
|
}
|
|
221
221
|
if (data.slug !== undefined) {
|
|
222
222
|
error(errors, "unexpected-slug", 1, `${sourcePath}: 不要写 slug,文件名就是标识`);
|
package/scripts/pack-editors.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
|
+
import { syncEditorVersion } from "./sync-editor-version.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const distDir = path.join(root, "editors", "dist");
|
|
@@ -16,7 +17,8 @@ function distReady() {
|
|
|
16
17
|
const vsixDir = path.join(distDir, "vscode");
|
|
17
18
|
const manifest = path.join(distDir, "typora", "manifest.json");
|
|
18
19
|
if (!fs.existsSync(manifest) || !fs.existsSync(vsixDir)) return false;
|
|
19
|
-
|
|
20
|
+
const version = syncEditorVersion({ check: true });
|
|
21
|
+
return fs.existsSync(path.join(vsixDir, `garden-ui-${version}.vsix`));
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function withPackLock(fn) {
|
|
@@ -166,6 +168,7 @@ function vsixManifest(manifest) {
|
|
|
166
168
|
<Identity Language="en-US" Id="${xmlEscape(manifest.name)}" Version="${xmlEscape(manifest.version)}" Publisher="${xmlEscape(manifest.publisher)}"/>
|
|
167
169
|
<DisplayName>${xmlEscape(manifest.displayName)}</DisplayName>
|
|
168
170
|
<Description xml:space="preserve">${xmlEscape(manifest.description)}</Description>
|
|
171
|
+
<Icon>extension/${xmlEscape(manifest.icon)}</Icon>
|
|
169
172
|
<Tags>${xmlEscape(tags)}</Tags>
|
|
170
173
|
<Categories>${xmlEscape(categories)}</Categories>
|
|
171
174
|
<GalleryFlags>Public</GalleryFlags>
|
|
@@ -179,6 +182,7 @@ function vsixManifest(manifest) {
|
|
|
179
182
|
</Installation>
|
|
180
183
|
<Dependencies/>
|
|
181
184
|
<Assets>
|
|
185
|
+
<Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="extension/${xmlEscape(manifest.icon)}" Addressable="true"/>
|
|
182
186
|
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true"/>
|
|
183
187
|
</Assets>
|
|
184
188
|
</PackageManifest>
|
|
@@ -188,6 +192,13 @@ function vsixManifest(manifest) {
|
|
|
188
192
|
function packageVsix() {
|
|
189
193
|
const source = path.join(root, "editors", "vscode");
|
|
190
194
|
const manifest = JSON.parse(fs.readFileSync(path.join(source, "package.json"), "utf8"));
|
|
195
|
+
syncEditorVersion({ check: true });
|
|
196
|
+
if (manifest.icon !== "dist/icon.png") throw new Error("VS Code extension icon must be dist/icon.png");
|
|
197
|
+
const icon = fs.readFileSync(path.join(source, manifest.icon));
|
|
198
|
+
if (icon.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a" || icon.readUInt32BE(16) < 128 || icon.readUInt32BE(20) < 128)
|
|
199
|
+
throw new Error("VS Code extension icon must be PNG, at least 128 × 128.");
|
|
200
|
+
if (!icon.equals(fs.readFileSync(path.join(root, "public/apple-touch-icon.png"))))
|
|
201
|
+
throw new Error("VS Code extension icon must reuse Garden's canonical public/apple-touch-icon.png.");
|
|
191
202
|
const entries = [];
|
|
192
203
|
const license = path.join(root, "LICENSE");
|
|
193
204
|
if (fs.existsSync(license)) entries.push({ name: "extension/LICENSE", data: fs.readFileSync(license) });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const defaultRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
|
|
7
|
+
/** Root Garden version is authoritative. npm version runs this before its release commit. */
|
|
8
|
+
export function syncEditorVersion({ root = defaultRoot, check = false } = {}) {
|
|
9
|
+
const version = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
10
|
+
if (!/^\d+\.\d+\.\d+$/u.test(version)) throw new Error("Garden editor releases require a stable major.minor.patch version.");
|
|
11
|
+
for (const name of ["package.json", "package-lock.json"]) {
|
|
12
|
+
const file = path.join(root, "editors/vscode", name);
|
|
13
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
14
|
+
const versions = name === "package-lock.json" ? [value, value.packages[""]] : [value];
|
|
15
|
+
if (versions.every((item) => item.version === version)) continue;
|
|
16
|
+
if (check) throw new Error(`${file} must match Garden ${version}; run node scripts/sync-editor-version.mjs and commit both editor manifests.`);
|
|
17
|
+
for (const item of versions) item.version = version;
|
|
18
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n");
|
|
19
|
+
}
|
|
20
|
+
return version;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
24
|
+
const version = syncEditorVersion({ check: process.argv.includes("--check") });
|
|
25
|
+
if (process.env.GITHUB_REF_TYPE === "tag" && process.env.GITHUB_REF_NAME !== `v${version}`)
|
|
26
|
+
throw new Error(`Release tag ${process.env.GITHUB_REF_NAME} does not match Garden v${version}`);
|
|
27
|
+
process.stdout.write(`Garden / VS Code extension version: ${version}\n`);
|
|
28
|
+
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: garden-compose
|
|
3
|
-
description: "
|
|
3
|
+
description: "Compile source prose plus a short effect request or reference image into faithful, verifiable Garden Compose layouts; use for creation, local tuning and repair."
|
|
4
4
|
---
|
|
5
5
|
# Garden Compose
|
|
6
|
+
Edit only authorized posts/blocks. Advice-only means no writes. Never automatically commit, publish, install dependencies or modify this skill during article work.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Reader question → constraints → recommended route → source leaves → verification
|
|
9
|
+
1. The author supplies prose plus one desired effect/image and optional don'ts. Separate facts, appearance and edit scope. Preserve wording, order, complete paragraphs and resources; invent no summaries, numbers or causal arrows. Keep ordinary Markdown when sufficient.
|
|
10
|
+
2. Read references/visual-selection.md before selection. Read references/mobile-contract.md first: choose the mobile reading task (scan, sequence, comparison or spatial relation), then internally derive the desktop+narrow geometry card: reader question, shared containers, hanging/body scope, centering target, typography relative to body, scene/internal/background layers, narrow-screen fallback and don'ts. This is AI working context, not author paperwork or runtime attributes. Use documented defaults for harmless ambiguity; ask only when facts, hidden content or reading order face material tradeoffs.
|
|
11
|
+
3. Choose one **recommended** route, not a closed component whitelist: icon-rail; short flow-row or long flow-stack; equal-cards; centered-chip; prose-list. frameless-scene is an orthogonal option, as are tone and size. Demand-load the matching references/layout-recipes.md section; unfamiliar layouts/images require references/rendering-model.md. New combinations use references/composition-patterns.md while retaining the same constraints.
|
|
12
|
+
4. Use one garden compose fence. Compare requires real shared comparison dimensions, not unrelated cards. Ordinary icon-led editorial paragraphs default to generic icon-rail; native icon-list is an exception for required semantic/host behavior with its actual styling limitations. Never recommend the removed garden list fence. Read references/static-play.md only for needed semantic/state capabilities; query the actual installed schema for props, children and icons. Markdown/safe HTML islands preserve full blocks; restricted expressions are not arbitrary JS.
|
|
8
13
|
|
|
9
|
-
##
|
|
10
|
-
1.
|
|
11
|
-
2.
|
|
12
|
-
3.
|
|
13
|
-
4.
|
|
14
|
+
## Write and verify
|
|
15
|
+
1. Read references/workflow.md; scan and preserve target id/line, whole-fence base hash and source. Initial insertion is not apply. An uncompiled intent is not finished output.
|
|
16
|
+
2. Follow centralized references/rendering-model.md pitfalls: icon-rail needs icon plus one shared right group containing heading and every paragraph/list; dividers separate rows. row>stack adds contextual top padding; row title/text flex:1 and center couples axes/text. Static unknown attributes may be ignored. Use group where the recipe requires it; do not assume direction="row" changes a stack.
|
|
17
|
+
3. Body defaults inherit actual dynamic article family/size/line height. Do not shrink to fit. Explicit size needs a meaningful hierarchy or explicit author request. Scene frame, internal border and background are separate controls (references/frames.md); colors only as needed. Editorial prose prefers a frameless outer figure; independent experiments may retain a boundary.
|
|
18
|
+
4. For micro-edits, modify only necessary candidate attributes/leaves, not component families. Stable id when available, otherwise freshly scanned line; always original complete-fence base and candidate file. Apply still replaces a whole fence: no attribute-patch or dry-run API. On conflicts rescan, never bypass protection or overwrite neighbors. Distinguish source bold markup from title's default weight.
|
|
19
|
+
5. Run unchanged scripts/check.mjs plus garden check. Optional scripts/lint-layout.mjs uses a scratch constraint JSON and authoritative installed parser; workflow documents its limited structure coverage and exit codes. Risks are not global syntax errors. It cannot recover ignored attributes or prove fidelity/geometry.
|
|
20
|
+
6. Every new layout owns mobile usability without asking the author for device tuning. Validate the narrowest case and enlarged body type first, then desktop under complete theme/CSS order; inspect reading density as well as geometry/computed styles and any actual interaction. centered-chip remains a browser-pending candidate; flow must not leave right arrows in a vertical fallback. Report accepted structure, render, browser geometry/interaction and independent clean-context model reliability separately. Missing evidence means unverified.
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
1. Read references/workflow.md from the site root. Run `node .agents/skills/garden-compose/scripts/scan.mjs content/posts/post.md`; retain source, diagnostics, target id/line and original whole-fence hash. Do not redraw correct blocks.
|
|
17
|
-
2. `npx --no-install garden ui schema` is the demand-loaded source of actual fields, props/defaults, child contracts and icon names. Use `garden ui prompt` for working recipes. Save JSON under `.garden` and inspect only the needed components/icons. Some unsupported static layout attributes are ignored: inspect normalized output and actual styles, not acceptance alone.
|
|
18
|
-
3. If no fence exists, insert one Compose intent block only at the authorized location, preserving prose and appearance requirements, then scan. Insertion is not apply; uncompiled intent is not a finished scene. Every candidate remains a complete `garden compose` fence, preserving original intent without rendering prose twice.
|
|
19
|
-
4. Inline leaves accept Markdown and safe inline HTML. Use markdown/html islands for full article blocks. Allowlisted article HTML and local CSS are useful, but scripts/events/unsafe URLs are errors. Intentional executable demos use sandbox within Compose. Read `references/static-play.md` for literal-island versus restricted DIL expression boundaries; advertise neither unrestricted HTML/JS nor zero Markdown/HTML support.
|
|
20
|
-
5. Body defaults inherit the **actual dynamic article** font family/size; do not invent a universal pixel baseline or shrink-to-fit. Use supported explicit variation for meaningful emphasis. Semantic values, graph labels, captions and controls retain their hierarchy; consult rendering-model for contextual source/computed sizes. Load colors/frames/examples only as needed.
|
|
21
|
-
6. Store the complete candidate in temporary Markdown, then use scripts/apply.mjs: --id or scanned --at, always original whole-fence --base and candidate --file. Rescan if content changed; never remove conflict protection or target a null intent hash.
|
|
22
|
-
7. Run scripts/check.mjs and garden check for real file paths. Match visuals using complete theme/CSS order, desktop+narrow screenshots, geometry and actual interactions. Separately report accepted markup, successful render, correct geometry/interactions and independently tested model reliability. Hand-authored passing DIL proves capability, not prompt reliability; disclose missing browser/model trials.
|
|
23
|
-
|
|
24
|
-
Scripts only delegate to the installed CLI; no parser duplication, package downloads, default model calls or clipboard writes. Report missing local dependencies rather than installing another version. Propose reusable skill improvements for normal review; do not modify skills while processing an article.
|
|
22
|
+
Scripts use only the installed local engine, no parser copies, downloads, default model calls or catalog dumps. Report missing dependencies. Shared references are demand-loaded; propose reusable maintenance separately from article editing.
|