@lark-apaas/coding-steering 0.1.32-dev.05acdd1 → 0.1.32-dev.0f5e9dd

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 (35) hide show
  1. package/package.json +1 -1
  2. package/steering/design-html/skills/charts/SKILL.md +11 -4
  3. package/steering/design-html/skills/data-viz/SKILL.md +1 -1
  4. package/steering/design-html/skills/hi-fi-design/SKILL.md +1 -1
  5. package/steering/design-html/skills/interactive-prototype/SKILL.md +1 -17
  6. package/steering/design-html/skills/slide-deck/SKILL.md +5 -3
  7. package/steering/design-html/skills/social-card/SKILL.md +263 -0
  8. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +3 -0
  9. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +1 -0
  10. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +2 -0
  11. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +6 -0
  12. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +56 -14
  13. package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +67 -4
  14. package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +41 -52
  15. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +75 -0
  16. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +10 -4
  17. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +57 -14
  18. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/SKILL.md +29 -28
  19. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/eslint.mcp-ui.config.cjs +19 -0
  20. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/ui-tsconfig.json +19 -0
  21. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/client-onboarding.md +90 -0
  22. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/debugging.md +11 -11
  23. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/mcp-apps.md +24 -32
  24. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/tool-authoring.md +59 -17
  25. package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +3 -1
  26. package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
  27. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +6 -0
  28. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +49 -22
  29. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +7 -3
  30. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +57 -14
  31. package/steering/vite-react/skills/plugin-guide/SKILL.md +1 -0
  32. package/steering/vite-react/skills/react-three-fiber/SKILL.md +1 -0
  33. package/steering/vite-react/tech.md +3 -0
  34. package/steering/design-html/skills/preflight/SKILL.md +0 -51
  35. package/steering/design-html/skills/preflight/scripts/probe.sh +0 -108
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.32-dev.05acdd1",
3
+ "version": "0.1.32-dev.0f5e9dd",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
@@ -145,13 +145,20 @@ metadata:
145
145
  ```jsx
146
146
  function EChart({ option, style }) {
147
147
  const ref = React.useRef(null);
148
+ const chartRef = React.useRef(null);
148
149
  React.useEffect(() => {
149
150
  const el = ref.current;
150
151
  const chart = echarts.init(el);
151
- chart.setOption(option);
152
- const ro = new ResizeObserver(() => chart.resize());
153
- ro.observe(el);
154
- return () => { ro.disconnect(); chart.dispose(); };
152
+ chartRef.current = chart;
153
+ const resizeObserver = new ResizeObserver(() => chart.resize());
154
+ resizeObserver.observe(el);
155
+ return () => {
156
+ resizeObserver.disconnect();
157
+ chart.dispose();
158
+ };
159
+ }, []);
160
+ React.useEffect(() => {
161
+ chartRef.current.setOption(option);
155
162
  }, [option]);
156
163
  return <div ref={ref} style={{ width: '100%', minHeight: 300, ...style }} />;
157
164
  }
@@ -139,7 +139,7 @@ metadata:
139
139
 
140
140
  ### 6. 自检
141
141
 
142
- 读一遍自己写出的代码(源码级自查),逐项验证以下几点:
142
+ 读一遍自己写出的代码,逐项验证以下几点(源码级自查,不用打开浏览器截图):
143
143
 
144
144
  - 报表是否回答了步骤 1 确定的核心问题。
145
145
  - 信息层级是否清晰(读者能在 5 秒内抓到主要结论)。
@@ -11,7 +11,7 @@ metadata:
11
11
 
12
12
  创建高保真、精细打磨的设计。
13
13
 
14
- 遵循以下通用设计流程(用 todo list 记住):
14
+ 遵循以下通用设计流程:
15
15
  1. 澄清关键信息:能从需求、附件、截图或常见模式合理推断的,直接继续;只在关键信息缺失且会影响设计方向时才向用户提问
16
16
  2. 查找现有 UI kit 并收集设计上下文——复制所有相关组件,阅读所有相关示例;如果找不到且会影响核心设计方向,再向用户询问
17
17
  3. 在文件开头写下假设、上下文和设计推理(把自己当作初级设计师,用户是你的主管),放好设计占位,并尽早展示给用户
@@ -24,20 +24,4 @@ metadata:
24
24
 
25
25
  **面向桌面的场景**(管理后台、内部工具、数据密集型仪表盘)——不需要重排为移动端布局,但必须设 `min-width`(通常 1024px–1200px),窄于此宽度时整体水平滚动,而不是让布局被挤压变形。
26
26
 
27
- brief 未指明时默认按终端用户产品处理。
28
-
29
- ## 宣告可升级为全栈应用
30
-
31
- 宿主为交互原型提供「升级为全栈应用」入口,把纯前端原型转成带服务端的真实应用。入口是否出现,取决于原型有没有向父窗口宣告:
32
-
33
- ```js
34
- function announceUpgrade() {
35
- window.parent.postMessage({ type: 'miaoda:upgrade:available', kind: 'interactive-prototype' }, '*');
36
- }
37
- announceUpgrade();
38
- // 宿主在 iframe 'load' 时重置能力声明,脚本早于 load 执行时补一次
39
- if (document.readyState !== 'complete') window.addEventListener('load', announceUpgrade, { once: true });
40
- ```
41
-
42
- - 重复宣告无副作用;宁可多发,也不要因时序错过让入口不出现。
43
- - 只宣告,不实现:原型侧不写升级逻辑,转全栈由宿主发起。
27
+ brief 未指明时默认按终端用户产品处理。
@@ -45,6 +45,8 @@ metadata:
45
45
 
46
46
  deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**在幻灯片 `<section>` 元素上自行设置 position/inset/width/height。
47
47
 
48
+ 组件撑满的只是 `<section>` 本身,section 内的容器高度仍由内容撑开;高度由内容撑开的容器上,`justify-content` 等垂直分布声明静默无效。要垂直居中或上下分布,先给承载声明的容器高度(如 `height: 100%`)。
49
+
48
50
  ### 使用 PPTX 风格包
49
51
 
50
52
  如果前一步产出了 `pptx-style-extract` 风格包,必须把它当作本 deck 的设计系统:
@@ -104,7 +106,7 @@ deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**
104
106
 
105
107
  - **动效。** 动效服务于叙事——引导视线、分层揭示信息、平滑衔接页面——而不是炫技或填空。默认克制,始终以不干扰阅读为底线。deck 动效的形态是**翻到该页时播放一次的入场 / 分步揭示**,不做环境循环——无限循环的装饰动画会持续争夺注意力。实现用 CSS 动画(幻灯片保持可直编的静态 HTML),两条契约(细节见 deck-stage.js 头部 Authoring guidance):
106
108
  - 动画门控在 `[data-deck-active]` 与 `prefers-reduced-motion: no-preference` 上——组件在激活页维护该属性,翻页即触发;需要 JS 编排时监听组件的 `slidechange` 事件。
107
- - 基础样式写**可见的最终态**,隐藏态只进 `@keyframes` 的 `from`——缩略图栏、reduced-motion 等场景只渲染静态基础态、从不播动画,把 `opacity: 0` 写在基础规则上,会导致这些场景全成空白。
109
+ - 基础样式写的就是**动画结束后的最终画面**,`@keyframes` 只描述进入它的路径——判据:删掉所有 `animation` 声明,静止画面应一个像素不变(缩略图栏、print、reduced-motion 渲染的正是这个无动画基态)。
108
110
 
109
111
  - **层次靠版式,不靠特效。** 页内层级由字号、字重、色块、边框、分隔线和留白建立;内容卡片和区块默认平面化——不加 box-shadow、发光、玻璃拟态(backdrop-filter + 半透明底),渐变默认只用于图上文字的保护渐变(见「图片呈现」)和数据可视化的连续色带。深色底 + 紫蓝渐变 + 发光卡片的「科技感」组合是模型默认值而非设计选择(frontend-design 校准清单第 4 种长相),除非品牌 / brief 明确要求,不要用它。
110
112
 
@@ -135,7 +137,7 @@ deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**
135
137
 
136
138
  ## 规划步骤
137
139
 
138
- 在常规规划之外,务必完成以下步骤:
140
+ 务必完成以下步骤:
139
141
 
140
142
  1. 如果不清楚受众、期望的品牌风格,先提问。
141
143
  2. 写出完整的标题序列。选择**一种**语法风格(例如短主题名词短语或简短陈述句),确保适合内容,并用该风格写出每一个标题。回头通读一遍,判断一个人**仅凭标题**能否跟上整个演示的脉络。标题应像书的章节——用直白的语言告诉读者接下来是什么。审阅这些标题并按需修订。将它们写入 scratchpad.md 文件。
@@ -162,4 +164,4 @@ deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**
162
164
  - 没有被动空洞:边框 / 底色圈出的范围与其内容相称
163
165
  - 页面视觉重量在画布上分布均衡,没有大片区域读成「缺了东西」
164
166
  - 每页能指出视觉锚点;版式原型的重复经得起「内容使然还是没做选择」的追问
165
- - 带动效的元素在缩略图栏和打印视图下完整可见(基础样式即最终态,隐藏态只在 keyframes 的 `from` 里)
167
+ - 带动效的元素在缩略图栏和打印视图下与激活页一致(基础样式即动画终态——删掉 `animation` 声明,静止画面不变)
@@ -0,0 +1,263 @@
1
+ ---
2
+ name: social-card
3
+ description: 制作小红书 / 公众号 / 社交平台的静态图文卡片 —— 固定画幅(比例任意,竖版横版都行)、一套多张、供直接发布的图片组。触发词:小红书, 公众号配图, 图文卡片, 知识卡片, 数据卡片, 数据图卡, 笔记配图, 横版卡片, 首图, 封面, 干货图, 卡片图, RED, 小红书笔记
4
+ metadata:
5
+ display-names:
6
+ zh-CN: 图文卡片
7
+ en-US: Social Card
8
+ ---
9
+
10
+ # 图文卡片(social card)
11
+
12
+ 把内容做成一组固定画幅的静态卡片,用户导出后直接发到小红书、公众号这类图文渠道。
13
+
14
+ 进入这个角色:你在为**无人讲解**的场景做图。读者在双列信息流里滑动,用一秒多决定要不要点开;点开后可能只看前两张就划走。deck 有讲者兜底,卡片没有——每张卡必须自己把话说完。
15
+
16
+ **先判断内容类型,再定语气和密度。** 常见的几类:
17
+
18
+ - **知识切片**——一篇文章、一段口播稿、一份文档,切成封面 + N 张内容卡。
19
+ - **单张封面**——只要一张点击率高的首图,正文走笔记正文区。
20
+ - **推荐与对比**——选购思路、清单、参数对照这类,不依赖某个具体商品的实物外观。
21
+
22
+ **不伪造证据。** 插画、场景、概念图都可以生成,但"体验证据"不行:商品的上身效果、质地细节、使用前后对比,读者会当实拍看;AI 人像做穿搭美妆分享,读者会当真人推荐——这类图必须来自用户提供的实拍。用户是品牌方给自家产品做宣传视觉,属于正常设计需求,照做。测评、种草口吻又拿不出实拍时,请用户补图,或把角度改成不依赖实拍("这支口红显白吗"改成"挑显白色号看哪三点")。
23
+
24
+ ## 先记住这四条(细则在后文;感觉冲突时以这四条为准)
25
+
26
+ 1. **字是配额,图是主角。** 每张卡的全部文字(标题、结构件都算)不超过 120 字,超了移进发布配文。内容里的对比、流程、结构、程度关系画成图形,不排成文字列表——一套卡至少三分之一的内容卡要有图形语言。
27
+ 2. **底色默认浅色,色板三件套封顶。** 一底一墨一主彩,主彩从题材找锚。深色只有两条准入:用户给的素材主图是深色,或用户要求 / 账号风格是深的。题材和作品氛围都不算。
28
+ 3. **手机上读得清是底线。** 每张卡缩到 360px 宽通读一遍,读不清就放大字号或把内容移走。
29
+ 4. **每张卡视线要有唯一落点。** 内容顺着排出主次节奏,别让内容缩在一角,也别让四五个块抢一个画面。
30
+
31
+ ## 动手前先定视觉方向
32
+
33
+ **默认调 `frontend-design` skill 立方向,不要先去问用户喜欢什么风格。** 主题、内容类型和受众通常已经够推出一个有把握的方向——讲 API 定价的干货卡和讲通勤心态的观点卡本来就不该长一样。推断出的方向要说出来,并给一句「内容气质 → 风格选择」的理由——配色深浅、字体气质、密度都从这里推,风格跟内容走,不跟题材标签走。
34
+
35
+ 只有两种情况用提问工具:
36
+
37
+ - 主题、素材、受众都判断不出,纯从零起。
38
+ - 用户要延续账号已有风格。这时要问的不是"你喜欢什么风格",而是**要两张现有卡片的截图**,从中提取配色和字体,让新卡片长在同一套系统里。
39
+
40
+ 需求边界该问还是要问,这跟视觉方向是两回事:内容体量不明确时确认是要一套(几张)还是只要封面一张。
41
+
42
+ ## 构建准备与技术契约
43
+
44
+ ### card-stage 组件
45
+
46
+ **绝不**手写画幅 / 缩放 / 导出的脚手架——先调用 `copy_starter_component` 并传入 `kind: "card-stage.js"`(传 kind 时要带上确切的扩展名),该工具会返回组件的使用说明。然后把卡片写成:
47
+
48
+ ```html
49
+ <script src="card-stage.js"></script>
50
+ <card-stage ratio="3:4">
51
+ <section data-label="封面">…</section>
52
+ <section data-label="规格">…</section>
53
+ </card-stage>
54
+ ```
55
+
56
+ 画布、浏览、出图都由组件负责(细节见它返回的使用说明),你只管写卡片内容。写的时候记住这几条:
57
+
58
+ - **画幅**:`ratio` 收任意 `"宽:高"`,短边恒 1080(与上传规格一致),长边按比例算——`"11:12"` 这类照写就行。常用预设:`"3:4"` → 1080×1440(默认);`"9:16"` → 1080×1920;`"1:1"` → 1080×1080;`"4:5"` → 1080×1350;`"16:9"` → 1920×1080。长边上限 4096(约到 1:3.7),超了组件报错回落 3:4。
59
+ - **`<section>` 就是卡面,里外都不再套容器。** 外面包一层,`card-stage > section` 直接子选择器失配;里面套一层整卡 div,`flex: 1` 和 `margin-top: auto` 失去参照——两种症状都是内容只占上半画布。背景、圆角、内边距直接写在 section 上,眉标、身、页脚是 section 的直接子元素。
60
+ - **不要自己加下载 / 导出按钮**,也不要手搓缩放翻页的脚手架——出图走产品侧链路,交互组件已经有了。
61
+ - **自查加 `?guides=1`** 看安全区参考线,默认不出现,用户看到的就是成品。
62
+ - **页脚、来源这类底部结构件不要绝对定位。** 用 flex 列的末尾元素 + `margin-top: auto` 收底。绝对定位钉死的页脚在内容变长时会被穿透。
63
+ - **上下窄边 120px 左右。** section 的上下 padding 用 `--edge`(120px 左右);小红书详情页会在卡面右上角叠自己的页码角标,眉标别顶进顶部 100px 内。12.5% 的安全距离由眉标行加首块间距自然满足,不需要另设整段的安全内边距。
64
+ - **短件不折行,短段配平**:标签、数值、页码这类短件加 `white-space: nowrap`;标题和两行以内的句子加 `text-wrap: balance`,行尾不留孤字。
65
+ - **产物形态声明**:`<head>` 里写 `<meta name="creative-medium" content="social-card">`。
66
+
67
+ ### 把卡片写成静态 HTML,不要用 React
68
+
69
+ 每段文字住在自己的叶子元素里,重复结构逐条写出来而不是循环渲染——内容一旦由脚本生成,用户改一个字都要绕回来找你。
70
+
71
+ ## 画幅与安全区
72
+
73
+ ### 画幅怎么定
74
+
75
+ **按用户意图定。** 三种情况:
76
+
77
+ 1. **什么都没说** → 3:4(1080×1440),小红书图文的默认,信息流里展示面积最大。
78
+ 2. **给了具体数字或比例**("1080×1920"、"做成 9:16"、"11:12")→ 直接用,不质疑,也不要往预设上凑。只有长边超上限的(定宽自由高的长图这类)才直说组件做不了,问最接近的画幅能不能接受——不要静默换画幅,更不要改用别的 starter 组件承载。
79
+ 3. **只说了平台** → 按平台推演取推荐值,拿不准就问一句而不是猜:
80
+
81
+ | 平台与位置 | 画幅 |
82
+ | --- | --- |
83
+ | 小红书图文(默认) | 3:4 · 1080×1440 |
84
+ | 小红书 · 产品图 / 单点结论 | 1:1 · 1080×1080 |
85
+ | 视频号 / 抖音封面 | 9:16 · 1080×1920 |
86
+ | Instagram 帖子 | 4:5 · 1080×1350 |
87
+ | 公众号文章配图 | 16:9 · 1920×1080 |
88
+
89
+ **一篇笔记里所有卡片必须同比例。** 首图的比例决定整篇的展示比例,混用会让后面的图被裁。选定 `ratio` 后不要中途换。
90
+
91
+ 用户指定 9:16 做小红书首图时,提醒他在发布页的裁切预览里确认一眼缩略图的裁切位。
92
+
93
+ 安全区取上下各 12.5%,剩下的中央区是核心区。规则是:标题、人脸、产品主体、任何"漏看就不懂"的信息,必须落在中央核心区内;页码、眉标、来源这类丢了也不影响理解的结构件才可以进上下两条带。
94
+
95
+ ## 封面
96
+
97
+ 封面和内页是两套规则,不要用同一套版式糊过去。
98
+
99
+ - **三秒判据**:把封面缩到信息流里的实际尺寸(约 200px 宽)看一眼,读不出"这在讲什么"就重做。缩小后第一个还认得出的元素,就是这张封面的锚点。
100
+ - **主标题给到画幅的绝对统治地位**。封面主标题的字号档位比内页标题高一档,占据视觉重心(画面中上部)。
101
+ - **钩子要能兑现。** 悬念式、数字式(「99% 的人都不知道」)在小红书是有效套路,不禁用——但标题许诺什么,内容就得给什么:说是多数人不知道的,后面就不能是常识。能给具体的优先给具体:「缓存命中差 120 倍」自带信息。
102
+ - **钩子的强度跟账号定位走。** 研究、财经、专业向的账号用悬念式会掉可信度(量子位不会写「99% 的人都不知道」);泛知识、生活向的账号用它是常规打法。拿不准就看用户给的已有笔记怎么写标题。
103
+ - **篇幅承诺按需给。** 清单、分析、教程这类要读者投入时间的内容,封面要让他知道有多少、什么结构——目录条、「20本 · 4个方向」、编号阵列都行;观点、情绪、故事类不给,预告结构会破坏节奏。
104
+ - **副标题必须增加信息**,不复述标题。限定、转折、范围、方法都行——标题给事实,副标题给条件(「收入增速虽然放缓,但盈利能力仍未见顶」)。
105
+ - **有真实素材就让素材当主视觉。** 用户给的截图、产品图排成阵列本身就是内容,比生成图可信,也一眼传达"这篇有干货"。
106
+ - 封面可以在版式上区别于内页;底色深浅跟全套的规律走,首尾两张毫无来由地突然转深是最常见的翻车位。字体、强调色、结构件位置必须同一套。
107
+
108
+ ## 切片
109
+
110
+ ### 内容预算
111
+
112
+ **卡片是大纲,不是容器。** 卡片承载一两秒能扫到的东西——结论、数字、对照、清单;原理解释、背景细节、长论证住在发布配文里。内容多出来的第一反应永远是分流,不是把卡排密。
113
+
114
+ **顺序是画幅 → 每张预算 → 张数,中间不能跳。** 画幅由用户意图定(见上),每张的预算由画幅算出来,张数由内容总量除以预算得到——**向上取整,再凑到 3 的倍数**(默认下限见「怎么切 › 张数」)。张数是唯一的自由度,不要反过来先拍张数再往里塞,也不要因为内容装不下就去改画幅。
115
+
116
+ 可用内容区 = 画幅高减去上下各 12.5% 的安全区。「卡片设计与构图」那套 token 下,3:4 的可用区 1080px 高,折算下来:带一个标题块时约 15 行正文;换成大数字块,**三块就顶满,再加一段结论必超**。
117
+
118
+ 其他画幅按可用区高度换算(3:4 为基准,向上取整):**倍数 = 画幅高 ÷ 1440**。9:16 是 1.33 倍——7 张 3:4 的量取 6 张不是 5 张;4:5 是 0.94 倍,照 3:4 排通常成立;1:1 和 16:9 都只有 0.75 倍,一张放一个判断就满。**宽大于高的画幅再加一条**:内容必须左右分区或分两栏,满宽单栏的行长读不下去。
119
+
120
+ **排内容时就按预算裁**,不要写完再看塞不塞得下。装不下的处置顺序:**拆卡 > 删内容 > 把次要信息降级成更省空间的版式 > 缩字号**。降级 = 三个并列大数字里次要的那个改成一行小字——既腾空间又强化主次。缩字号是最后手段,且不越过 26px 下限;压间距同理,组间距不许压到 `--gap-item` 以下——还装不下就回去分流,不靠贴着排硬塞。
121
+
122
+ **并列项列表先算账再排。** 单项高度 × 项数对可用区一比,超了走处置顺序(并列项还可两列分栏);压行距贴边、放任尾项被裁都不算解法。
123
+
124
+ **修溢出前先量大小。** 几十像素收间距就能吸掉;超过一个内容块的量才动处置顺序。修完回头看一眼,别把溢出修成大片空白。
125
+
126
+ ### 怎么切
127
+
128
+ - **每张卡承载一个完整判断**。判据:单独截这一张发出去,读者不需要前后文也能读懂。做不到就说明切错了。
129
+ - **结论必须写在画面上。** 没有讲者当场解释,需要读者自己"得出"的判断都要落成文字。载体自选:标题本身或底部一句收尾。别为形式统一给每张套结论框,也别把结论留给读者猜。
130
+ - **每张卡的文字量在写文案终稿时就锁死**(终稿在规划第 4 步写,见「规划步骤」)。配额:一张卡 = 1 个标题 + 至多 3 个内容块,每块至多两行短句;全卡的字加起来(标题、眉标、页脚都算)不超过 120 字。装不下的内容移进发布配文。
131
+ - **两种情况可以超**:一是对比表、参数表、清单标签这种一格几个字的形态——但格子里得是短词,不能是成段句子;二是用户明确要干货密度。
132
+ - **并列项超过 4 个先分主次**:挑共鸣最强的两个展开成单卡,其余合并成一张组合卡。七个要点平铺七张是流水账,主次本身就是内容判断。
133
+ - 不出现"接上页""见下页""如前所述"。
134
+ - **段落不中断**,标题必须和它领起的内容在同一张,列表项不落单(三条里跨两张切成 2+1 可以,1+2 不行——单条起头读起来像残页)。
135
+ - **张数**:用户给了数字照用户的——材料偏薄也照做,用图形化和留白把每张撑住,不擅自减张。没给时**默认一套 9 张起步,总张数取 3 的倍数(9 / 12 / 15,上限 18)**——小红书图片位三张一行,凑整行不缺角。按预算算出来不足 9 张时,先把要点拆细、提高图形化占比;**拆完有卡只剩一句话的,说明材料撑不起 9 张,直接做 6 张**(同样凑 3 的倍数),交付时说明一句。超过 18 张拆成两篇。
136
+
137
+ ### 标题序列
138
+
139
+ - **标题语法按内容类型选,选定后贯穿全套**:分析判断类用结论式(读完标题就得到信息,如「Q2 营收环比只增了 7.8%」),清单教程类用主题式(当导航用,如「打破历史滤镜」),观点类用陈述式。不要中途换。
140
+ - 切完通读一遍**只看标题**的序列,能不能跟上完整脉络。跟不上就回去改标题,不要靠正文补。
141
+ - **清单类内容让顺序承担牵引**:按重要性递减、使用顺序或由浅入深排,选一种排法贯穿,别随机排——任意两张换位置都不影响理解,说明顺序没干活。
142
+
143
+ ## 卡片设计与构图
144
+
145
+ frontend-design 的通用规则与本 skill 的卡片规则冲突时,以本 skill 为准。它的四个槽位(世界 / 材质 / 配色 / 签名)照用,但配色槽按下面的「色板三件套」收敛——它要的 4~6 色是网页口径,卡片装不下。
146
+
147
+ ### token 体系
148
+
149
+ 写任何卡片**之前**,先在 `<head>` 的 `<style>` 里把字号、行高、间距定成 CSS 变量。1080×1440 的一套可用起点:
150
+
151
+ ```css
152
+ :root {
153
+ --type-hero: 118px; /* 封面主标题 */
154
+ --type-title: 70px; /* 内页标题 */
155
+ --type-num: 118px; /* 大数字 */
156
+ --type-lead: 44px; /* 导语 / 次级强调 */
157
+ --type-body: 38px; /* 正文 */
158
+ --type-small: 29px; /* 注解 */
159
+ --type-tag: 26px; /* 眉标 / 来源 */
160
+ --lh-tight: 1.12; --lh-body: 1.55;
161
+ --pad-x: 92px;
162
+ --edge: 120px; /* 上下窄边:眉标、收尾离边的距离 */
163
+ --gap-title: 48px; --gap-item: 30px;
164
+ }
165
+ ```
166
+
167
+ 所有 font-size 走 `--type-*`,所有间距走 `--pad-*` / `--gap-*`,用户改一个数字就能重调整套。**正文不低于 32px,任何文字不低于 26px**——这是下限不是目标。**最小号的注解灰字每张最多用 2 处**——注解多了,说明该进正文或该删。9:16 画幅纵向多出 480px,字号体系不变,多出来的空间给内容或留白,不要整体放大。
168
+
169
+ **字重是显性决策,默认越大越轻。** 最大档的字(hero、大数字)用 300~400 的细字重,眉标、标签这类小字反而加重到 500~600 并放宽字距——大而细是冷静的工程感,大而粗是海报式的击打感——后者要是视觉方向里的显式选择,不能是每档顺手加粗的结果。标题档在两端里选(细衬线 / 细黑,或 800 以上的超黑),600~700 的中间档最没姿态。同一档位的字重全套一致。
170
+
171
+ ### 构图
172
+
173
+ - **视觉锚点**:每张卡要能回答"视线第一眼落在哪里"。写不出锚点的卡,是内容撑不起一张的信号——回去合并或换形式。
174
+ - **眉标和页码钉在页缘**:离上边一个窄边的小字行,全套节奏一致。口径、出处、肩标这类跟着内容的小字,紧挨它标注的那块;哪都不挨、悬在半空的小字是排版事故。
175
+ - **卡片的下边要有交代**:一句收尾结论、来源、用户给的账号名都行;满版图、居中金句这类版式自己压住底边的,不用再添。这三样都没有就空着,别拿风格名、栏目名凑一行小字——一行小字压不住底边,收住靠的是内容本身排到底。内容停在高处、下方大片空白,就是没收住,回「内容预算」的处置顺序。
176
+ - **内容卡的身子从眉标下方顺着往下排**,块间距是内容的节奏,不是填空的手段;排完剩下的空白自然落在收尾之前,剩多剩少都成立。这段空大过一个安全区(画幅高 12.5%)就是内容太薄——回「内容预算」的处置顺序。满版图、居中金句这类整版版式不按这条排。
177
+ - **眉标、页码用等宽小字写**(品牌名、栏目、页码、日期),像杂志的编目信息,档案感从这里来。**页码并进顶部眉标行**(「02 / 主题词」这种写法),不在底部单独占位。**标题序号不是页码**:它与标题同档,或放大一档做视觉锚点(可压浅、用主彩),别缩成小字嵌在标题旁。**编号只留一套**:内容卡带标题序号(01、02…)时,眉标只放品牌和栏目、底部不再放页码;用眉标页码(02 / 09)时标题不编号。
178
+ - **层次靠版式**:字号、字重、色块、分隔线、留白。不加 box-shadow、发光、玻璃拟态,不给卡边贴通高、通宽的装饰条;渐变只用于图上文字的保护渐变和数据色带。深色底 + 紫蓝渐变 + 发光卡片是模型默认值,不是设计选择。
179
+ - **底色默认浅色,气质默认明亮。** 小红书吃治愈、专业、审美好,不吃暗黑氛围;浅底在信息流缩略图里还读得出标题,深底缩小后只剩一个黑块。深色仅两条准入:用户给的素材主图本身是深色,或用户要求 / 账号已有风格是深的——题材和作品氛围都不构成理由——讲电影就取片子里明亮的记忆点配色,不取暗夜场景。**自己生成的图不算素材准入**:先定色板,生成图跟着色板走,不是色板跟着图走。真用深色时选带情绪的彩色深底(酒红、深紫、橄榄绿这类),不用纯黑和黑蓝灰。选深色要在方向声明里写明走的哪条准入。
180
+ - **色板三件套封顶。** 一个底色、一个墨色、一个主彩;主彩可以带一个降饱和的浅版铺大面积(同色两档,不算开新色),再多一个彩色就必须说得出分工。主彩从题材里找锚——品牌色、作品标志色(角色的标志服装、标志场景的颜色)、行业惯例色都算;有锚就用锚,不自造"更有科技感"的替代色,哪怕锚色看起来像常见搭配。锚定的是色相和识别性,不是原始色值——品牌色过亮过艳时压一档明度饱和度到印刷感(#4D6BFE 这类亮蓝压到 #2A6FDB 档),色相不动。无锚时从题材情绪推一个有倾向的当代色。底色用白到冷白——判据:**底色的蓝通道不低于红通道**(#F5F7FA 这类成立,#F5EFE3 这类偏黄的不成立);发黄的纸色只在用户点名复古手作时用。题材的世界观进主彩和签名元素,不进底色。浅底上高饱和色只做小面积点睛(关键数字、下划线、小色块),大面积图形用降饱和版或墨色。好坏、推荐 / 避雷的两栏对照不开红绿双彩——主彩标正面,中性灰标负面。深浅可以穿插(比如章节卡深、内容卡浅),但要成规律、贯穿全套;只禁一种:整套浅色排到结尾,突然冒出一张深色收尾。
181
+ - **装饰件不做默认手段。** 印章、贴纸、手绘批注、胶带这类拟物装饰,只在用户文风明确要手账感时用,且全套最多一种、每张最多一处——层次靠版式,不靠贴东西。
182
+ - **彩色 emoji 不当图标用。** 列表符、判定符、图标位用可调色的字符(✓ ✕ →)或手写 SVG——彩色 emoji(✅ ❌ 💡)没法被 CSS 调色,永远和色板打架。文本里承载语气的 emoji 跟文风走(发布配文另算,见「发布配文」):用户文风里有就用,默认不用。
183
+ - **形状词要画成形状。** 内容里出现「阶梯」「金字塔」「漏斗」「闭环」「象限」这类自带形状的词,版式就做成那个形状——三级阶梯就是三层递进的台阶,不许排成普通列表。
184
+ - **可视化的对象是关系,不只是数字。** 对比(以前 vs 现在)、流程(第一步到第四步)、结构(几个部分怎么组成)、程度(多与少)都画得出来:双列对照、步骤链、结构图、程度条都是手写 SVG / 纯 CSS 就能做的。什么算图形语言,判据是有画出来的视觉元素在承载关系——连线、色条、色块、程度条都算,形态不限;**纯文字分两栏、加个箭头符号不算**。内容里有这类关系却排成了纯文字列表,就是把图欠着了。
185
+ - **图表**优先写成静态 SVG 或纯 CSS(条形用 `width`,趋势用内联 `<svg>`),直接在数据点上标数值,去掉网格线和图例。数字之间存在能被眼睛读出的关系时就转成图,不要原样铺成文字。
186
+ - **一行数据是一个整体。** 标签、数值、图形条对齐在同一行,数值紧贴它描述的图形——拆到两个角落或换行竖排,读者就得自己跨行配对。数字旁是多行文字块时,块与数字垂直居中;只有单行标签才对基线。
187
+ - **图形读不出差异就删掉图形。** 条形同一起点、长度差一眼可辨;几条看不出谁长谁短时比较关系已丢,退回大数字直接给数。
188
+
189
+ ### 配图
190
+
191
+ 图从哪来、该不该用图走通用规则。卡片这边补四条:
192
+
193
+ - **生不生图看题材,两边各有各的默认。** 题材自带视觉世界或具体形象的——影视、动漫、游戏,以及靠人物、地点、物件、步骤演示立住的主题——配图是默认动作,不配等于把最能立住的牌扣着不打;插画是原创的气质致敬,不复刻剧照或版权画面。抽象知识、方法论、观点类题材默认纯版式——这类内容缺的从来不是插画(通用 AI 插画贴在干货卡上是机器痕迹,掉可信度),缺的是把关系画出来的图形和图表(见「构图」),那才是它的默认配图。用户要求配图时直接生,不必走判据。判据仍是形象可信度:靠形象被认出的生成,靠符号约定被认出的手写 SVG,绝不手写具象 SVG。
194
+ - **动手前盘点,批量生成。** 规划时把整套要生图的卡位一次盘完,并行生成、拿到地址再落笔——写到一半才发现缺图的位置,最容易被占位块或几何图形顶掉。
195
+ - **生成图按画幅出比例。** 满版位按画幅比例生,局部图按实际占的框出——靠 aspect-fill 补裁掉的往往正是主体。
196
+ - **图上叠字与全套一致。** 满版图配底部文字时加保护渐变,核心信息仍落在中央核心区。对比度手段(保护渐变、暗化蒙版、文字底块)选一种,全套一致;一套卡片的图片风格必须统一,用户是在九宫格里同时看到它们的。
197
+
198
+ ## 语感
199
+
200
+ **文风先跟用户走。** 用户在 query 里提了语气、举了例子、给了自己的原文或已有笔记,就照着那个写——这跟画幅、配图是同一个规矩,用户说了就听。什么都没提,才走下面的默认。
201
+
202
+ ### 默认文风
203
+
204
+ - **别为形式牺牲内容。** 对仗、倒装、短句本身都不是问题——「需求稳下限,收入结构决定上限」工整,但两半各带一个实质判断;「要学的东西,比想象中少」也工整,却只是把「其实不难学」绕着说。判据只有一条:这个说法比直说多给了什么?答不上来就改直说。
205
+ - **人称跟着内容类型走。** 个人经验、踩坑、教程用第一人称和具体场景;分析、清单、资料整理用中性陈述——研究机构和编辑口吻同样成立,不是非得"我"。别用"用户""大家"这类产品腔指代读者。
206
+ - **结构件承担身份和导航,不放标语。** 眉标写栏目名、编号都成立;账号名用户给了才写——署名是可选项,不编造账号和二维码,不拿风格名、规则词当署名。给单页现编一句标语(`THE REAL UPGRADE`)既不指明归属也不导航,纯装饰。
207
+ - **动词具体。** "拖上轨道、调色、加字幕"比"进行后期处理"有用。
208
+
209
+ ### 避免
210
+
211
+ - **张力要来自内容,不是句式**。内容里真有反差就写出来;没有却靠"不是 X 而是 Y"、强祈使、刻意留白硬造,读者点进来会落空。"不是 X 而是 Y"尤其是模型的句式口癖,用之前先确认 X 和 Y 真构成对立。
212
+ - **空转的强调**:"真的绝了""必看""建议收藏"这类不承载信息的词。
213
+ - **把标题写成金句**。标题的职责是把人领进这张卡的内容,不是替演讲者立 flag。
214
+
215
+ 正面做法:给具体的数、对象、场景——「$0.435 / 百万 token」比「价格低到离谱」有用。
216
+
217
+ **事实不编造。** 数据要能溯源到用户给的材料或可验证的来源;跑分、价格这类容易过期的数字,在最后一张标明来源和口径。
218
+
219
+ ## 发布配文
220
+
221
+ 发布形态是「一组卡 + 标题 + 配文」两个容器,交付卡片时把配文一并给出,不要等用户开口要。
222
+
223
+ **正文和卡片是两套文风。** 卡面的克制规则(禁 emoji、禁感叹式强调)不适用于正文——口语化、短句、emoji、结尾邀互动是这个载体的常规写法。只有「语感」末条(事实不编造、数字可溯源)两边都适用。
224
+
225
+ - **标题**:≤20 字。从封面那组钩子句式里选一条(见「封面」),封面主标题偏克制时另写一条、不要照抄。强度仍跟账号定位走——研究、财经向账号堆感叹号会掉可信度。
226
+ - **正文**:目标 200 字左右,别超 250。结构是「开头一句勾住 → 中间 2~3 个点 → 结尾一句邀互动」:
227
+ - 开头写自己的真实处境或一句反差,不要"今天给大家分享"这类开场。
228
+ - 中间是「内容预算」分流出去的那部分——切片时随手记进 scratchpad,交付时成文。**不复述卡面**:正文给的是卡上放不下的原因、过程和体验。
229
+ - **短句为主,但 2~4 句成一段、段间空行**,不要每句独占一行。要点挑最值得说的 2~3 个,不罗列所有卖点。
230
+ - **优缺点都写**,短板一句带过也算。只夸不提短板读起来就是广告。
231
+ - 结尾一句邀互动:问读者怎么做、请对方评论区补充。
232
+ - **emoji 点缀**,标段落或收尾用,不是每句都挂。
233
+ - **标签直接续在正文末尾**:3~5 个,按品类 / 人群 / 场景各取一到两个(`#社区食堂 #打工人吃饭 #本地生活`),不堆 `#生活` 这类泛词。不单起一行写「标签:」——标签就是正文的一部分,复制时要能一并带走。
234
+
235
+ 只要单张封面的场景本来就是「正文自行撰写」,标题照给,正文不代写除非用户要。
236
+
237
+ **总结里给标题和正文加一句概述就收尾,不要逐张复述卡片结构**——画布上每张卡都带标签,用户看得见。
238
+
239
+ ## 规划步骤
240
+
241
+ 1. 用 `frontend-design` 定视觉方向;主题和受众都判断不出时才问。方向声明里当场把色板三件套写死:底色色值(白到冷白,蓝通道不低于红通道)、墨色、主彩和它的锚——这一步定完,后面生图和构建只准用这套色。
242
+ 2. 定画幅(默认 3:4),按「切片 › 内容预算」算出张数,写进 scratchpad。
243
+ 3. 写出完整的标题序列,按「切片 › 标题序列」的规则选语法、验脉络。
244
+ 4. 给每张卡标注**版式原型**(大字陈述 / 大数字 / 条形图 / 双列对照 / 步骤链 / 时间线 / 并列要点 / 引用 / 纯文字)和**视觉锚点**,并**按选定的原型写出这张卡的文案终稿**——定了大数字卡,文案就是一个数字加半句话;定了对照卡,文案就是两列短语。配额见「切片 › 怎么切」。通读这一列检查节奏——原型的重复要么是内容使然,要么就是没做选择;**整套只有文字类原型同样是没做选择**(纯观点金句类除外)——找出内容里的对比、流程、结构关系,至少三分之一的内容卡要有图形语言。
245
+ 5. 先写 token 块,再写第一张卡。
246
+ 6. 逐张构建,**文案只从第 4 步的终稿里搬,不在 HTML 里新写句子**——发现终稿不合适,回第 4 步改终稿再搬。封面单独按封面规则做。
247
+ 7. 导出前先看审阅视图,逐项过下面的检查清单。
248
+ 8. 交付时按「发布配文」附上标题与正文(标签在正文末尾)。
249
+
250
+ ## 验证要点
251
+
252
+ 把每张卡放到实际尺寸看,别用缩略图判断版面;按画幅边界判构图,不是按内容块的边界判。
253
+ - 没有内容被画幅底边裁掉。大数字 + 结论块的组合最容易超,重点查。
254
+ - 核心信息没有落进上下安全带。
255
+ - 每张卡单独看能成立,没有"接上页"。
256
+ - 封面缩到 200px 宽仍能读出主题。
257
+ - 整张卡缩到约 360px 宽(手机实际显示宽度)通读一遍,正文、注脚全读得清才放行——读不清只有放大或分流两条路,没有「保持原样」。
258
+ - 没有卡片的内容只占上半画布。封面和尾卡重点查——这两张是自由版式:内容压底的块要有 `margin-top: auto`,垂直居中的容器要有 `display: flex`,漏一行整卡就吊在顶上。
259
+ - 字号匹配 token 体系,没有为塞内容缩到 26px 以下;没有内容卡全卡字数过 120 还没分流(短词密集的表格清单类除外)。
260
+ - 结构件在全套卡上位置一致;编号只有一套。
261
+ - 内容区平面化,没有装饰性渐变、发光、玻璃拟态。
262
+ - 标题许诺的东西内容都兑现了;没有硬造的张力和空转的强调词。
263
+ - 容易过期的数字标了来源和口径。
@@ -71,6 +71,7 @@ available-agents:
71
71
  | 实时多人编辑、毫秒级协同 | 弱实时刷新、提交后同步 |
