仓库自包含化:CI/文档/规范入库,升级 echarts,填关于页链接
CI / test (push) Successful in 53s
Release / release (push) Successful in 32s

This commit is contained in:
2026-07-09 19:05:28 +08:00
parent db45a669e3
commit 9d0c116ce3
31 changed files with 2235 additions and 71 deletions
+5
View File
@@ -0,0 +1,5 @@
# API 层与状态管理规范
- 所有请求经统一 request 封装(`src/api/request`):注入 JWT、401 统一跳登录、错误统一提示;组件内不直接 `fetch`/裸 axios。
- 跨页面共享状态才进 Pinia;页面内状态用 `ref`/`computed` 就地管理,不把一切塞 store。
- 请求函数返回类型化数据(`Promise<Instance[]>`),错误向上抛由封装层兜底,不在每个调用点重复 try/catch。
@@ -0,0 +1,59 @@
# Component Guidelines
> How components are built in this project.
---
## Overview
<!--
Document your project's component conventions here.
Questions to answer:
- What component patterns do you use?
- How are props defined?
- How do you handle composition?
- What accessibility standards apply?
-->
(To be filled by the team)
---
## Component Structure
<!-- Standard structure of a component file -->
(To be filled by the team)
---
## Props Conventions
<!-- How props should be defined and typed -->
(To be filled by the team)
---
## Styling Patterns
<!-- How styles are applied (CSS modules, styled-components, Tailwind, etc.) -->
(To be filled by the team)
---
## Accessibility
<!-- A11y requirements and patterns -->
(To be filled by the team)
---
## Common Mistakes
<!-- Component-related mistakes your team has made -->
(To be filled by the team)
@@ -0,0 +1,36 @@
# 前端目录结构
> Vue 3 前端工程的目录职责与命名约定。
## 目录职责
```text
oci-portal-dash/
├── package.json 脚本 dev / build / typecheck / lint / format
├── vite.config.ts @ 别名指向 src/;dev 代理 /api → localhost:8080
├── .env.development VITE_MOCK=1,开发默认走内置 mock 数据
├── index.html
└── src/
├── main.ts 应用入口,挂 Pinia 与 router
├── App.vue NConfigProvider 注入 Naive 主题
├── assets/ main.css:Tailwind @theme 设计 token(浅色)+ html.dark 暗色变量覆盖与全局基础样式
├── theme/ tokens.ts(JS 侧 token 唯一来源,含 darkTokens 暗色板)、naive.ts(亮暗两套 themeOverrides 同一工厂生成)
├── types/ api.ts:与 docs/API接口文档.md 一一对应的 DTO 类型
├── api/ request 封装(JWT 注入、401 跳登录、错误 hint 拼接)+ 按资源分模块;VITE_MOCK=1 时返回 mock.ts 数据
├── stores/ Pinia:auth(令牌持久化)、app(侧栏收缩、暗色主题:默认跟随系统 + localStorage 记忆,驱动 html.dark 与 Naive darkTheme)、scope(全局作用域:租户/区域/区间,localStorage 按租户记忆)
├── router/ 路由与登录守卫
├── composables/ useAsync(异步状态)、useFormat(时间/字节/OCID 格式化)、useRegionAlias(区域友好名)、useTransientPoll(过渡态自动轮询:实例电源 / 挂载操作后每 5 秒刷新至稳态)
├── components/ StatusBadge、OcidText、CostChart 等复用组件;表单基座 FormModal / FormField(hint 问号 tooltip + error / feedback 下置提示)/ FormSection(分组标题)/ FilePicker(含 compact 单行态)/ AppInputNumber(数字框统一封装:右侧垂直上下箭头步进);tenant/ 下为租户详情各 tab 与失联占位 DeadPlaceholder(失联时整体替换 tab 内容),storage/ 下为引导卷编辑 / 挂载弹窗,task/ 下含 CronEditor 可视化编辑器与任务表单(tab 分类型),instance/ 下含 InstanceSpecForm(实例规格表单,创建实例弹窗与抢机任务共用),logs/ 下为日志页各 tab 面板(SystemLogPanel 系统日志、LogEventPanel 回传日志)
├── layouts/ AppLayout(顶栏 + 内容区,顶栏右侧为全局作用域租户 / 区域选择器)、SidebarNav(分组导航、收缩态)
└── views/ 页面:登录、总览、租户列表/详情、实例列表/详情、网络、VCN 详情、引导卷列表(编辑 / 挂载 / 删除以弹窗内联完成,无单独详情页)、任务、日志(tab 结构:「系统日志」操作留痕,预留回传日志)、设置(tab 结构:「通知」通知管理 + Telegram 配置,「任务」抢机熔断阈值)等
```
## 全局作用域约定
租户 / 区域在顶栏右上角全局选择,区间在各资源列表页内选择,三者经 scope store 全站共享——实例 / 网络 / 引导卷页跟随同一作用域查询单租户数据,切页无需重选;选择按租户记忆在 localStorage,未开启多区域 / 多区间支持的租户对应选择器禁用锁定默认值。租户导入 / 修改 / 删除 / 测活、订阅新区域成功后经 `scope.refreshConfigs()` 即时刷新全局选择器,无需刷新页面。
## 命名约定
- `src/` 下按职责分层:`views/`(页面)、`components/`(复用组件)、`composables/``api/`(接口封装)、`stores/`(Pinia)、`types/``assets/`
- 组件文件 PascalCase(`TaskLogDrawer.vue`),composable/工具文件 camelCase,路由路径 kebab-case。
- `api/` 模块按后端资源划分(`instances.ts``tasks.ts`…),函数名与接口语义对应,不散调 axios。
+51
View File
@@ -0,0 +1,51 @@
# Hook Guidelines
> How hooks are used in this project.
---
## Overview
<!--
Document your project's hook conventions here.
Questions to answer:
- What custom hooks do you have?
- How do you handle data fetching?
- What are the naming conventions?
- How do you share stateful logic?
-->
(To be filled by the team)
---
## Custom Hook Patterns
<!-- How to create and structure custom hooks -->
(To be filled by the team)
---
## Data Fetching
<!-- How data fetching is handled (React Query, SWR, etc.) -->
(To be filled by the team)
---
## Naming Conventions
<!-- Hook naming rules (use*, etc.) -->
(To be filled by the team)
---
## Common Mistakes
<!-- Hook-related mistakes your team has made -->
(To be filled by the team)
+44
View File
@@ -0,0 +1,44 @@
# Frontend Development Guidelines(oci-portal-dash 前端规范)
> Vue 3 前端(`oci-portal-dash/`)编码规范入口。
---
## 技术栈约束
Vue 3 + Vite + TypeScript + Naive UI + Tailwind CSS v4 + ECharts + Pinia。Naive UI 承担组件视觉(`themeOverrides`),Tailwind 只承载设计 token 与布局工具类。
---
## Guidelines Index
| Guide | 内容 |
|-------|------|
| [Directory Structure](./directory-structure.md) | src 分层、文件命名与全局作用域约定 |
| [TypeScript & Vue](./typescript-vue.md) | TS 严格模式与组件写法 |
| [Styling & Design Tokens](./styling-design-tokens.md) | Anthropic 风设计 token、字体与反模式清单 |
| [API & State](./api-and-state.md) | 请求封装与 Pinia 使用边界 |
---
## Pre-Development Checklist
写代码前确认:
- [ ] 读过 [Directory Structure](./directory-structure.md),文件放对层(views / components / composables / api / stores / types)
- [ ] 涉及颜色、圆角、间距时先看 [Styling & Design Tokens](./styling-design-tokens.md),禁止硬编码 hex
- [ ] 新增请求走 `api/` 统一封装,DTO 类型进 `types/`(与 `docs/API接口文档.md` 一一对应)
- [ ] 出 UI 设计稿前读 `../guides/design-workflow.md`
## Quality Check
提交前必过:
```bash
npm run lint # ESLint(flat config,含 eslint-plugin-vue recommended)+ Prettier
npm run typecheck # vue-tsc --noEmit
npm run build # 类型检查 + 产物构建
```
- 提交信息沿用项目规范(中文、≤50 字),不采用 Conventional Commits。
- 依赖变更后 `package.json` 与 lockfile 一起提交;不提交 `node_modules`、构建产物。
@@ -0,0 +1,51 @@
# Quality Guidelines
> Code quality standards for frontend development.
---
## Overview
<!--
Document your project's quality standards here.
Questions to answer:
- What patterns are forbidden?
- What linting rules do you enforce?
- What are your testing requirements?
- What code review standards apply?
-->
(To be filled by the team)
---
## Forbidden Patterns
<!-- Patterns that should never be used and why -->
(To be filled by the team)
---
## Required Patterns
<!-- Patterns that must always be used -->
(To be filled by the team)
---
## Testing Requirements
<!-- What level of testing is expected -->
(To be filled by the team)
---
## Code Review Checklist
<!-- What reviewers should check -->
(To be filled by the team)
@@ -0,0 +1,51 @@
# State Management
> How state is managed in this project.
---
## Overview
<!--
Document your project's state management conventions here.
Questions to answer:
- What state management solution do you use?
- How is local vs global state decided?
- How do you handle server state?
- What are the patterns for derived state?
-->
(To be filled by the team)
---
## State Categories
<!-- Local state, global state, server state, URL state -->
(To be filled by the team)
---
## When to Use Global State
<!-- Criteria for promoting state to global -->
(To be filled by the team)
---
## Server State
<!-- How server data is cached and synchronized -->
(To be filled by the team)
---
## Common Mistakes
<!-- State management mistakes your team has made -->
(To be filled by the team)
@@ -0,0 +1,111 @@
# 样式与设计 Token 规范
> Anthropic 风(warm editorial)设计体系:色板、字体、圆角与反模式清单。代码事实源:`src/theme/tokens.ts`(JS 侧唯一来源)与 `src/assets/main.css`(Tailwind `@theme`)。
## 基本规则
- 设计 token(色板、圆角、字体、间距)唯一来源是全局 CSS 变量(Tailwind `@theme`),Naive UI `themeOverrides` 从同一份取值;组件内禁止散落硬编码 hex。
- Tailwind 工具类只用于布局、间距、排版;组件视觉(按钮/输入/表格)交给组件库主题,不用工具类二次描皮。
- 间距遵循 4px 网格;圆角、阴影、配色遵守下方 token 与反模式清单。
## 风格特征
- **暖纸色底**:不用纯白,用象牙色 `#FAF9F5` 做主背景,靠 1px 细边框(hairline)和一层灰度差分层,几乎不用投影。
- **单一强调色**:赤陶橙(terracotta)只出现在主按钮、激活态、品牌标记上,页面 95% 面积是黑白灰米。
- **小圆角、扁平**:圆角 4–6px 量级;无渐变、无玻璃拟态、无 3D 浮雕、无大面积色块光晕。
- **排版即装饰**:层级靠字号/字重/留白拉开;图标用细线性风格(lucide/tabler),不用 emoji 和彩色立体图标。
- **克制的动效**:过渡 150–250ms、只做透明度/位移,无弹跳和视差。
## 官方色值(Anthropic 官方 brand-guidelines)
主色:
| Token | 色值 | 用途 |
| --- | --- | --- |
| Dark | `#141413` | 主文本、深色背景 |
| Light | `#FAF9F5` | 浅色背景、深底上的文本 |
| Mid Gray | `#B0AEA5` | 次要元素、占位、禁用 |
| Light Gray | `#E8E6DC` | 细微背景、分隔线、边框 |
强调色:
| Token | 色值 | 用途 |
| --- | --- | --- |
| Orange | `#D97757` | 主强调(按钮、激活、链接 hover) |
| Blue | `#6A9BCC` | 次强调(信息、链接) |
| Green | `#788C5D` | 三级强调(成功) |
品牌橙近似变体(社区考证):`#C15F3C`(Crail,更深)与 `#CC785C`(Book Cloth),可用作橙色的 hover/active 深档。
## 管理面板适配 token
官方色板缺管理面板必需的语义色,按同色温补齐:
| 语义 | 建议值 | 说明 |
| --- | --- | --- |
| 背景主 | `#FAF9F5` | 页面底 |
| 背景面 | `#F0EEE6` | 侧栏、区块底(Light 与 Light Gray 之间) |
| 卡片 | `#FFFFFF` | 白卡浮在暖底上,配 1px `#E8E6DC` 边框 |
| 文本主/次 | `#141413` / `#6B6A63` | 次级文本由 Dark 与 Mid Gray 插值 |
| 主强调 | `#D97757`,hover `#C15F3C` | |
| 成功 / 信息 | `#788C5D` / `#6A9BCC` | 官方绿 / 蓝 |
| 警告 | `#B8860B` 一档的暖赭 | 与暖色调和谐,避免高饱和黄 |
| 危险 | `#BF4D43` 一档的砖红 | 避免纯红 `#FF0000` 系 |
| 暗色模式 | 底 `#141413`、面 `#1F1E1D`、边框 `#33322E` | 文本反转用 Light |
| 圆角 | 按钮/输入 4px,卡片 6px,最大不超过 8px | 拒绝大圆角 |
| 阴影 | 无或 `0 1px 2px rgba(20,20,19,.05)` | 分层靠边框 |
## 字体
- 官方品牌字体 **Styrene**(无衬线)+ **Tiempos**(衬线)为商业授权,不可直接使用;官方开源替代:标题 **Poppins**、正文 **Lora**
- 本项目:UI 与数据全用无衬线(`Inter``Poppins`,中文回退 `PingFang SC` / `Noto Sans SC`);OCID、IP、日志等用等宽(`JetBrains Mono` / `ui-monospace`);衬线(Lora)只做登录页/大标题的编辑风点缀,可选。
## 反模式清单(设计稿与代码评审逐条对照)
大圆角(>8px 的按钮/卡片)、任何渐变(含品牌色渐变文字)、玻璃拟态/毛玻璃、霓虹光晕、彩色大投影、emoji 当图标、深紫深蓝"AI 感"配色、满屏色块卡片、弹跳动效。
## Common Mistake: NModal 宽度不能用 Tailwind class
**Symptom**:`<NModal preset="card" class="w-[480px]">` 弹窗实际渲染为全宽。
**Cause**:naive-ui 的样式为运行时注入(CSSinJS,`<style>` 追加在 head 尾部),其 `.n-card{width:100%}` 与 Tailwind utilities 同特异性但**后加载**,覆盖 `w-[480px]`
**Fix / Prevention**:弹窗宽度一律用内联 style(优先级最高),与 FormModal.vue 同法:
```html
<!-- Bad:被 naive-ui 运行时样式覆盖 -->
<NModal preset="card" class="w-[480px] max-w-[92vw]">
<!-- Good -->
<NModal preset="card" :style="{ width: '480px', maxWidth: 'calc(100vw - 24px)' }">
```
## Common Mistake:编辑 SFC 时残留 `defineProps<...>()()` 双括号
**Symptom**:运行时 `ReferenceError: defineProps is not defined`(setup 内),但 `vue-tsc``vite build` 全绿——编译期完全静默。
**Cause**:对 `defineProps<{...}>()` 做字符串替换时旧串漏了尾部 `()`,替换后残留成 `()()``@vue/compiler-sfc` 只识别「宏调用语句」,对「调用宏返回值」的表达式不做宏转换,把 `defineProps` 原样保留成运行时标识符。
**Prevention**:替换 defineProps/defineEmits 行必须包含**完整语句(含结尾 `()`)**;宏相关改动后,不能只信 build 全绿,须实际打开一次使用该组件的页面(详情弹窗等惰性挂载组件尤其容易漏)。
## Common Mistake:scoped style 中 `:global(...)` 后接后代选择器会丢失后代
**Symptom**:`:global(html.dark) .totp-qr { ... }` 编译产物变成 `html.dark { ... }`——`.totp-qr` 整段丢失,规则错误落到 `html` 元素上(background/padding 直接作用于全页),目标元素完全不匹配。build 全绿、无告警。
**Cause**:`@vue/compiler-sfc` 的 scoped 转换对「`:global()` 后跟普通后代选择器」的组合处理不可靠,后代部分被丢弃而非追加 scope id。
**Fix / Prevention**:完整选择器整体放进 `:global()`:
```css
/* Bad:编译后 .totp-qr 丢失,规则落到 html.dark 本身 */
:global(html.dark) .totp-qr { background: #f5f4ed !important; }
/* Good:原样输出 html.dark .totp-qr(自命名 class 需保证全局唯一) */
:global(html.dark .totp-qr) { background: #f5f4ed !important; }
```
验证方法:build 后 `grep -o 'html\.dark[^}]*}' dist/assets/<view>-*.css` 核对编译产物选择器,或线上 DevTools 看 computed style 是否命中。
## 组件速查:NQrCode 去白底
NQrCode 自带 inline `background-color:#FFF; padding:12px`(props 可控)。两主题都不垫任何色块的方案:`:padding="0" background-color="transparent"` + `:color="app.dark ? '#f5f4ed' : '#141413'"`——暗色下反色渲染(浅模块透明底),主流扫码器(iOS 相机 / Google Lens / 新版 ML Kit)可识别,扫不动时弹窗内的手动密钥是兜底。color 是响应式 prop,切主题自动重绘。(历史方案「html.dark 下垫 #f5f4ed 浅底」因用户不接受色块框已废弃。)
+51
View File
@@ -0,0 +1,51 @@
# Type Safety
> Type safety patterns in this project.
---
## Overview
<!--
Document your project's type safety conventions here.
Questions to answer:
- What type system do you use?
- How are types organized?
- What validation library do you use?
- How do you handle type inference?
-->
(To be filled by the team)
---
## Type Organization
<!-- Where types are defined, shared types vs local types -->
(To be filled by the team)
---
## Validation
<!-- Runtime validation patterns (Zod, Yup, io-ts, etc.) -->
(To be filled by the team)
---
## Common Patterns
<!-- Type utilities, generics, type guards -->
(To be filled by the team)
---
## Forbidden Patterns
<!-- any, type assertions, etc. -->
(To be filled by the team)
+16
View File
@@ -0,0 +1,16 @@
# TypeScript 与 Vue 组件规范
## TypeScript
- 开启 `strict`;禁止 `any`,未知类型用 `unknown` 后收窄。
- 对象形状优先 `interface`,联合/工具类型用 `type`;类型导入写 `import type`
- 与后端交互的 DTO 类型集中在 `types/`,字段与 `docs/API接口文档.md` 一一对应,不在组件里手写内联响应类型。
-`const` 对象 + `as const` 代替 `enum`
## Vue 组件
- 一律 `<script setup lang="ts">` 组合式 API;组件名 PascalCase 且多词(`InstanceTable`,避免与 HTML 元素冲突)。
- `defineProps`/`defineEmits` 写完整类型;props 只读,不在子组件内改。
- `v-for` 必须带 `key`,且不与 `v-if` 写在同一元素上。
- 组件内样式一律 `scoped`;跨组件复用样式上提到全局 token 或工具类。
- 复用逻辑抽 composable(`useXxx` 命名,放 `composables/`);单文件组件函数遵循 ≤30 行的项目约定。
@@ -0,0 +1,223 @@
# Code Reuse Thinking Guide
> **Purpose**: Stop and think before creating new code - does it already exist?
---
## The Problem
**Duplicated code is the #1 source of inconsistency bugs.**
When you copy-paste or rewrite existing logic:
- Bug fixes don't propagate
- Behavior diverges over time
- Codebase becomes harder to understand
---
## Before Writing New Code
### Step 1: Search First
```bash
# Search for similar function names
grep -r "functionName" .
# Search for similar logic
grep -r "keyword" .
```
### Step 2: Ask These Questions
| Question | If Yes... |
|----------|-----------|
| Does a similar function exist? | Use or extend it |
| Is this pattern used elsewhere? | Follow the existing pattern |
| Could this be a shared utility? | Create it in the right place |
| Am I copying code from another file? | **STOP** - extract to shared |
---
## Common Duplication Patterns
### Pattern 1: Copy-Paste Functions
**Bad**: Copying a validation function to another file
**Good**: Extract to shared utilities, import where needed
### Pattern 2: Similar Components
**Bad**: Creating a new component that's 80% similar to existing
**Good**: Extend existing component with props/variants
### Pattern 3: Repeated Constants
**Bad**: Defining the same constant in multiple files
**Good**: Single source of truth, import everywhere
### Pattern 4: Repeated Payload Field Extraction
**Bad**: Multiple consumers cast the same JSON/event fields locally:
```typescript
const description = (ev as { description?: string }).description;
const context = (ev as { context?: ContextEntry[] }).context;
```
This is duplicated contract logic even when the code is only two lines. Each
consumer now has its own definition of what a valid payload means.
**Good**: Put the decoder, type guard, or projection next to the data owner:
```typescript
if (isThreadEvent(ev)) {
renderThreadEvent(ev);
}
```
**Rule**: If the same untyped payload field is read in 2+ places, create a
shared type guard / normalizer / projection before adding a third reader.
---
## When to Abstract
**Abstract when**:
- Same code appears 3+ times
- Logic is complex enough to have bugs
- Multiple people might need this
**Don't abstract when**:
- Only used once
- Trivial one-liner
- Abstraction would be more complex than duplication
---
## After Batch Modifications
When you've made similar changes to multiple files:
1. **Review**: Did you catch all instances?
2. **Search**: Run grep to find any missed
3. **Consider**: Should this be abstracted?
### Reducers Should Use Exhaustive Structure
When state is derived from action-like values (`action`, `kind`, `status`,
`phase`), prefer a reducer with one `switch` over scattered `if/else` updates.
```typescript
// BAD - action-specific state transitions are hard to audit
if (action === "opened") { ... }
else if (action === "comment") { ... }
else if (action === "status") { ... }
// GOOD - one reducer owns the transition table
switch (event.action) {
case "opened":
...
return;
case "comment":
...
return;
}
```
This matters when the event log is the source of truth. A reducer is the
documented replay model; display code and commands should not duplicate pieces
of that replay model.
---
## Checklist Before Commit
- [ ] Searched for existing similar code
- [ ] No copy-pasted logic that should be shared
- [ ] No repeated untyped payload field extraction outside a shared decoder
- [ ] Constants defined in one place
- [ ] Similar patterns follow same structure
- [ ] Reducer/action transitions live in one reducer or command dispatcher
---
## Gotcha: Python if/elif/else Exhaustive Check
**Problem**: Python's if/elif/else chains have no compile-time exhaustive check. When you add a new value to a `Literal` type (e.g., `Platform`), existing if/elif/else chains silently fall through to `else` with wrong defaults.
**Symptom**: New platform works partially — some methods return Claude defaults instead of platform-specific values. No error is raised.
**Example** (`cli_adapter.py`):
```python
# BAD: "gemini" falls through to else, returns "claude"
@property
def cli_name(self) -> str:
if self.platform == "opencode":
return "opencode"
else:
return "claude" # gemini silently gets "claude"!
# GOOD: explicit branch for every platform
@property
def cli_name(self) -> str:
if self.platform == "opencode":
return "opencode"
elif self.platform == "gemini":
return "gemini"
else:
return "claude"
```
**Prevention**: When adding a new value to a Python `Literal` type, search for ALL if/elif/else chains that switch on that type and add explicit branches. Don't rely on `else` being correct for new values.
---
## Gotcha: Asymmetric Mechanisms Producing Same Output
**Problem**: When two different mechanisms must produce the same file set (e.g., recursive directory copy for init vs. manual `files.set()` for update), structural changes (renaming, moving, adding subdirectories) only propagate through the automatic mechanism. The manual one silently drifts.
**Symptom**: Init works perfectly, but update creates files at wrong paths or misses files entirely.
**Prevention**:
- **Best**: Eliminate the asymmetry — have the manual path call the automatic one (e.g., `collectTemplateFiles()` calls `getAllScripts()` instead of maintaining its own list)
- **If asymmetry is unavoidable**: Add a regression test that compares outputs from both mechanisms
- When migrating directory structures, search for ALL code paths that reference the old structure
**Real example**: `trellis update` had a manual `files.set()` list for 11 scripts that `getAllScripts()` already tracked. Fix: replaced the manual list with a `for..of getAllScripts()` loop. See `update.ts` refactor in v0.4.0-beta.3.
---
## Template File Registration (Trellis-specific)
When adding new files to `src/templates/trellis/scripts/`:
**Single registration point**: `src/templates/trellis/index.ts`
1. Add `export const xxxScript = readTemplate("scripts/path/file.py");`
2. Add to `getAllScripts()` Map
That's it. `commands/update.ts` uses `getAllScripts()` directly — no manual sync needed.
**Why this matters**: Without registration in `getAllScripts()`, `trellis update` won't sync the file to user projects. Bug fixes and features won't propagate.
**History**: Before v0.4.0-beta.3, `update.ts` had its own hand-maintained file list that frequently fell out of sync with `getAllScripts()`. This caused 11 Python files to be silently skipped during `trellis update`. The fix was to eliminate the duplicate list and use `getAllScripts()` as the single source of truth.
### Quick Checklist for New Scripts
```bash
# After adding a new .py file, verify it's in getAllScripts():
grep -l "newFileName" src/templates/trellis/index.ts # Should match
```
### Template Sync Convention
`.trellis/scripts/` (dogfooded) and `packages/cli/src/templates/trellis/scripts/` (template) must stay identical. After editing `.trellis/scripts/`, always sync:
```bash
rsync -av --delete --exclude='__pycache__' .trellis/scripts/ packages/cli/src/templates/trellis/scripts/
```
**Gotcha**: Running rsync with wrong source/destination paths can create nested garbage directories (e.g., `.trellis/scripts/packages/cli/...`). Always double-check paths before running.
@@ -0,0 +1,327 @@
# Cross-Layer Thinking Guide
> **Purpose**: Think through data flow across layers before implementing.
---
## The Problem
**Most bugs happen at layer boundaries**, not within layers.
Common cross-layer bugs:
- API returns format A, frontend expects format B
- Database stores X, service transforms to Y, but loses data
- Multiple layers implement the same logic differently
---
## Before Implementing Cross-Layer Features
### Step 1: Map the Data Flow
Draw out how data moves:
```
Source → Transform → Store → Retrieve → Transform → Display
```
For each arrow, ask:
- What format is the data in?
- What could go wrong?
- Who is responsible for validation?
### Step 2: Identify Boundaries
| Boundary | Common Issues |
| --------------------- | --------------------------------- |
| API ↔ Service | Type mismatches, missing fields |
| Service ↔ Database | Format conversions, null handling |
| Backend ↔ Frontend | Serialization, date formats |
| Component ↔ Component | Props shape changes |
### Step 3: Define Contracts
For each boundary:
- What is the exact input format?
- What is the exact output format?
- What errors can occur?
---
## Common Cross-Layer Mistakes
### Mistake 1: Implicit Format Assumptions
**Bad**: Assuming date format without checking
**Good**: Explicit format conversion at boundaries
### Mistake 2: Scattered Validation
**Bad**: Validating the same thing in multiple layers
**Good**: Validate once at the entry point
### Mistake 3: Leaky Abstractions
**Bad**: Component knows about database schema
**Good**: Each layer only knows its neighbors
### Mistake 4: Every Consumer Parses The Same Payload
**Bad**: A command reads JSONL events and casts fields inline:
```typescript
const thread = (ev as { thread?: string }).thread;
const labels = (ev as { labels?: string[] }).labels;
```
This looks local, but it means every consumer owns a private version of the
event contract. The next field change will update one command and miss another.
**Good**: Decode once at the event boundary, then export typed projections:
```typescript
if (!isThreadEvent(ev)) return false;
return ev.thread === filter.thread;
```
**Rule**: For append-only logs, JSON streams, RPC payloads, or config files,
create one owner for:
- event / payload type definitions
- type guards and normalization from `unknown`
- metadata projections used by UI commands
- reducers that replay state from the source of truth
Rendering code may format fields, but it must not redefine the payload contract.
---
## Checklist for Cross-Layer Features
Before implementation:
- [ ] Mapped the complete data flow
- [ ] Identified all layer boundaries
- [ ] Defined format at each boundary
- [ ] Decided where validation happens
After implementation:
- [ ] Tested with edge cases (null, empty, invalid)
- [ ] Verified error handling at each boundary
- [ ] Checked data survives round-trip
- [ ] Checked that consumers import shared decoders / projections instead of
casting payload fields locally
- [ ] Checked that derived state points back to the source event identifier
(`seq`, `id`, `version`) instead of inventing a second cursor
---
## Cross-Platform Template Consistency
In Trellis, command templates (e.g., `record-session.md`) exist in **multiple platforms** with identical or near-identical content. This is a cross-layer boundary.
### Checklist: After Modifying Any Command Template
- [ ] Find all platforms with the same command: `find src/templates/*/commands/trellis/ -name "<command>.*"`
- [ ] Update all platform copies (Markdown `.md` and TOML `.toml`)
- [ ] For Gemini TOML: adapt line continuations (`\\` vs `\`) and triple-quoted strings
- [ ] Run `/trellis:check-cross-layer` to verify nothing was missed
**Real-world example**: Updated `record-session.md` in Claude to use `--mode record`, but forgot iFlow, Kilo, OpenCode, and Gemini — caught by cross-layer check.
---
## Generated Runtime Template Upgrade Consistency
Some generated files are both documentation and runtime input. In Trellis,
`.trellis/workflow.md` is parsed by `get_context.py`, `workflow_phase.py`,
SessionStart filters, and per-turn hooks. Template changes must be validated
against both fresh init and upgrade paths.
### Checklist: After Modifying A Runtime-Parsed Template
- [ ] Identify every runtime parser that reads the template, not just the file
writer that installs it
- [ ] Check whether relevant syntax lives outside obvious managed regions
such as tag blocks
- [ ] Verify fresh `init` output and a versioned `update` scenario that writes
the older `.trellis/.version`
- [ ] Add an upgrade regression using an older pristine template fixture, then
assert the installed file reaches the current packaged shape
- [ ] Update the backend spec that owns the runtime contract
---
## Versioned Documentation Boundary
Versioned documentation is a cross-layer boundary: source paths, `docs.json`
version routing, and the rendered version selector must all describe the same
release line.
### Checklist: Before Editing Versioned Docs
- [ ] Identify the target release line: stable, beta, or RC
- [ ] Verify the edited MDX path matches that line:
- stable: `docs-site/{start,advanced,...}` and `docs-site/zh/{start,advanced,...}`
- beta: `docs-site/beta/**` and `docs-site/zh/beta/**`
- RC: `docs-site/rc/**` and `docs-site/zh/rc/**`
- [ ] Verify `docs.json` navigation points the version label to the same paths
- [ ] Grep the opposite tree for release-line-specific terms before committing
- [ ] Treat beta content appearing under root release paths as a source-path bug,
not a rendering bug
**Real-world example**: A beta-only task workflow change documented
`prd.md` + `design.md` + `implement.md`, task-creation consent, and Codex
mode banners under root `start/` and `advanced/` paths. The docs site then
served 0.6 beta behavior under the Release selector. The fix was to restore root
release docs, move the 0.6 content to `beta/` and `zh/beta/`, and add a grep
audit for beta markers against the root release tree.
**Real-world example**: Codex inline mode changed workflow platform markers from
`[Codex]` / `[Kilo, Antigravity, Windsurf]` to `[codex-sub-agent]` /
`[codex-inline, Kilo, Antigravity, Windsurf]`. Fresh init was correct, but
`trellis update` only merged `[workflow-state:*]` blocks and preserved stale
markers outside those blocks. Result: upgraded projects got new hook scripts
but old workflow routing, so `get_context.py --mode phase --platform codex`
could return empty Phase 2.1 detail.
---
## Mode-Detection Probe Checklist
When a CLI auto-detects a mode by probing a remote resource (e.g., checking if `index.json` exists to decide marketplace vs direct download):
### Before implementing:
- [ ] Probe runs in **ALL** code paths that use the result (interactive, `-y`, `--flag` combos)
- [ ] 404 vs transient error are distinguished — don't treat both as "not found"
- [ ] Transient errors **abort or retry**, never silently switch modes
- [ ] Shared state (caches, prefetched data) is **reset** when context changes (e.g., user switches source)
- [ ] **Shortcut paths** (e.g., `--template` skipping picker) must have the same error-handling quality as the probed path — check that downstream functions don't call catch-all wrappers
### After implementing:
- [ ] Trace every path from probe result to the mode-decision branch — no fallthrough
- [ ] External format contracts (giget URI, raw URLs) are tested or at least documented as comments
- [ ] Metadata reads consume a complete response or use a streaming parser — never parse a fixed-size prefix as full JSON
- [ ] When reconstructing a composite identifier from parsed parts, verify **all** fields are included and in the **correct position** (e.g., `provider:repo/path#ref` not `provider:repo#ref/path`)
- [ ] Verify that **action functions** called after a shortcut don't internally use the old catch-all fetch — they must use the probe-quality variant when error distinction matters
**Real-world example**: Custom registry flow had 8 bugs across 3 review rounds: (1) probe only ran in interactive mode, (2) transient errors fell through to wrong mode, (3) giget URI had `#ref` in wrong position, (4) prefetched templates leaked across source switches, (5) `--template` shortcut bypassed probe but `downloadTemplateById` internally used catch-all `fetchTemplateIndex`, turning timeouts into "Template not found".
**Real-world example**: Agent-session update hints fetched npm `latest` metadata with `response.read(4096)` and then parsed it as complete JSON. The `@mindfoldhq/trellis` package metadata exceeded 4 KB, so the JSON was truncated, parse failed silently, and the first session injection showed no update hint. Fix: read the complete response before parsing, and add a regression where `version` is followed by an 8 KB metadata tail.
---
## Cross-Platform Template Consistency
In Trellis, command templates (e.g., `record-session.md`) exist in **multiple platforms** with identical or near-identical content. This is a cross-layer boundary.
### Checklist: After Modifying Any Command Template
- [ ] Find all platforms with the same command: `find src/templates/*/commands/trellis/ -name "<command>.*"`
- [ ] Update all platform copies (Markdown `.md` and TOML `.toml`)
- [ ] For Gemini TOML: adapt line continuations (`\\` vs `\`) and triple-quoted strings
- [ ] Run `/trellis:check-cross-layer` to verify nothing was missed
**Real-world example**: Updated `record-session.md` in Claude to use `--mode record`, but forgot iFlow, Kilo, OpenCode, and Gemini — caught by cross-layer check.
---
## Generated Runtime Template Upgrade Consistency
Some generated files are both documentation and runtime input. In Trellis,
`.trellis/workflow.md` is parsed by `get_context.py`, `workflow_phase.py`,
SessionStart filters, and per-turn hooks. Template changes must be validated
against both fresh init and upgrade paths.
### Checklist: After Modifying A Runtime-Parsed Template
- [ ] Identify every runtime parser that reads the template, not just the file
writer that installs it
- [ ] Check whether relevant syntax lives outside obvious managed regions
such as tag blocks
- [ ] Verify fresh `init` output and a versioned `update` scenario that writes
the older `.trellis/.version`
- [ ] Add an upgrade regression using an older pristine template fixture, then
assert the installed file reaches the current packaged shape
- [ ] Update the backend spec that owns the runtime contract
**Real-world example**: Codex inline mode changed workflow platform markers from
`[Codex]` / `[Kilo, Antigravity, Windsurf]` to `[codex-sub-agent]` /
`[codex-inline, Kilo, Antigravity, Windsurf]`. Fresh init was correct, but
`trellis update` only merged `[workflow-state:*]` blocks and preserved stale
markers outside those blocks. Result: upgraded projects got new hook scripts
but old workflow routing, so `get_context.py --mode phase --platform codex`
could return empty Phase 2.1 detail.
---
## Mode-Detection Probe Checklist
When a CLI auto-detects a mode by probing a remote resource (e.g., checking if `index.json` exists to decide marketplace vs direct download):
### Before implementing:
- [ ] Probe runs in **ALL** code paths that use the result (interactive, `-y`, `--flag` combos)
- [ ] 404 vs transient error are distinguished — don't treat both as "not found"
- [ ] Transient errors **abort or retry**, never silently switch modes
- [ ] Shared state (caches, prefetched data) is **reset** when context changes (e.g., user switches source)
- [ ] **Shortcut paths** (e.g., `--template` skipping picker) must have the same error-handling quality as the probed path — check that downstream functions don't call catch-all wrappers
### After implementing:
- [ ] Trace every path from probe result to the mode-decision branch — no fallthrough
- [ ] External format contracts (giget URI, raw URLs) are tested or at least documented as comments
- [ ] Metadata reads consume a complete response or use a streaming parser — never parse a fixed-size prefix as full JSON
- [ ] When reconstructing a composite identifier from parsed parts, verify **all** fields are included and in the **correct position** (e.g., `provider:repo/path#ref` not `provider:repo#ref/path`)
- [ ] Verify that **action functions** called after a shortcut don't internally use the old catch-all fetch — they must use the probe-quality variant when error distinction matters
**Real-world example**: Custom registry flow had 8 bugs across 3 review rounds: (1) probe only ran in interactive mode, (2) transient errors fell through to wrong mode, (3) giget URI had `#ref` in wrong position, (4) prefetched templates leaked across source switches, (5) `--template` shortcut bypassed probe but `downloadTemplateById` internally used catch-all `fetchTemplateIndex`, turning timeouts into "Template not found".
**Real-world example**: Agent-session update hints fetched npm `latest` metadata with `response.read(4096)` and then parsed it as complete JSON. The `@mindfoldhq/trellis` package metadata exceeded 4 KB, so the JSON was truncated, parse failed silently, and the first session injection showed no update hint. Fix: read the complete response before parsing, and add a regression where `version` is followed by an 8 KB metadata tail.
---
## When to Create Flow Documentation
Create detailed flow docs when:
- Feature spans 3+ layers
- Multiple teams are involved
- Data format is complex
- Feature has caused bugs before
---
## Event Log / Projection Boundary
Append-only logs are cross-layer contracts. A single event travels through:
```
CLI input → event writer → events.jsonl → reader → filter → reducer → display
```
### Checklist: After Adding A New Event Kind Or Field
- [ ] Add the event kind to the central event taxonomy
- [ ] Add a typed event variant or type guard at the event layer
- [ ] Add normalization helpers for array/object fields that come from
user input or JSON
- [ ] Keep `seq` / `id` assignment in the event writer only
- [ ] Make filters and reducers consume the typed event guard, not local casts
- [ ] Make display code consume reducer output or typed events, not raw JSON
- [ ] Add at least one regression that proves history replay and live filtering
use the same filter model
**Real-world example**: Thread channels added `kind: "thread"`, `description`,
`context`, labels, and `lastSeq`. The first implementation replayed thread
state correctly, but several commands still re-parsed event payload fields with
local casts. The fix was to make the core event layer own `ThreadChannelEvent`
and `isThreadEvent`, make `reduceChannelMetadata` the only channel metadata
projection, and make `reduceThreads` the only thread replay reducer.
+27
View File
@@ -0,0 +1,27 @@
# Claude Design 设计稿工作流
> 设计稿在 **Claude Design**(claude.ai/design)中产出与评审:云端项目制文件工作区,Claude 通过 MCP 工具直接读写项目文件,用户在网页编辑器中实时查看、点选修改、评论,双方操作同一份文件。
## 产物形态
- 设计稿是 **`.dc.html`(Design Components)文件**:HTML 承载视觉,`<x-dc>` 模板 + `support.js` 运行时(服务端下发,不手写)让用户能在 Claude Design 编辑器里点选任意元素直接改文案/样式,而不是只读预览。
- 交互原型(视图切换、筛选、hover 态)由文件内的逻辑类驱动,单文件即可承载多视图的完整面板原型。
- 设计 token 不依赖平台 design system,以 [styling-design-tokens.md](../frontend/styling-design-tokens.md) 的 Anthropic 风 token 直接内嵌为设计稿的样式事实源。
## 工作流
| 阶段 | 动作 | 说明 |
| --- | --- | --- |
| ① 加载规范 | `get_claude_design_prompt` | 每次会话先加载平台设计规范(.dc.html 约定、反 AI-slop 内容准则),再动手 |
| ② 建项目 | `create_project` | 一个设计主题一个项目;返回 claude.ai/design 项目链接 |
| ③ 写运行时 | `create_support_js` | 每个含 .dc.html 的目录一次,服务端写入当前 dc-runtime |
| ④ 声明与写入 | `finalize_plan``write_files` | 先声明本批要写的路径拿 plan_token;写入时带 etag(if_match)防止覆盖用户在编辑器里的并发修改 |
| ⑤ 渲染验证 | `render_preview` + playwright-cli | 渲染循环:截图 1440×900、查 console 错误/404/空白 → 修复 → 重渲,直到干净;再以"新眼睛"逐条核对需求与反模式清单 |
| ⑥ 交付评审 | 分享 claude.ai/design 页面链接 | 用户在编辑器里直接批注/修改;`?embed=1` 链接可实时看 Claude 的每次写入 |
## 协作与边界
- **并发安全**:用户可能同时在编辑器里改文件,所有写入必须带上次读到的 etag,冲突时以用户改动为基准重新套用变更,绝不无条件覆盖。
- **链接纪律**:交付给人的永远是 claude.ai/design 链接;`serve_url`(claudeusercontent.com)是带项目令牌的短时链接,仅供自动化浏览器验证,不得外发。
- **平台边界**:Claude Design 没有本地 skill/plugin 市场与内部生成 agent,设计由当前会话直接产出;风格底座与评审职责由 token 清单(含反模式)与渲染验证循环承接。
- **导出衔接**:定稿 .dc.html 即结构化 HTML/CSS,实现阶段按 `.trellis/frontend/` 前端规范翻译成 Vue SFC + Naive UI themeOverrides,token 一一对应。
+97
View File
@@ -0,0 +1,97 @@
# Thinking Guides
> **Purpose**: Expand your thinking to catch things you might not have considered.
---
## Why Thinking Guides?
**Most bugs and tech debt come from "didn't think of that"**, not from lack of skill:
- Didn't think about what happens at layer boundaries → cross-layer bugs
- Didn't think about code patterns repeating → duplicated code everywhere
- Didn't think about edge cases → runtime errors
- Didn't think about future maintainers → unreadable code
These guides help you **ask the right questions before coding**.
---
## Available Guides
| Guide | Purpose | When to Use |
|-------|---------|-------------|
| [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md) | Identify patterns and reduce duplication | When you notice repeated patterns |
| [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md) | Think through data flow across layers | Features spanning multiple layers |
---
## Quick Reference: Thinking Triggers
### When to Think About Cross-Layer Issues
- [ ] Feature touches 3+ layers (API, Service, Component, Database)
- [ ] Data format changes between layers
- [ ] Multiple consumers need the same data
- [ ] You're not sure where to put some logic
- [ ] You are adding an event kind, JSONL record, RPC payload, or config field
- [ ] UI / command code starts casting raw payload fields directly
→ Read [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md)
### When to Think About Code Reuse
- [ ] You're writing similar code to something that exists
- [ ] You see the same pattern repeated 3+ times
- [ ] You're adding a new field to multiple places
- [ ] **You're modifying any constant or config**
- [ ] **You're creating a new utility/helper function** ← Search first!
- [ ] Two files read the same untyped payload field with local casts
- [ ] Multiple branches update the same derived state from `kind` / `action`
→ Read [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md)
### When Verifying AI Cross-Review Results
- [ ] Reviewer claims "user input can be malicious" → Check the actual data source (internal manifest? user config? external API?)
- [ ] Reviewer flags "missing validation" → Is the data from a trusted internal source?
- [ ] Reviewer says "behavior change" → Read the code comments — is it intentional design?
- [ ] Reviewer identifies a "bug" in test → Mentally delete the feature being tested — does the test still pass? If yes → tautological test
**Common AI reviewer false-positive patterns**:
1. **Trust boundary confusion**: Treating internal data (bundled JSON manifests) as untrusted external input
2. **Ignoring design comments**: Flagging intentional behavior documented in code comments as bugs
3. **Variable misreading**: Not tracing a variable to its actual definition (e.g., Map keyed by path vs name)
**Verification rule**: Every CRITICAL/WARNING finding must be verified against the actual code before prioritizing. Budget ~35% false-positive rate for AI reviews.
---
## Pre-Modification Rule (CRITICAL)
> **Before changing ANY value, ALWAYS search first!**
```bash
# Search for the value you're about to change
grep -r "value_to_change" .
```
This single habit prevents most "forgot to update X" bugs.
---
## How to Use This Directory
1. **Before coding**: Skim the relevant thinking guide
2. **During coding**: If something feels repetitive or complex, check the guides
3. **After bugs**: Add new insights to the relevant guide (learn from mistakes)
---
## Contributing
Found a new "didn't think of that" moment? Add it to the relevant guide.
---
**Core Principle**: 30 minutes of thinking saves 3 hours of debugging.