72
72
  | 原生 App、桌面端、浏览器插件 | Web 应用、响应式页面 |
73
73
  | 服务端持久写本地文件 | 平台文件服务、数据库、临时 `/tmp` |
74
+ | 服务启动时下载大文件、全量同步或批量迁移 | 采用异步处理,避免阻塞服务启动 |
74
75
  | 无凭证调用受限第三方系统 | 要求用户提供 API、凭证或授权方式 |
75
76
  | 自建账号体系绕过平台登录 | 使用平台内置身份与权限 |
76
77
  | 多语言 i18n、深浅色主题切换(非平台内置) | 需自行实现并计入工作量;规格中先确认是否必要 |
@@ -82,6 +83,7 @@ available-agents:
82
83
  | 无持久化状态、纯展示或前端工具 | 纯前端 |
83
84
  | 需要数据存储、用户隔离、后端逻辑 | 全栈 |
84
85
  | 涉及文件上传与管理 | 全栈 + 文件服务 |
86
+ | 服务端图像、音视频处理(易引入 native addon) | 先确认能否改用 client 处理、平台服务或纯 JS/WASM |
85
87
  | 以飞书多维表格为数据源 | 插件优先;复杂查询场景评估是否同步至本地库 |
86
88
  | 需要飞书通知、群组、AI 能力 | 插件集成,标记插件实例需求 |
87
89
  | 需要定时或事件驱动的后台处理 | 全栈 + 自动化任务 |
@@ -112,6 +114,7 @@ available-agents:
112
114
  - 集成点:飞书、多维表格、AI、内网接口、OpenAPI、自动化触发。
113
115
  - 边界与限制:不支持的能力、外部依赖、数据权限边界。
114
116
  - 交给 Code agent 时标明需要加载的专项 skill(表格、表单、图表、插件、权限、文件、自动化)。
117
+ - 需要 native addon 时:确认它提供覆盖 linux-x64 / glibc 的预编译二进制并记录包名与版本;没有预编译二进制、装的时候要现场编译的不能用。
115
118
 
116
119
  ## Common Mistakes
117
120
 
@@ -21,6 +21,7 @@ match-template-name: nestjs-react-fullstack
21
21
  ## 使用注意
22
22
  - **入口边界**:本 SDK 是**应用前端代码**读写应用存储的唯一入口。Agent 自己在对话 / 开发中上传、灌数据或调试文件,先加载应用文件存储操作 skill(按「应用文件存储 / 文件上传 / 文件下载」召回),用其 CLI 命令操作,具体命令一律以该 skill 为准——二者操作同一个应用存储桶,但**该 CLI 命令只供 Agent 在沙箱终端使用,禁止写进页面代码**,页面里一律用本 SDK。
23
23
  - @lark-apaas/client-toolkit/dataloom 这个SDK只适用于前端调用,禁止在服务端调用
24
+ - **沙箱 dev 限制**:`uploadFile` 打的 `/app/<appId>/__runtime__/api/v1/storage/object/<bucket>/pre_upload` **在沙箱 dev 下恒 404**,只有发布态可用。这是环境限制不是代码缺陷,按本 skill 写法即为正确,不要为它改代码;开发期要验证上传链路改走接口测试直接打后端接口,或复用库里已有的文件 URL。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
24
25
  - 上传成功后,最重要的返回值是 `data.download_url`。需要将此URL保存到你的业务数据库中
25
26
  - **⚠️ 场景区分(重要)**:`dataloom.storage` 仅适用于需要持久化存储文件或获取 `download_url` 保存到数据库的场景。如果文件仅作为插件输入(传给 `capabilityClient`),**必须直接传 File/Blob 对象,禁止先走 dataloom 上传再传 URL**;插件调用(capability)不属于 dataloom,详见 plugin-guide
26
27
  - **download_url 格式说明**:`download_url` 返回的可能是相对路径(如 `/spark/app/.../storage/object/...`),这是正常行为。**禁止**在前面拼接 `window.location.origin` 或其他域名前缀,平台会自动解析相对路径。直接使用原始值即可。
@@ -14,6 +14,8 @@ match-template-name: nestjs-react-fullstack
14
14
 
15
15
  > **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
16
16
  >
17
+ > **沙箱 dev 限制**:本 skill 的能力底层打 `/app/<appId>/__runtime__/api/v1/account/*`,这类端点**在沙箱 dev 下打不通**(POST 404;GET 落 SPA fallback 返回 200 + index.html),只有发布态可用。`useCurrentUserProfile()` 打的是 `account/login/user`;UserSelect / DepartmentSelect / UserDisplay 打 `search_user` / `list_users` / `user_profile` / `search_department`。看到 404 或选人列表为空时,禁止改 `business-ui` 组件、禁止加兜底请求或换自研控件绕过;开发期要验证「按当前用户过滤 / 展示姓名」改由服务端出数据(`req.userContext` / `AuthNPaasService`)。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
18
+ >
17
19
  > **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
18
20
 
19
21
  ## 怎么选(决策指引)
@@ -111,6 +111,12 @@ Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponse
111
111
  - [ ] 已根据实际定义修正代码
112
112
  - [ ] 已验证修改后类型使用正确
113
113
 
114
+ ## 业务日期错位
115
+
116
+ **问题描述**:本地凌晨、周一或月初附近,日期落到前一天、上周或上月。
117
+
118
+ **解决步骤**:按 `coding-guide` 的「业务日期」一节改——自然日统一走业务时区 helper;精确时刻不动。
119
+
114
120
  ## JSX 和语法错误
115
121
 
116
122
  ### 特殊字符转义处理
@@ -155,8 +155,9 @@ shared/ # 前后端共享的目录
155
155
 
156
156
  ## 依赖使用规范
157
157
 
158
- 1. **子包完整性检查**:部分库有多个子包(如 `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities`),添加 import 后必须确认 package.json 中包含所有需要的子包
159
- 2. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
158
+ 1. **子包完整性检查**:部分库有多个子包(如 `@radix-ui/react-*` 系列每个组件都是独立子包),添加 import 后必须确认 package.json 中包含所有需要的子包
159
+ 2. **禁止安装时需现场编译的 native addon**(`node-gyp rebuild`、`binding.gyp`、需要系统头文件或编译器)。依赖按平台内置能力 / 浏览器能力 → 纯 JS 或 WASM → 有预编译二进制的 native addon 的顺序选型
160
+ 3. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
160
161
 
161
162
  ## 文件命名约定
162
163
 
@@ -179,6 +180,36 @@ shared/ # 前后端共享的目录
179
180
  - `.eslintrc.js`、`.prettierrc`
180
181
  - `node_modules/`、`.git/`、`dist/`、`build/`
181
182
 
183
+ ### 沙箱 dev 不提供平台 runtime 接口(`__runtime__`)
184
+
185
+ dev server 只代理 `/api`、`/openapi`、`/__innerapi__`(外加 legacy 的 `/af/p/:appId/api`、`/af/p/:appId/__innerapi__`)。`/app/<appId>/__runtime__/*` 这一整类**平台 runtime 接口在沙箱 dev 下打不通**,只有发布态才由网关接管。
186
+
187
+ **症状分两种,按请求方法区分**:POST 直接 404;**GET 会落到 Vite 的 SPA fallback,拿回 200 + `index.html`**,业务侧表现为「返回了一坨 HTML,JSON 解析失败」——见下方「问题排查指引」里「API 测试返回 HTML 内容时」那条,两者是同一回事。
188
+
189
+ 已知受影响端点:
190
+
191
+ | 端点 | 方法 | 谁会打到它 |
192
+ |------|------|-----------|
193
+ | `api/v1/permissions/roles` | GET | `AppContainer` **每次加载应用自动打**,不需要你写任何代码 |
194
+ | `api/v1/observability/{logs,traces,metrics}/collect`、`current_server_timestamp` | POST/GET | 平台埋点上报,**自动打** |
195
+ | `api/v1/account/login/user` | POST | `useCurrentUserProfile()` → `authClient.session.getUserInfo()` |
196
+ | `api/v1/account/search_user`、`list_users`、`user_profile`、`search_department`、`convert_lark_user` | POST / `user_profile` 是 GET | `business-ui` 的 UserSelect / DepartmentSelect / UserDisplay |
197
+ | `api/v1/account/search`、`api/v1/account/chat/list_chats` | POST | ChatSelect 选群组件 |
198
+ | `api/v1/studio/user/profile` | GET | `getUserProfile()` |
199
+ | `api/v1/storage/object/<bucket>/pre_upload` | POST | `dataloom.storage.uploadFile()` 前端上传 |
200
+ | `api/v1/studio/plugins/tmp_files/acquire_upload_url`、`acquire_download_url` | POST | `capabilityClient` 的文件类入参 |
201
+
202
+ > `useCurrentUserProfile()` 打的是 `account/login/user`,**不是** `user_profile`/`search_user` 那一排;它另外还打一个 `GET /api/authnpaas/lark-user-id`,那条走 `/api`、被代理、正常。
203
+
204
+ **这是环境限制,不是应用缺陷,也不是组件坏了。** 看到这些 404 / HTML 响应时:
205
+
206
+ - **禁止**改 `client/src/components/business-ui/**`(平台保护文件)、**禁止**给组件加兜底请求或改用自研选人控件绕过——发布后这些端点是正常的,绕过写法反而是错的
207
+ - 需要在开发期验证「按当前用户过滤」「展示用户姓名」这类逻辑:改由服务端出数据。服务端的 `req.userContext` 和 `AuthNPaasService` 由网关注入,**不受此限制**,始终可用(见 `user-identity` / `contacts-service` skill)。`useCurrentUserProfile()` 在这里长期停在加载态就是这个原因
208
+ - 需要在开发期验证上传链路:改用接口测试直接打后端接口,或复用库里已有的文件 URL,不要在浏览器里点上传
209
+ - 依赖上述端点的 E2E 用例不要写成浏览器交互 Case,改走接口层往返验证
210
+
211
+ 另外,`UserSelect` 弹层是**搜索驱动**的:不输关键词时列表为空属预期行为,不是接口挂了。
212
+
182
213
  ## 质量保障流程
183
214
 
184
215
  通用提交前检查(代码检查 / 接口测试 / 读日志确认无错误)见系统任务流程约束;本栈具体落点:
@@ -232,8 +263,10 @@ shared/ # 前后端共享的目录
232
263
  - **三方集成**:调用第三方 API 需在后端实现,使用 @nestjs/axios
233
264
  - **能力边界**:服务端不支持文件上传(FaaS 限制),前端用 dataloom SDK 上传,服务端仅保存元信息
234
265
  - **环境判断**:`process.env.NODE_ENV === "production"` 表示生产环境
235
- - **文件系统**:**严禁使用 `fs` 写入非 `/tmp` 路径**(无状态 FaaS 容器)
266
+ - **文件系统**:临时文件、下载内容和处理中间产物统一写入 `/tmp` 下的独立目录,用完后清理
267
+ - **启动关键路径**:`bootstrap`、`onModuleInit` 和 `OnApplicationBootstrap` 只做必须的同步装配,不得执行 DDL、全量数据同步、长轮询或无界外部请求。应用应尽早 `listen()`;非关键预热放到后台任务,外部调用必须有超时、错误日志和降级路径
236
268
  - **服务端运行时资源文件**(字体 / 证书 / 模板 / wasm 等需在运行时读取的非代码文件,CRITICAL — 发布后静默失效根因):① 必须在 `nest-cli.json` 的 `assets` 中声明(如 `{"include": "assets/<dir>/**/*", "outDir": "dist/server"}`),否则不会进 `dist/` 构建产物;② 路径用 `__dirname` 相对**编译产物**定位(如 `path.join(__dirname, "../assets/...")`),**禁止 `process.cwd()` 相对源码路径**——dev 跑源码能命中、发布跑 `dist/` 会落空;③ 资源缺失或加载失败必须 **fail-loud**(抛错或明确错误日志),禁止 `catch` 后静默返回残缺产物
269
+ - **启动生命周期**:`constructor`、`onModuleInit` 和 `onApplicationBootstrap` 应快速完成,避免在其中下载大文件、全量同步或批量迁移;仅将钩子声明为 `async` 仍会被等待,无法缩短启动时间
237
270
 
238
271
  ## 日志约定
239
272
 
@@ -339,6 +372,12 @@ await db.select().from(users).where(eq(users.adminUser, userId));
339
372
 
340
373
  - **禁止删类型注解消除报错**(`as unknown as T` 同禁):典型症状是「改完 service,错误跑到 controller 了」——错误没有转移,是校验点被往外推了一层;推到最外层删完,报错归零而契约失守。
341
374
 
375
+ ### 业务日期
376
+
377
+ 需求里的日期是**自然日**(哪一天 / 哪一周 / 哪一月)而不是精确时刻时,按业务时区计算,未指定时默认 `Asia/Shanghai`。
378
+
379
+ 项目里放一个 helper 统一产出 `day` / `month` / `weekStart` / `shiftDay`;服务端写入、统计聚合、前端展示都比较它的返回值。
380
+
342
381
  ### PostgreSQL SQLSTATE / Drizzle 异常处理(CRITICAL)
343
382
 
344
383
  - 可预期幂等 / 冲突优先用数据库原子语义(`ON CONFLICT`、`UPDATE ... WHERE ... RETURNING`),不要靠异常流兜底。
@@ -446,6 +485,7 @@ await db.select().from(users).where(eq(users.adminUser, userId));
446
485
  async submitPublic(@Body() dto) { ... }
447
486
  ```
448
487
  8. **OpenAPI 文档同步**:改动 `*.openapi.controller.ts` 或其引用的 interface / service 返回值 / schema 字段时,加载 `openapi-guide` skill,同步更新 `docs/openapi.json`
488
+ 9. **服务端查用户信息(CRITICAL)**:**必须** `import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core'` 并用 `listUsersByIds`(`PlatformModule.forRoot()` 已注册 global 的 `AuthNPaasModule`,**不要**加 `providers`、**不要**加直接依赖)。**禁止**复用应用内已有的用户 service、裸调平台接口、从库表 join 人名头像、依赖 `req.userContext.userId`(`/openapi` 下恒为空)、`try/catch` 吞掉 SDK 抛的平台错误、用 `getBatchLarkUserIds` 判断用户是否存在(外部用户无 employeeId)、拿不到的字段用 `''` 占位。搜人 / 搜部门 / 搜群、手机号职位工号上级服务端**拿不到**,走飞书开放平台。动手前加载 `contacts-service` skill 第六节
449
489
 
450
490
  ## 异常处理
451
491
 
@@ -467,10 +507,13 @@ await db.select().from(users).where(eq(users.adminUser, userId));
467
507
 
468
508
  - **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码(`const { userId } = req.userContext`)
469
509
  - 依赖用户信息的接口不要用 API 测试工具测试,用 `think` 工具思考
510
+ - 后端按 `user-identity` 和 `@lark-apaas/fullstack-nestjs-core` 的 Express `Request` 增强读取 `req.userContext`;`tenantId` 为可选 number,不能套用前端 `window.tenantId` / `viewContext` 字符串语义。
511
+ - Controller 直接用 `@Req() req: Request`;测试、mock 或业务窄类型用独立接口,只声明实际读取字段。不要继承 Express `Request` 或重声明 `userContext`;未读 `tenantId` 就省略,必须保留时用 `unknown` 或 SDK 兼容类型。
470
512
 
471
513
  ```typescript
472
- // req.userContext 字段(由身份认证中间件挂载):
473
- // - userId/tenantId/appId: string
514
+ // req.userContext 字段(由身份认证中间件挂载,完整合同见 user-identity):
515
+ // - userId/appId: string
516
+ // - tenantId: SDK Request.userContext 的可选 number
474
517
  // - env: 'preview' | 'runtime' (preview=预览态, runtime=发布运行态)
475
518
  // - userName/userNameEn: string
476
519
  // - userNameI18n: string (多语名字, 如 {zh_cn: '用户', en_us: 'user'})
@@ -547,7 +590,9 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
547
590
 
548
591
  1. 使用 API 测试工具测试不依赖用户信息的接口
549
592
  2. 积极使用 `read_logs` 工具,如无有效日志可增加 logger 打印
550
- 3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController
593
+ 3. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController。若路径含 `__runtime__`,那就是下一条——GET 未被代理时会落 SPA fallback 返回 index.html
594
+ 4. 请求路径含 `__runtime__` 的 404(POST)或 200 + HTML(GET):属沙箱 dev 环境限制,不是应用缺陷,见「沙箱 dev 不提供平台 runtime 接口」
595
+ 5. 服务端启动即崩、`/api` 全部 502 且报 `UnknownDependenciesException ... argument Function at index [0]`:查构造函数有没有空参 `@Inject()`(见 plugin-guide),不要先去改 Module 的 imports / providers
551
596
 
552
597
  ---
553
598
 
@@ -568,6 +613,8 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
568
613
  {% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
569
614
  - **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
570
615
  - **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
616
+ {% else %}
617
+ - **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
571
618
  {% endif %}
572
619
 
573
620
  ## API 请求
@@ -605,6 +652,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
605
652
  {% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
606
653
  | TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
607
654
  | Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
655
+ {% else %}
656
+ | Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
608
657
  {% endif %}
609
658
 
610
659
  ### 组件 Skill 召回规则(强制执行)
@@ -768,15 +817,8 @@ return <h1>{data?.title || '未知标题'}</h1>;
768
817
  | 验证 | zod |
769
818
  | 工具函数 | lodash |
770
819
  | 样式 | clsx |
771
- | Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
772
- | PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
773
- | 文件上传 | react-dropzone |
774
- | 二维码 | qrcode.react |
775
820
  | 用户反馈 | sonner |
776
- | 拖拽 | @dnd-kit/core |
777
- | 数字动画 | react-countup |
778
- | Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
779
- | 3D 场景 | cobe |
821
+
780
822
 
781
823
  ## 滚动分页最佳实践
782
824