# TFDS 页面布局与宽高自适应规范

> **作用域**：所有 TFDS 业务页面的**外部框架**与**响应式行为**。本规范回答两个问题：
>
> 1. 页面骨架长什么样？（页框 / 内容区 / 卡片分层）
> 2. 浏览器窗口缩放/极宽/极窄时，页面要如何反应？（不溢出、不截断、不被撑歪）
>
> **优先级**（与 SKILL.md 一致）：组件索引 > SKILL.md > 本文件 > GLOBAL_DESIGN_RULES.md > DESIGN_PRINCIPLES.md > 模型自身审美。

---

## 0.0 快查索引（先按问题找章节，再读细节）

> 本文件较长。**生成或排查页面时**，先按下表定位章节，不要从头读到尾。已读过 `LAYOUT_RECIPES.md` 选好主 recipe 的，再回这里拿骨架。

| 你的问题 / 信号 | 直接跳到 |
| --- | --- |
| 我和 `LAYOUT_RECIPES.md` 的分工是什么？什么时候读哪份？ | §0.1 与 `LAYOUT_RECIPES.md` 的关系 |
| B 端不同视口宽度（1920 / 1440 / 1280 / 1024）该如何反应？需要做移动端吗？ | §0 B 端响应式策略 + §0.1 桌面视口验收点 |
| 用户在这一屏要堆叠浏览还是多面板对照工作？该用整页滚还是面板内滚？ | §0.5 滚动模型决策树 |
| 我要不要照搬基础页面框架 + 6 个页面示例的写法？哪些片段必须复制？ | §0.8 学习与强化来源 |
| 页面最外层根容器怎么写？`h-dvh` / `bg-*` / `overflow-*` 怎么定？ | §1 页面根容器 |
| 左侧 NavBar 怎么固定？AppShell 高度模型是什么？ | §1.1 NavBar 左侧固定 App Shell |
| 灰底框架 → 白卡 / NavBar → 白卡之间到底留几像素？哪些误用是高频错？ | §1.2 灰底框架间距铁律 |
| 页面 header 到底坐灰底还是包白卡？什么时候是“裸 PageMetaBar”？ | §1.5 白卡 vs 灰底裸区决策 |
| 顶 / 中 / 底三段式骨架（header + 主内容 + 底部）的固定写法 | §2 三段式骨架 |
| 双栏 / 三栏 / 多栏布局（含 Copilot 侧助、客服三栏、参数区 + 结果区）怎么写？ | §3 双栏 / 多栏布局 |
| 长表格 / 长列表怎么滚？表头怎么吸顶？分页放哪？ | §4 表格 / 长列表的滚动规则 |
| IM / Chat 场景输入框为什么飘 / 为什么会跟消息一起滚？怎么吸底？ | §5 输入框吸底（IM / Chat） |
| 页面整体是否需要居中收窄？`max-w-*` 是否允许？ | §6 页面宽度策略 |
| Input / Select / TextArea 为什么没满宽？正文表单 vs 筛选栏的宽度规则 | §6.3 表单控件宽度决策 |
| 白卡里又出现一张白卡 / 灰底里又出现灰底？嵌套到底允许几层？ | §7 白卡嵌套层级规则 |
| Modal / Drawer / Sheet / 全屏页用什么尺寸？什么场景用哪种？ | §8 弹窗 / 抽屉 / 浮层尺寸 |
| z-index 写成 9999 / 99999？统一 token 在哪？ | §9 Z-index 规范 |
| 自定义滚动条要不要写？什么时候写？ | §10 滚动条样式 |
| 整页生成完了，最终交付前要按哪张清单逐项扫一遍？ | §11 自适应自检 Checklist |
| 没有思路时先抄什么骨架？哪段是“拷贝即用”的最小可运行模板？ | §12 黄金骨架代码 |

---

## 0.1 与 `LAYOUT_RECIPES.md` 的关系

生成整页时先读 `LAYOUT_RECIPES.md` 选择唯一主 recipe，再回到本文件拿骨架细节。

- `LAYOUT_RECIPES.md` 回答：这是 `base-management`、`chat-home`、`chat-conversation`、`copilot-workbench`、单线程 `im-thread`，还是三栏客服 / IM 工作台 `customer-service-workspace`？外层是灰底直排、白色工作卡，还是客服工作台整页框架？
- 本文件回答：根容器、`main`、白卡、滚动层、双栏、多栏和响应式具体怎么写？
- 若 AI 入口页命中 `chat-home`，不得用本文件的通用白卡工作区规则覆盖入口页的“灰底直排”规则。

---

## 0. B 端响应式策略（统一决策）

TFDS 是**桌面优先**的 B 端工具产品，响应式策略和 C 端不同：


| 视口宽度          | 行为                                 |
| ------------- | ---------------------------------- |
| ≥ **1280px**  | 完整布局（首选目标）                         |
| 1024 - 1280px | 完整布局，必要时折叠次要侧栏                     |
| 768 - 1024px  | 双栏可降级为「侧栏抽屉化」；表格保留横向滚动             |
| < 768px       | **不主动适配**（B 端工具页极少需要手机访问），允许整页横向滚动 |


⛔ **禁止**给 B 端页加 `@media (max-width: 480px)` 的移动端样式（除非用户明确要求）。
✅ B 端首要任务是**桌面宽屏可用**，不是 mobile-first。

### 0.1 桌面视口验收点（交付前必查）

生成页面至少按以下桌面尺寸做布局自检，不能只看一个大屏截图：

| 视口 | 必查项 |
| --- | --- |
| `1920 × 900` | 页面不被过度居中或收窄；白卡、表格、主工作区充分利用宽度 |
| `1440 × 900` | 主工作流完整；右侧面板不挤压主区到不可读宽度 |
| `1280 × 768` | 筛选栏、Tabs、按钮组、表格工具条不溢出；底部操作 / 输入区可达 |
| `1024 × 768` | 次要侧栏允许折叠 / 收窄 / 内滚；主内容仍可完成核心任务 |

⛔ **禁止**通过浏览器缩放、隐藏关键控件或让 body 横向滚动来掩盖布局问题。
✅ 横向超宽内容只能在对应组件 / 面板内部滚动，例如 Table、代码区、Tabs viewport、InfoDisplayPanel 栏内内容。

---

## 0.5 滚动模型决策树（生成页面**前**必判）

> **整页滚动**和**面板内滚动**是两种完全不同的 UX 模型，绝不通用。
> 选错一次，就会出现"调参数得滚下去看结果、写 prompt 得滚回去看输入"这种破坏对照工作流的问题。

**决策步骤**：先判断"用户在这一屏需要做的事是堆叠浏览，还是多面板对照工作"。


| 信号                                                                  | 用户的认知模型     | 滚动策略                    | Recipe id | 用哪个骨架                           |
| ------------------------------------------------------------------- | ----------- | ----------------------- | -------- | ------------------------------- |
| **页面使用 `NavBar` 作为站点级导航**                                           | 平台壳 / 模块工作区 | **左侧固定，右侧内部滚 / 分区滚**    | `base-management` / `chat-home` / `chat-conversation` / `copilot-workbench` / `customer-service-workspace`（按页面主角再裁决） | § 1.1 + § 12.6                  |
| **多个独立工作区面板共存**（Prompt 编辑 + 参数 + 运行结果 / 输入 + 输出 / 代码 + 预览 / A/B 对比） | IDE / 调试器型  | **整页不滚**，每张面板独立全高、内部各自滚 | `copilot-workbench`（Playground 语义） | § 3.4 + § 12.7 三栏 Playground 骨架 |
| 列表 / 表格 / IM / 编辑器 单一主内容                                            | 单焦点工作       | 内容区内部滚                  | `base-management` / `im-thread` / `chat-conversation` / `copilot-workbench`（按模板裁决） | § 12.1 / § 12.3 / § 12.5        |
| 双栏：左导航 + 右内容                                                        | 浏览 + 操作     | 右侧内部滚                   | `base-management`（或 `customer-service-workspace` 的左/中/右三栏变体） | § 3.1 / § 12.2                  |
| **Dashboard / 信息聚合首页**（KPI + 入口卡 + 简介块，纯堆叠浏览，**没有**面板内编辑/输入/对照工作流）  | 堆叠浏览        | **整页滚**                 | （无 recipe；仅此场景例外） | § 12.4（仅此场景）                    |
| 长表单 / 详情 / 长文档                                                      | 通读 + 偶尔编辑   | 整页滚或单卡内滚                | `copilot-workbench`（若含“配置+预览/结果对照”优先） / `base-management`（若是单卡详情） | § 12.5 + § 6.2                  |


**Playground / 多面板触发关键词**（命中任一即走 §3.4 / §12.7，**不**走 §12.4）：

```
playground / 调试 / 试验 / 实验 / IDE / 多面板 /
prompt editor / prompt workspace / runtime / run result /
模型参数 + 输入区 + 结果区 / 编辑器 + 预览 / 输入 + 输出 / A/B 对比
```

⚠️ **典型误判**：Model Playground、Prompt Editor、API Playground、规则调试器，**不是** Dashboard。它们看着像"多张卡片堆一起"，但语义是 IDE，必须用 §3.4。

---

## 0.8 学习与强化来源（最高优先级样例：基础页面框架 + 6 个页面示例）

> 本文件的目标不是让你“凭规则脑补布局”，而是让你**直接对齐一套可运行的 B 端样例**，避免出现“宽高没撑满、卡片不齐、滚动层错位、组件基线不对齐”这类基础 UI 问题。

当你不确定某个布局细节（外框间距、白卡对齐、双栏分割、表格如何撑满、哪里滚动）时，**不要凭感觉**，直接对照以下“规范源”学习：

- **基础页面框架（页面模式 / page-pattern）**：`@tfdesign/b-end` 的 `BasePageFramePattern`
  - 源文件：`packages/tfds-b-end/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx`
  - 作用：提供“左 NavBar + 右主区”的最基础骨架，并承载 6 个页面示例的切换。
- **页面示例 0~5（可复制的模板 / page-template）**：用于强化“列表页/双白卡/顶部胶囊 Tab/空状态”的对齐方式
  - 示例 0：`StrategyListPage`（筛选 + 树形可展开列表 + 分页）
  - 示例 1：`VariableManagementPage`（单白卡：标题/筛选/表格；表格撑满 + 内滚）
  - 示例 2：`McpManagementPage`（双白卡：左侧可拖拽辅助大卡 + 右主白卡弹性撑满）
  - 示例 3：`TabTopBarListPage`（顶部胶囊 Tab 标题栏 + 白卡内容区；右侧详情面板可收起）
  - 示例 4：`NoAccessPage`（空状态 + 行动按钮；白卡居中对齐）
  - 示例 5：`ConstructionPage`（建设中空状态；白卡居中对齐）
- **列表页共享结构（避免手搓不齐）**：`pageListShared.jsx`
  - 源文件：`packages/tfds-b-end/src/_b_end_runtime/page-patterns/pageListShared.jsx`
  - 作用：沉淀“标题栏 + 筛选栏 + 表格列宽”的统一排布与对齐节奏。

### 0.8.1 从样例提炼出的“页面撑满 + 对齐”硬规则（高频 UI Bug 的根因清单）

只要你遇到下面任意现象，就按这张表回到样例结构做对照修复：


| 现象（你看到的 UI 问题）              | 99% 根因                                       | 直接修复到的目标结构（不要发明新结构）                | 首选对照样例                                       |
| --------------------------- | -------------------------------------------- | ---------------------------------- | -------------------------------------------- |
| NavBar 跑到页面顶部 / 与内容上下堆叠     | 把 `NavBar` 当普通内容块，根容器仍写成 `flex-col`          | §1.1 的左侧固定 app shell 骨架            | `BasePageFramePattern` + §12.6               |
| 页面没撑满高度，出现 body 滚动 / 顶底栏被滚走 | 根容器没用 `h-dvh + overflow-hidden`；滚动层放错了       | §1 根容器骨架（根不滚，滚动下沉到内容层）             | `BasePageFramePattern` + `TabTopBarListPage` |
| 页面左右没撑满或两侧大空白               | `max-w-`* / `mx-auto` / `px-6` 误用在内容外框；基础页面框架额外给 NavBar 右侧加了左 padding | §1.2 间距金字塔；基础页面框架专用 `PAGE_FRAME_CONTENT_MARGIN` | `LAYOUT_RULES §1.2` + `BasePageFramePattern` + `McpManagementPage`    |
| 双栏不齐 / 左侧被压缩 / 右侧溢出         | 横向 flex 漏 `min-w-0`；固定侧没写 `shrink-0 w-[Npx]` | §3.1 / §3.2 双栏骨架                   | `McpManagementPage`                          |
| 表格不撑满、下方空白或撑到很高             | 表格外层缺 `flex-1 min-h-0`（没给高度边界）               | §4.1 表格滚动骨架（表格层内部滚）                | `VariableManagementPage`                     |
| 卡片内容溢出把整页撑开                 | 卡片正文层没写 `flex-1 min-h-0` 或滚动没下沉              | §3.4 / §3.5（标题固定，正文承担滚动）           | `CreateQAContent`（BasePageFramePattern 内）    |
| 组件基线不齐（标题、筛选、表格上沿对不齐）       | 乱用 margin/padding；没有“外框/白卡/白卡内容”三层节奏         | 通用页面按：外框 `p-4`、白卡间 `gap-2`、白卡内 `p-6`；基础页面框架按 `pageListShared.jsx` 专用 token | `pageListShared.jsx`（节奏参考）                   |
| Tabs / 筛选栏 / 按钮组撑破卡片或页面 | 横向 flex 漏 `min-w-0`；Tabs 或父级被写成 `w-fit` / `min-w-max` / 固定大宽度 | Tabs 保持默认内部横向滚动；父级 `min-w-0 max-w-full overflow-hidden`；筛选和操作区允许 wrap / 更多菜单 | `Tabs` 组件规则 + `pageListShared.jsx` |


> ⚠️ 说明：基础页面框架的 6 个页面示例是最高优先级样例。生成 `BasePageFramePattern` / 菜单栏入口页时，必须复制 `pageListShared.jsx` 的间距 token，而不是回退到通用 `main p-4`。

---

## 1. 页面根容器（最外层）

**所有**新生成的页面，最外层根容器**必须**满足：

```jsx
<div
  className="
    h-dvh                       /* 高度撑满视口（动态视口，比 h-screen 更适配移动端浏览器栏） */
    w-full min-w-0 max-w-full   /* 宽度自适应，禁止写死 */
    flex flex-col               /* 纵向布局，子项垂直排列 */
    overflow-hidden             /* 根容器永远不滚动！滚动交给内部某一层 */
  "
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  {/* 顶部固定区（如 Topbar） — shrink-0 */}
  {/* 中部弹性区 — flex-1 min-h-0 + 内部滚动 */}
  {/* 底部固定区（如 ChatInput / 工具条）— shrink-0 */}
</div>
```

⛔ **禁止**：


| ❌ Bad                                  | 后果                           |
| -------------------------------------- | ---------------------------- |
| `<div className="min-h-screen">`       | 浏览器 body 滚动 → 输入框被挤出视口、整页上下滚 |
| `<div className="w-[1440px]">`         | 写死宽度 → 小屏出现横滚                |
| `<div className="overflow-auto">` 在最外层 | 滚动条出现在最外层，吸顶/吸底失效            |
| 漏写 `min-w-0`                           | 子项内容溢出会把页面撑出浏览器宽度            |


⚠️ **页面根容器无外圈圆角/描边**：灰底必须**贴浏览器边缘**展示，不要 `rounded-[12px]` 包整页。

## 1.1 NavBar 左侧固定 App Shell（高优先级例外）

> `NavBar` 不是普通内容模块，而是平台级站点导航锚点。
> **只要页面使用 `NavBar`，就默认进入 app shell 模式**：左侧固定导航 + 右侧弹性主工作区。

### NavBar 命中时的一级骨架铁律

- ✅ 页面根容器优先使用 `flex flex-row`，不要继续沿用通用 `flex-col` 页面壳
- ✅ `NavBar` 永远放在最左侧，并作为 `shrink-0` 固定区存在
- ✅ 右侧主内容区必须是 `flex-1 min-w-0 flex flex-col overflow-hidden`
- ✅ `NavBar` 属于平台壳层，不进入白卡，不进入顶部 header，不与主内容上下堆叠
- ✅ 当右侧是 `CustomerServiceWorkspaceFramePattern` 时，工作台框架直接作为右侧 `flex-1 min-w-0 min-h-0 overflow-hidden` 主工作区，不再套 `main p-4`、白卡 wrapper、外层 margin 或额外 padding；工作台顶部、底部、右侧贴浏览器边界，仅保留框架自身内部 16px 节奏
- ✅ 客服工作台左侧可组合 `NavBar platform="bytehi"` / ByteHi 菜单栏；该 NavBar 是 AppShell 左栏，不属于客服工作台内部插槽
- ✅ 只有用户**明确指定**“顶部导航”或“移动端降级”时，才允许偏离左侧固定模式

**标准骨架**：

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-row overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <NavBar className="shrink-0" />

  <div className="flex-1 min-w-0 flex flex-col overflow-hidden">
    {/* 右侧页面 header / Topbar / 白卡工作区都放这里 */}
  </div>
</div>
```

**客服工作台 + ByteHi/NavBar 骨架**：

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-row overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <NavBar platform="bytehi" className="shrink-0" />
  <div className="flex flex-1 min-w-0 min-h-0 overflow-hidden">
    <CustomerServiceWorkspaceFramePattern />
  </div>
</div>
```

⛔ **客服工作台组合禁止项**：

- 不要写 `<main className="p-4"><CustomerServiceWorkspaceFramePattern /></main>`
- 不要写 `<Card><CustomerServiceWorkspaceFramePattern /></Card>`
- 不要在 `NavBar` 与客服工作台之间插入 `gap`、`ml-*`、`padding-left` 或灰底空隙
- 不要再给工作台右侧、顶部、底部增加外层框架间距；这些边界应直接贴浏览器可视区

⛔ **禁止**：

- `<div className="flex flex-col"><NavBar /> <main>...</main></div>`
- `<section style={{ background: 'var(--color-surface)' }}><NavBar /></section>`
- 先搭一个纵向页面，再把 `NavBar` 当普通块塞到顶部

**生成口诀**：

> 用 `NavBar`，先搭壳；壳是横向，不是纵向。左边导航固定，右边主区弹性。

---

## 1.2 灰底框架间距铁律（高频误用 · 与白卡内 padding 严格区分）

> **B 端最高频的宽度问题**：AI 把"白卡内 padding（24px）"当成"外框间距"用，导致卡片距浏览器边缘 24px 甚至更多，左右两侧出现大空白。
>
> **第二高频问题**：把"横向并列卡间距（8px）"误用到"上下板块叠放"上，导致 KPI 区紧贴下方列表卡、筛选行紧贴 Tabs，节奏过紧、缺少呼吸。

### 三层间距金字塔（按"通用灰底页面 / 基础页面框架"区分）

```
┌────────────────────────────────────────────────────────────┐
│  浏览器可视区（灰底，background: var(--color-blueGrey-200)） │
│  ←── 通用页面 main wrapper: p-4（16px）———————————————    │
│  │  ┌─ 板块 A（KPI 区 / 工作区 / 列表卡）────────────────┐  │
│  │  │  ┌──────────┐  ←─ gap-2（8px）→  ┌──────────┐  │  │
│  │  │  │  白  卡  │  横向并列 8px      │  白  卡  │  │  │
│  │  │  └──────────┘                    └──────────┘  │  │
│  │  └────────────────────────────────────────────────┘  │
│  │             ↕  上下叠放 gap-4（16px）                  │
│  │  ┌─ 板块 B（列表 / 表格 / 详情）─────────────────────┐  │
│  │  │  ┌────────────────────────────────────────────┐ │  │
│  │  │  │           白卡（p-6 卡内 24px）            │ │  │
│  │  │  └────────────────────────────────────────────┘ │  │
│  │  └────────────────────────────────────────────────┘  │
│  ←── 通用页面 main wrapper: p-4（16px）———————————————    │
└────────────────────────────────────────────────────────────┘
```


| 位置                         | 值        | Tailwind            | 含义                                     |
| -------------------------- | -------- | ------------------- | -------------------------------------- |
| 通用灰底页面 → 白卡边缘                | **16px** | `p-4` / `px-4 py-4` | 非基础页面框架的通用 main 容器 padding，白卡距浏览器边 16px          |
| 基础页面框架普通入口 → 右侧白卡/工作区 | **16 / 16 / 16 / 0** | `PAGE_FRAME_CONTENT_MARGIN` | `margin/padding: 16px 16px 16px 0`，左侧 NavBar 与右侧白卡之间不额外加 16px 灰底空隙 |
| 基础页面框架页面示例3 → 内容白卡 | **0 / 16 / 16 / 0** | `PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN` | 页面示例3 顶部 Tab 已占据上方节奏，内容白卡不再额外加顶部和左侧间距 |
| **横向并列**：白卡 ↔ 白卡（同一工作区组内）  | **8px**  | `gap-2`             | 双白卡分栏 / 多面板 Playground 横向相邻；属于"同组协作"语义 |
| **纵向叠放**：白卡 ↔ 白卡 / 白卡 ↔ 其他板块组件（页面 header / Pill Tabs / 筛选行 / KPI 区 / 段落区）| **16px** | `gap-4`             | 上下两个独立 UI 板块默认 16px；不只对白卡，所有"块级板块"上下排列都按此节奏 |
| 白卡边缘 → 卡内内容                | **24px** | `p-6`               | 白卡内部 padding，仅写在白卡 div 上               |

⚠️ **判别口诀**：

- `flex-row` 父容器的子项 → 同组协作 → `gap-2`
- `flex-col` 父容器的子项（含 main、aside 子面板纵向叠放、灰底纵向板块栈）→ 跨板块呼吸 → `gap-4`
- 唯一例外：白卡**内部**多个子区块上下排（卡内层级），按 [§3.5 / §6.x] 的 `gap-4 / gap-6` 走，本节只管"灰底框架级"间距
- 基础页面框架菜单入口 → 直接使用 `pageListShared.jsx` 的 `PAGE_FRAME_CONTENT_MARGIN / PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN / PAGE_FRAME_PANEL_GAP`，不要自行写 `main p-4`


⛔ **禁止混淆（图同款误用）**：


| ❌ Bad                                                        | 后果                 | ✅ Fix                                                                             |
| ------------------------------------------------------------ | ------------------ | --------------------------------------------------------------------------------- |
| `<main className="... px-6 py-6">`                           | 白卡距浏览器边 24px，左右偏空  | 通用页面改 `p-4 gap-2`；基础页面框架改用 `PAGE_FRAME_CONTENT_MARGIN`                                                |
| `<main className="... px-8">`                                | 白卡距浏览器边 32px，明显过宽  | 通用页面改 `p-4 gap-2`；基础页面框架改用 `PAGE_FRAME_CONTENT_MARGIN`                                                |
| `<main className="... p-2">`                                 | 白卡距浏览器边 8px，外框过窄   | 通用页面改 `p-4 gap-2`；基础页面框架改用 `PAGE_FRAME_CONTENT_MARGIN`                                                |
| `<main>` 内再套 `<div className="max-w-[1440px] mx-auto px-8">` | 内容被居中收窄，左右出现大空白    | **禁止在 main 内套居中容器**，white cards 直接放 main 下                                        |
| 基础页面框架右侧主区写 `<main className="p-4">`                  | NavBar 与右侧白卡之间多出 16px 灰底空隙 | 使用 `PAGE_FRAME_CONTENT_MARGIN = 16px 16px 16px 0` 或页面示例3的 `PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN = 0 16px 16px 0` |
| 通用页面 `<main className="... p-4">` 但白卡自身再加 `mx-4`                  | 白卡有额外 margin，不紧贴框架 | 通用页面白卡 div 不加 `mx-*`；基础页面框架只允许使用共享 margin token |


✅ **通用灰底页面正确骨架**：

```jsx
{/* 通用页面 main：p-4 撑出 16px 灰底框架；纵向叠放统一 gap-4（16px） */}
<main className="flex-1 min-h-0 flex flex-col p-4 gap-4 overflow-hidden w-full">
  <header className="shrink-0 ...">...</header>  {/* 页面 header，直接坐灰底；与下方板块间距 16px */}

  {/* 板块 A：横向双白卡分栏（同组工作区） → 横向 gap-2（8px） */}
  <section className="shrink-0 flex flex-row gap-2 min-w-0">
    <div className="shrink-0 w-[260px] ..." style={{ background: 'var(--color-surface)' }}>...</div>
    <div className="flex-1 min-w-0 ..." style={{ background: 'var(--color-surface)' }}>...</div>
  </section>

  {/* 板块 B：单白卡 → 与板块 A 上下叠放，自动按 main 的 gap-4 间隔 16px */}
  <section
    className="flex-1 min-h-0 flex flex-col overflow-hidden"
    style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
  >
    <div className="shrink-0 px-6 py-4">...</div>       {/* 白卡内 header，p-6 */}
    <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-6">...</div>  {/* 白卡内容，p-6 */}
  </section>
</main>
```

✅ **基础页面框架菜单入口正确骨架**：

```jsx
import {
  PAGE_FRAME_CONTENT_MARGIN,
  PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN,
  PAGE_FRAME_PANEL_GAP,
  WHITE_CARD_STYLE,
} from './pageListShared';

{/* 普通入口：示例0/1/2/4/5，左侧 NavBar 到右侧白卡不额外加 16px */}
<section className="flex flex-1 min-h-0 min-w-0" style={{ margin: PAGE_FRAME_CONTENT_MARGIN }}>
  <div className="flex flex-1 min-w-0 flex-col overflow-hidden" style={WHITE_CARD_STYLE}>
    {/* 白卡内容，内部 padding 24px */}
  </div>
</section>

{/* 页面示例3：顶部 Tab 已占位，内容白卡使用 0/16/16/0 */}
<section className="flex flex-1 min-h-0 min-w-0" style={{ margin: PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN }}>
  <div className="flex flex-1 min-w-0 overflow-hidden" style={{ ...WHITE_CARD_STYLE, gap: PAGE_FRAME_PANEL_GAP }}>
    {/* 列表 + 详情面板 */}
  </div>
</section>
```

⚠️ **宽度 + 间距自检**：在生成完整页面后，检查代码中所有 `main` / `section` / 最外层白卡 div：

1. 通用灰底页面 `main` padding 默认是 `p-4`（或 `px-4 pb-4`）；基础页面框架菜单入口必须使用 `PAGE_FRAME_CONTENT_MARGIN / PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN`
2. `main` 内**不允许**再套 `max-w-*` / `mx-auto` 的居中容器
3. **纵向叠放**（`flex-col` 父容器、main 直系子项、白卡 + KPI 区 + 筛选行 + Tab 栏 + header 上下排）→ **统一 `gap-4`（16px）**
4. **横向并列**（`flex-row` 父容器，双白卡分栏 / 多面板 Playground 横向相邻）→ **统一 `gap-2`（8px）**
5. 通用页面白卡自身不加 `mx-* / my-*`；基础页面框架只允许使用 `pageListShared.jsx` 共享 margin token，禁止散落硬编码 margin
6. 所有横向 `flex` 父容器补 `min-w-0`，纵向补 `min-h-0`——缺失会让某一层被内容撑开而不撑满

---

## 1.5 白卡 vs 灰底裸区 决策（页面 header 专项 · 高频误用）

> TFDS 的视觉层次是「**灰底吸顶 + 白卡浮岛**」。白卡和灰底裸区**承担不同语义**，绝不通用。

### 1.5.1 白卡和灰底裸区分别承担什么


| 区域类型             | 视觉                                                                   | 语义                  | 必须装什么                                                | ⛔ 不该装什么                          |
| ---------------- | -------------------------------------------------------------------- | ------------------- | ---------------------------------------------------- | -------------------------------- |
| **白卡（工作区容器）**    | `surface` 纯白底 + 12px 圆角，⛔**无 border / outline / ring**、⛔**无 shadow** | "用户在这里**做事**"       | 列表 / 表格 / 对话流 / 参数表单 / 运行结果 / 编辑器 / 多步流程主体           | 页面标题 / 返回按钮 / 主次操作按钮 / 工具条 / 面包屑 |
| **灰底裸区（页面级元数据）** | 直接坐在 `blueGrey-200` 灰底上，无背景容器                                        | "用户**了解 / 切换**当前位置" | 页面标题 + 描述 + 主次操作（返回/导入/导出）/ 面包屑 / 顶部 Pill Tabs / 工具条 | 长内容 / 表格 / 对话流 / 表单字段            |


⚠️ **白卡描边专项铁律**（高频误用，详见 GLOBAL_DESIGN_RULES § 5.5）：

- ⛔ **禁止**给最外层大白卡加 `border` / `outline` / `ring` / `borderColor`（任何描边都不允许）
- ✅ **保留**白卡**内部**所有 TFDS 组件自带的描边（Input / Select / TagInput / Tag / Tabs line / Table 行线 / 内嵌 grey Card 等——这些是控件功能边界，不是卡片层级边界）
- 判别口诀：背景色 `--color-surface`（白）→ **禁止** border；背景色 `--color-blueGrey-200`（灰，页面外框）→ **允许** border

**判别口诀**：问自己**这块内容用户是"做事"还是"看位置/切换/触发动作"**？前者包白卡，后者直接坐灰底。

### 1.5.2 标准结构（强约束）

```jsx
<div className="h-dvh w-full ..." style={{ background: 'var(--color-blueGrey-200)' }}>
  <Topbar />  {/* 全局产品级顶导，独立组件，不在本节讨论 */}

  <main className="flex-1 min-h-0 flex flex-col p-4 gap-4 overflow-hidden">
    {/* ✅ 页面 header：直接坐灰底，无白卡背景 */}
    <header className="shrink-0 px-2 pt-2 pb-1 flex items-start justify-between gap-3">
      <div className="flex items-center gap-3 min-w-0">
        <Button variant="outline-black" iconOnly icon={<Icon name="arrow-left-stroked" />} />
        <FormTitle variant="form" title="Model Playground" titleSuffix={<Tag variant="success" size="m">TFDS</Tag>} description="保持原始工作区、中会话与右侧配置结果的位置关系。" showDescription />
      </div>
      <div className="shrink-0 flex items-center gap-2">
        <Button variant="outline-black">查看源稿</Button>
        <Button variant="primary">导入信息</Button>
      </div>
    </header>

    {/* ✅ 内容主体：白卡承载真正的工作区 */}
    <section
      className="flex-1 min-h-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      {/* Prompt Workspace / Conversation / Settings / Run Result */}
    </section>
  </main>
</div>
```

### 1.5.3 Bad / Good 对照（图 1 同款误用）


| ❌ Bad                                                    | 后果                        | ✅ Good                                                                     |
| -------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------- |
| `<div bg=surface rounded-lg p-6>{标题 + 描述 + 操作按钮组}</div>` | 上下两张同等大小的白卡，主从混乱、气息感丢失    | `<header className="px-2 pt-2 flex justify-between">{...}</header>`（直接坐灰底） |
| 把"返回 + 面包屑 + 主操作"包白卡                                     | 工具条变成"内容卡"，挤占真正内容区高度      | 工具条 `shrink-0` 直接坐灰底                                                       |
| 把页面级 Pill Tabs（服务策略 / 批测&归因 / 运行数据）放进白卡                  | Tabs 失去"页面级切换"语义，看着像卡内子页签 | Pill Tabs 直接坐灰底（参照 CopilotPagePattern 的 TopBarTabs）                        |
| 顶部"标题卡 + Tab 卡 + 内容卡"三张白卡叠加                              | 页面变成乐高积木，没有视觉焦点           | 顶部一行灰底裸放 + 一张内容白卡                                                          |


### 1.5.4 例外情况（极少数）

下面三种场景**允许**把 header 装进白卡，但要写注释说明理由，避免 AI 误学习：

1. **白卡内的子区块标题**：白卡内部分多个区块时，区块自己的小标题用 `<FormTitle variant="level-2" />` 写在卡内（这是卡内层级，不是页面级 header）
2. **沉浸式编辑器（§12.5 全白底页）**：整页白底，没有"灰底 vs 白卡"对比，header 直接坐白底（不是包白卡）
3. **Chat 列表项之类强制规整化的列表**：列表项用 Card 包，但列表项的 header 是卡内顶栏，不属于本节讨论

⛔ **禁止以"为了跟内容卡对齐 padding"为由把 header 包白卡**：直接给 header 写 `px-6` 跟内容卡 padding 对齐即可，不需要白卡背景。

### 1.5.5 AI 入口页（ChatHomePagePattern）背景与白卡边界（强约束）

AI 入口页的核心是「**从 0 发起任务**」：用户第一步是输入一句需求 / 搜索助手 / 从模板开始。它的视觉应该是**浅灰大背景承载整页氛围**，并在灰底上直接组织 Hero、筛选行与卡片网格。
⚠️ 这里最常见的误用是：为了“看起来更像后台”，在右侧内容区外又包了一整张白色大卡片容器，导致页面层级变成“灰底 → 大白卡 → 卡片”，整体像嵌套面板，入口页气质被破坏。

- ✅ **正确**：灰底为主，白卡只出现在**推荐卡片本身**（`<Card color="white" />`）
- ⛔ **禁止**：在右侧内容区外额外包“整张白色大卡片容器”（例如 `style={{ background: 'var(--color-surface)' }}` 的大 wrapper）
- ⛔ **禁止**：把 `Card color="grey"` 当作入口页模板卡默认值；若模板卡父级直接是灰底，必须用 `Card color="white"`。
- **代码扫描口诀**：同一首页里出现 `ChatInput` + 模板 `Card` 网格时，向上找最近三层父容器；如果有 `bg-surface` / `--color-surface` / `background: '#fff'` 且包住整块 Hero + 网格，它就是错误的大白卡 wrapper。

Bad / Good 对照：


| ❌ Bad（错误嵌套）                        | 后果                                | ✅ Good（正确层级）                  |
| ---------------------------------- | --------------------------------- | ----------------------------- |
| 右侧内容区整体包一层 `bg=surface rounded-lg` | 灰底被“吞掉”，入口页像普通白卡管理页；页面层级变硬、主输入不突出 | Hero / 筛选行 / 网格直接坐灰底；仅卡片自身为白卡 |


推荐骨架（可直接照抄）：

```jsx
<div className="h-dvh w-full min-w-0 flex overflow-hidden" style={{ background: 'var(--color-blueGrey-200)' }}>
  <NavBar className="shrink-0" />

  {/* 右侧内容区：整体仍在灰底上组织，不再外包一整张白卡 */}
  <main className="flex-1 min-w-0 min-h-0 flex flex-col overflow-hidden">
    {/* Hero（可用渐变区） */}
    <section className="shrink-0" style={{ padding: '120px 40px 80px' }}>
      <div className="w-full" style={{ maxWidth: 680, margin: '0 auto' }}>
        <ChatInput variant="default" placeholder="描述你想完成的任务…" />
      </div>
    </section>

    {/* 筛选行：Tabs 默认 SM */}
    <section className="shrink-0 flex items-center justify-between gap-4" style={{ padding: '16px 40px' }}>
      <Tabs variant="pill" size="sm" items={tabs} />
      <Input prefix={<Icon name="search-md-stroked" size="sm" />} placeholder="搜索模板" />
    </section>

    {/* 网格：内部滚动；卡片自身是白卡 */}
    <section className="flex-1 min-h-0 overflow-y-auto" style={{ padding: '0 40px 40px' }}>
      <div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(260px, 100%), 1fr))' }}>
        <Card color="white" title="智能客服助手" />
      </div>
    </section>
  </main>
</div>
```

---

## 2. 三段式骨架（顶 / 中 / 底）

最常见的业务页面结构：

```
┌─────────────────────────────┐  ← shrink-0  Topbar / 面包屑
├─────────────────────────────┤
│                             │  ← flex-1 min-h-0
│  内容区（自身内部滚动）        │     overflow-y-auto
│                             │
├─────────────────────────────┤
│  输入框 / 全局工具条（可选）  │  ← shrink-0
└─────────────────────────────┘
```

**Tailwind 实现**：

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  {/* 顶部：永远不参与滚动，shrink-0 让它保持自然高度 */}
  <header className="shrink-0">{/* Topbar */}</header>

  {/* 中部：flex-1 占满剩余空间；min-h-0 是 flex 子项必备，否则会被内容撑高 */}
  <main className="flex-1 min-h-0 flex flex-col overflow-hidden">
    {/* 真正的滚动层放这里 */}
    <div className="flex-1 min-h-0 overflow-y-auto p-6">
      {/* 业务内容 */}
    </div>
  </main>

  {/* 底部：可选，吸底 */}
  <footer className="shrink-0">{/* 可选：ChatInput 等 */}</footer>
</div>
```

**关键魔法**：

- ✅ `flex-1 min-h-0` 是 flex 子项**必须**配套写的两件套，缺一会导致内容把父容器撑高
- ✅ `overflow-y-auto` 只能加在「真正承载长内容」的那一层，不要每层都加
- ✅ 横向布局的所有 flex 父容器都要补 `min-w-0`，否则子项内容（特别是带 `text-overflow`/表格）会溢出

---

## 3. 双栏 / 多栏布局

### 3.1 左导航 + 右内容

```jsx
<main className="flex-1 min-h-0 flex flex-row min-w-0">
  {/* 左侧：固定宽度，shrink-0 防止被压缩 */}
  <aside className="shrink-0 w-[240px] overflow-y-auto">
    {/* TagBar 或左侧导航 */}
  </aside>

  {/* 右侧：弹性宽度，min-w-0 让内部 truncate 等生效 */}
  <section className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
    {/* 主内容 */}
  </section>
</main>
```

⛔ **禁止反向写法**：左侧 `flex-1`、右侧 `w-[XXXpx]`（应当**固定的写死，弹性的 flex-1**）

### 3.2 双白卡分栏（McpManagementPage 形态）

灰底页面上**两张独立白卡**：

```jsx
const [leftCardWidth, setLeftCardWidth] = useState(260);

<div className="flex-1 min-h-0 flex flex-row min-w-0" style={{ margin: PAGE_FRAME_CONTENT_MARGIN, gap: PAGE_FRAME_PANEL_GAP }}>
  {/* 左白卡：容器级拖拽调宽；TagBar 自身透明、无拖拽把手 */}
  <aside
    className="relative shrink-0 flex flex-col overflow-visible"
    style={{
      width: `${leftCardWidth}px`,
      background: 'var(--color-surface)',
      borderRadius: 'var(--radius-lg)',
    }}
  >
    <TagBar
      className="!border-r-0"
      style={{ width: '100%', background: 'transparent' }}
      collapsed={leftCollapsed}
      onCollapsedChange={setLeftCollapsed}
    />
    <div
      role="separator"
      aria-label="调整标签栏白卡宽度"
      className="absolute top-0 bottom-0 -right-[6px] z-20 w-3 cursor-col-resize"
      onPointerDown={handleLeftCardResizePointerDown}
    />
  </aside>

  {/* 右白卡：弹性 */}
  <div
    className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden"
    style={{
      background: 'var(--color-surface)',
      borderRadius: 'var(--radius-lg)',
    }}
  >
    {/* 主内容 */}
  </div>
</div>
```

**默认规则（新铁律）**：

- ✅ 只要是这种**横向并列的大卡工作区**，辅助白卡默认就要支持宽度拖拽；不要先写成固定宽死板双栏
- ✅ 主白卡始终保持 `flex-1 min-w-0`，辅助白卡由父容器维护 `width / min / max` 和拖拽事件
- ✅ TagBar 自身不再提供样式配置或右侧拖拽能力：不要写 `tone`、`resizable`、`onWidthChange`；TagBar 默认透明底，背景由所属白卡容器决定
- ✅ TagBar 底部展开/收起按钮必须保留，收起/展开语义是所属白卡容器一起收起/展开
- ✅ 拖拽把手挂在白卡容器外层，外层必须 `overflow-visible`，不能把把手裁掉
- ⛔ KPI 小卡、入口网格卡、列表项卡片不适用本规则；本规则只针对“主工作区级别”的大卡容器

⚠️ 左右同时存在辅助大卡时，左右都应允许在各自边界内调宽；不要默认只保留一个不可调的固定侧栏。

### 3.3 网格卡片（功能入口、KPI、产品列表）

**响应式列宽**用 CSS Grid 的 `auto-fit + minmax`：

```jsx
<div
  className="grid gap-4"
  style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}
>
  {items.map(item => <Card key={item.id}>{...}</Card>)}
</div>
```

**列宽断点参考**：


| 卡片类型              | minmax 第一个值        |
| ----------------- | ------------------ |
| 紧凑信息卡（KPI 数字、状态卡） | `min(240px, 100%)` |
| 中等卡（功能入口、配置卡）     | `min(320px, 100%)` |
| 宽信息卡（统计图表、内容预览）   | `min(420px, 100%)` |


⛔ **禁止**：写死 `grid-cols-3`、`grid-cols-4`（小屏会横滑或挤压）

### 3.4 多面板工作台 / Playground（最常被误用为 §12.4 网格工作台）

**适用**：Model Playground、Prompt Editor、API 调试器、规则编辑器、A/B 对比器——多个**独立工作区面板**并排或上下分块共享一屏。

**核心约束**（违反任一即视为 BUG）：

1. ✅ **整页绝不滚**：根容器永远 `h-dvh + overflow-hidden`
2. ✅ **每张面板独立全高**：用 `flex-1 min-h-0` 让面板撑满剩余空间，不靠面板自身高度
3. ✅ **每张面板独立内部滚**：面板**头部** `shrink-0`、面板**正文** `flex-1 min-h-0 overflow-y-auto`
4. ✅ **上下叠的子面板**：父容器 `flex flex-col`；固定块 `shrink-0`，唯一弹性块 `flex-1 min-h-0`
5. ✅ **横向辅助大面板默认支持拖拽调宽**：如左导航工作区、右配置区、右结果区，不要默认写死为不可调固定宽
6. ⛔ **禁止**把 main 写成 `overflow-y-auto`（这是 §12.4 信息聚合页骨架，不适合工作台）

**最小骨架**（横向三栏，每栏独立滚）：

```jsx
<main className="flex-1 min-h-0 flex flex-row p-4 gap-2 overflow-hidden min-w-0">
  {/* 面板 1：弹性宽 + 全高 + 内部滚 */}
  <section
    className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden"
    style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
  >
    <header className="shrink-0 px-6 py-4">
      {/* ⚠️ 面板内 section 标题统一用 FormTitle variant="level-2"（白卡内分区，描述同行右侧） */}
      <FormTitle variant="level-2" title="Prompt Workspace" showDescription={false} />
    </header>
    {/* ↓ 面板正文：标题不滚；System Prompt 这类主编辑器填满正文并在组件内部滚 */}
    <div className="flex-1 min-h-0 overflow-hidden px-6 pb-6">
      <TextArea variant="code" fillHeight resize="none" className="flex-1 min-h-0 w-full" />
    </div>
  </section>

  {/* 面板 2：默认支持拖拽调宽；上下两个子面板独立滚 */}
  <aside className="shrink-0 flex flex-col gap-4 overflow-visible" style={{ width: `${sidePanelWidth}px` }}>
    {/* 子面板 A：高度由内容决定 → shrink-0 */}
    <div
      className="shrink-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      <header className="shrink-0 px-6 py-4">
        <FormTitle variant="level-2" title="Model Settings" showDescription={false} />
      </header>
      <div className="px-6 pb-6 flex flex-col gap-4">
        {/* 参数表单 */}
      </div>
    </div>

    {/* 子面板 B：占满剩余空间 → flex-1 min-h-0 + 内部滚 */}
    <div
      className="flex-1 min-h-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      <header className="shrink-0 px-6 py-4">
        <FormTitle variant="level-2" title="Run Result" showDescription={false} />
      </header>
      <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-6">
        {/* 运行结果 */}
      </div>
    </div>
  </aside>
</main>
```

**宽度规则补充**：

- 默认初始宽度可以是 `280 / 320 / 360px` 一类语义值，但必须给出 `minWidth` 与 `maxWidth`
- 工作台里承担“辅助工作区”角色的横向大卡，默认都应可调宽；不要生成“左 280px 固定 + 右 360px 固定 + 中间硬挤”的僵硬三栏
- 主工作区面板继续优先 `flex-1 min-w-0`，由剩余空间自然伸缩
- 把手如果挂在卡片边缘，卡片外层必须 `overflow-visible`

⛔ **常见 BUG（用户截图同款）**：


| ❌ Bad                                    | 后果                        | ✅ Fix                                                                           |
| ---------------------------------------- | ------------------------- | ------------------------------------------------------------------------------- |
| `<main className="... overflow-y-auto">` | 整页滚，下方卡片被挤出一屏，对照工作流断裂     | `<main className="flex-1 min-h-0 overflow-hidden">`，把 `overflow-y-auto` 下沉到面板正文 |
| 面板自己 `min-h-[600px]` / `h-[800px]` 写死高度  | 面板高度大于视口剩余空间，整页又被撑开       | 面板用 `flex-1 min-h-0`，让它**自动撑满剩余视口**                                             |
| 上下子面板都不写 `min-h-0`                       | 子面板 A 内容多时直接撑死子面板 B，B 看不见 | 父容器 `flex-col`，固定 A `shrink-0`、弹性 B `flex-1 min-h-0`                            |
| 面板正文直接堆内容，没有独立滚动层                        | 内容超长直接撑爆面板，整页跟着滚          | 面板正文加 `flex-1 min-h-0 overflow-y-auto`                                          |
| 面板 header 不加 `shrink-0`                  | 内容多时 header 被压缩、标题被挤变形    | 面板 header 永远 `shrink-0`                                                         |


⚠️ **混合横向 + 纵向**：横向是 `flex-row`，每个直接子项要么 `shrink-0 w-[Npx]`、要么 `flex-1 min-w-0`；纵向是 `flex-col`，子项要么 `shrink-0`、要么 `flex-1 min-h-0`——**横纵两套规则各自独立适用**。

---

### 3.4.1 白卡 / 面板标题栏防挤压（禁止标题竖排）

> 标题是板块语义锚点，优先级高于右侧 Tabs、按钮、状态说明。标题区只能横向可读，绝不允许因为面板窄而出现一字一行的竖状标题。

**适用信号**：白卡 header、面板 header、`Prompt Workspace`、`System Prompt`、`Conversation`、`Model Settings`、`Run Result`、左右栏/三栏 Playground 的每张卡片标题。

**标准结构**：

```jsx
<header className="shrink-0 flex items-start justify-between gap-3 px-6 py-4">
  <div className="min-w-[120px] flex-1">
    <FormTitle variant="level-2" title="Prompt Workspace" showDescription={false} />
  </div>

  {/* Tabs / Button：操作区可以换行，不能挤压标题 */}
  <div className="shrink-0 flex flex-wrap justify-end gap-2">
    <Tabs variant="segment" size="sm" items={[...]} />
  </div>
</header>
```

如果标题右侧有状态 / 数量 / 身份标签，Tag 不是操作区内容，必须贴在主标题右侧：

```jsx
<FormTitle
  variant="level-2"
  title="Conversation"
  titleSuffix={<Tag variant="grey" size="m">0 条消息</Tag>}
/>
```

`description` 是低频补充信息，默认隐藏。只有板块需要解释业务规则、使用方式、风险/空状态原因，或需求明确说要展示副标题/描述时，才同时传 `description` 与 `showDescription`。

**决策规则**：


| 场景                | 标题策略                                                    | 右侧内容策略                           |
| ----------------- | ------------------------------------------------------- | -------------------------------- |
| 正常白卡（宽度 ≥ 320px）  | `FormTitle variant="level-2"`，默认只显示主标题                  | 右侧按钮/Tabs `shrink-0`             |
| 有板块介绍/场景说明诉求      | `description="..." showDescription`，按 variant 放在标题下方或右侧 | 仍不能挤压标题                          |
| 标题旁有状态/数量 Tag     | 用 `titleSuffix={<Tag ... />}` 紧跟主标题，位于 description 前    | 不放进右侧操作区                         |
| 窄面板（宽度 < 320px）   | 保持默认无描述，只保留短标题                                          | Tabs/按钮移到下一行或减少选项文案              |
| 极窄侧栏（宽度 < 240px）  | 标题仍横排，必要时缩短标题文案                                         | 不放长 Tabs；改为正文区切换或更多菜单            |
| 标题 + 多个 Tabs/按钮冲突 | 标题 `min-w-[120px] flex-1` 优先                            | 操作区 `flex-wrap justify-end`，允许换行 |


⛔ **禁止**：


| ❌ Bad                                                       | 问题                  | ✅ Fix                                                                      |
| ----------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------- |
| `<div className="w-4 break-all">原始信息只读</div>`               | 标题一字一行，语义锚点失效       | `<FormTitle variant="level-2" title="原始信息" />`                             |
| `writing-mode: vertical-rl` / `text-orientation: upright`   | B 端卡片标题不使用竖排文字      | 只用横排标题                                                                     |
| 标题容器 `w-[16px]` / `max-w-[24px]`                            | 把标题压成竖条             | 标题区 `min-w-[120px] flex-1`                                                 |
| 右侧 Tabs/按钮过多仍 `nowrap`                                      | 挤压标题                | 操作区 `flex-wrap` 或迁移到正文顶部                                                   |
| `<FormTitle ... /><Tag>0 条消息</Tag>` 放在 `justify-between` 右侧 | 数量标签被误当成操作区，和标题语义断开 | `<FormTitle titleSuffix={<Tag variant="grey" size="m">0 条消息</Tag>} ... />` |


---

### 3.5 卡片内容区弹性控件（TextArea / 代码块 / JSON / 列表）

> **标题稳定，内容滚动**：白卡里的 `FormTitle` / Tabs / 操作按钮属于 header，永远 `shrink-0`；高度变化只发生在正文 body。正文里的主控件如果承载长内容，必须填满 body，并在控件内部滚动。

**适用信号**：System Prompt、Prompt Workspace、模型高级参数 JSON、请求参数、代码参数、JSON 编辑、代码编辑、工单详情、规则描述、日志、长列表、运行结果、消息流。

**通用结构（必须逐层成立）**：

```jsx
<section className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
  {/* 标题 / tabs / 操作区：稳定，不参与滚动 */}
  <header className="shrink-0 px-6 py-4">
    <FormTitle variant="level-2" title="System Prompt" description="使用 TFDS TextArea 重构编辑区。" showDescription />
  </header>

  {/* 正文 body：占满剩余高度，不自己滚时就给子控件提供高度边界 */}
  <div className="flex-1 min-h-0 overflow-hidden px-6 pb-6">
    <TextArea
      variant="code"
      fillHeight
      resize="none"
      className="flex-1 min-h-0 w-full"
      placeholder="请输入 System Prompt"
    />
  </div>
</section>
```

**TextArea 专项**：


| 场景                                                          | 正确高度策略                                                                                                   |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| 卡片/面板主编辑区（System Prompt、模型高级参数 JSON、请求参数、代码参数、JSON、代码、规则配置） | `variant="code" fillHeight resize="none" className="flex-1 min-h-0 w-full"`，带行号并填满正文区，长文本在 TextArea 内部滚动 |
| 卡片/面板主编辑区（工单详情、普通长文本）                                       | `fillHeight resize="none" className="flex-1 min-h-0 w-full"`，填满正文区，长文本在 TextArea 内部滚动                    |
| 普通表单备注 / 弹窗字段                                               | `minRows={3}` 或按语义选择 2-5 行，自然高度，不用 `fillHeight`                                                          |
| 模型 ID / URL / 单行起但可能很长                                      | `minRows={1}`，与 Input MD 对齐，必要时自然增高                                                                      |


⛔ **禁止**：


| ❌ Bad                                    | 后果                      | ✅ Fix                                |
| ---------------------------------------- | ----------------------- | ------------------------------------ |
| `<TextArea minRows={5} />` 直接放进高卡片       | TextArea 只占 5 行，下方大片空白  | 卡片主编辑区用 `fillHeight resize="none"`   |
| body 写 `overflow-y-auto`，TextArea 自己自然高度 | 滚动发生在整块正文，输入框边框/标题关系不稳定 | body `overflow-hidden`，TextArea 内部滚  |
| header 放进 `overflow-y-auto` body         | 滚动时标题被卷走                | header 拆出来并 `shrink-0`               |
| 父容器漏 `min-h-0`                           | 子控件无法拿到剩余高度，会撑爆卡片       | section/body/子控件逐层补 `flex-1 min-h-0` |


**代码 / Prompt 输入框专项**：

- System Prompt、模型高级参数 JSON、请求参数、代码参数、JSON、代码、SQL、Markdown、Agent 指令、规则配置等需要逐行定位的输入，统一使用 `<TextArea variant="code" />`。
- `variant="code"` 自带左侧行号、等宽字体、禁用拼写检查；不要手搓行号列，也不要用原生 `<textarea>`。
- 如果它是卡片主编辑器，仍必须叠加 `fillHeight resize="none" className="flex-1 min-h-0 w-full"`。
- 纯数值模型参数（Temperature / Top-P / Max Tokens / 阈值 / 权重）不属于本类，必须使用 `InputNumber`。

**判断口诀**：这块控件是不是这张卡片的“主要工作区”？是 → 子控件填满 body 并内部滚；不是 → 按自身内容自然高度。

---

## 4. 表格 / 长列表的滚动规则

### 4.1 表格

```jsx
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
  {/* 表头工具条：固定高度 */}
  <div className="shrink-0 px-6 py-4 flex items-center justify-between">...</div>

  {/* 表格本体：内部滚动（关键！） */}
  <div className="flex-1 min-h-0 overflow-auto">
    <Table ... />
  </div>

  {/* 分页：吸底 */}
  <div
    className="shrink-0 px-6 py-3"
    style={{ borderTop: '1px solid var(--color-border-default)' }}
  >
    <Pagination ... />
  </div>
</div>
```

**关键**：

- 表格父容器 `flex-1 min-h-0 overflow-auto`，让表格在数据多时**内部滚动**
- 横向太宽 → 由表格自身处理 `overflow-x: auto`
- ⛔ **禁止**：把表格直接放在没有高度约束的容器里（会撑成天高）

### 4.2 长列表（chat / log / feed）

最后一条消息距离输入框/底部至少 **16px** 安全间距：

```jsx
<div className="flex-1 min-h-0 overflow-y-auto pb-4">
  {messages.map(...)}
</div>
```

---

## 5. 输入框吸底（IM / Chat 场景）

**ChatInput 与 IMConversationPattern 组合**：

```jsx
<main className="flex-1 min-h-0 flex flex-col overflow-hidden">
  {/* 消息区：自身滚动 */}
  <div className="flex-1 min-h-0 overflow-hidden">
    <IMConversationPattern messages={messages} />
  </div>

  {/* 输入框：吸底，绝不进入 overflow 容器 */}
  <div className="shrink-0 px-6 pb-4">
    <ChatInput ... />
  </div>
</main>
```

⛔ **禁止**：把 ChatInput 包进 `overflow-y-auto` 容器里（会被滚走）

---

## 6. 页面宽度策略

### 6.1 默认行为（全宽撑满，禁止收窄）

**B 端页面必须撑满浏览器全宽**——灰底贴边、白卡距边缘 16px、卡与卡之间 8px（详见 §1.2 三层间距金字塔）。

⛔ **禁止一切导致左右大空白的写法**：


| 禁止写法                                                              | 后果                 |
| ----------------------------------------------------------------- | ------------------ |
| 根容器 / main 加 `max-w-screen-xl` / `max-w-7xl` / `max-w-[1440px]` 等 | 页面被居中收窄，两侧死白/死灰大空白 |
| 根容器 / main 加 `mx-auto`                                            | 页面居中，两侧出现空白        |
| `main` 内套 `<div className="max-w-* mx-auto px-*">` 居中容器           | 内容被套层收窄，白卡不撑满      |
| `main` padding 用 `px-6` / `px-8` 代替 `px-4`                        | 白卡距浏览器边缘过大，看着像居中留白 |
| 白卡 div 自身加 `mx-4` / `mx-6` 等外边距                                   | 白卡被横向缩进，不贴框架       |


✅ 让页面在所有宽度下**撑满浏览器**（B 端工具产品宽屏要利用率）
✅ 内容区按 § 3「双栏 / 网格 / 自适应」规则反应

---

## 6.3 表单控件宽度决策（修复“Input 不满宽”高频问题）

> 这是一个**非常高频**的 AI 生成错误：在白卡/面板正文里放了 `Input`，但输入框只占一小段宽度，看起来像“没对齐/没撑满/不专业”。
>
> 根因：`Input` 根容器默认是 `inline-flex`，如果你不显式给它宽度，它会按内容收缩。**筛选栏**需要这种收缩/固定宽，但**板块正文的主字段**不需要。

### 6.3.1 决策表（必须按此选）


| 场景                                      | 正确宽度策略             | 写法                                                             |
| --------------------------------------- | ------------------ | -------------------------------------------------------------- |
| **筛选栏 / 工具条**（一行并排多个字段）                 | 固定宽（约 240/200/160） | `<Input style={{ width: 240 }} ... />`；或外层 `flex: '0 1 240px'` |
| **白卡正文 / 板块表单 / 单列编辑区**（主字段、名称、标题、单行配置） | **满宽**             | `<Input className="w-full" ... />`（父容器需 `min-w-0`）             |
| **双栏表单**（左/右两列各自为字段列）                   | 列内满宽               | 列容器 `flex-1 min-w-0`，字段 `w-full`                               |


### 6.3.2 满宽结构模板（推荐照抄）

```jsx
<div className="flex flex-col gap-4 min-w-0">
  <FormTitle variant="level-2" title="Prompt Workspace" />

  {/* 单列主字段：必须满宽 */}
  <div className="flex flex-col gap-2 min-w-0">
    <div className="text-sm font-medium text-blueGrey-800">标题</div>
    <Input className="w-full" placeholder="请输入标题" />
  </div>
</div>
```

> ⚠️ 自检：如果你看到 `Input` 出现在白卡正文里但没有 `w-full`（或没有明确 `width`），先按本节修复；不要用 margin/justify 去“摆正”，那只是掩盖问题。

### 6.4 横向控件组防溢出（Tabs / 筛选 / 操作区）

横向控件组的目标是“保持同一行流内可用”，不是“强行把所有内容完整铺开”。当空间不足时，优先局部滚动、自然换行或收进更多菜单，禁止撑破白卡或整页。

| 场景 | 正确策略 | 禁止写法 |
| --- | --- | --- |
| Tabs / segment | 保持 `<Tabs />` 默认 `overflow="auto"`；父级 `min-w-0 max-w-full overflow-hidden` | `w-fit` / `min-w-max` / `overflow-visible` / 固定大宽 |
| 筛选栏 | `flex items-center flex-wrap gap-2`；搜索约 240px，筛选控件 160-200px | `flex-nowrap` 强塞一行、每个控件 `w-full`、筛选行 `overflow-hidden` 裁切 |
| Header 操作区 | `shrink-0 flex flex-wrap justify-end gap-2`；超过 3-4 个动作时收进更多菜单 | 所有按钮固定一行并挤压标题 / Tabs |
| 表单参数网格 | `auto-fit + minmax(min(100%, 240px), 1fr)`，窄面板降单列 | 两列固定宽导致标签与控件重叠 |

### 6.2 例外：表单页可局部收窄

表单页/详情页**主标题区**可以**局部**加 `max-w-[1280px] mx-auto` 防止单行表单过长难读：

```jsx
<form className="max-w-[1280px] mx-auto px-6 py-8">
  {/* 表单字段 */}
</form>
```

⛔ **禁止**：把整页根容器加 `max-w`，那会让灰底页面在宽屏左右出现死白条。

---

## 7. 白卡嵌套层级规则（避免白叠白 / 灰叠灰）

> 在动手嵌套白卡之前，先回 §1.5 检查"这块内容是不是页面级 header / 工具条"——是的话**直接坐灰底**，根本不需要白卡。本节只规范"已经确定要包白卡"的内容应该选什么颜色。

层级颜色按"父子互斥"：


| 父容器                          | 子卡片用                        | 孙卡片用                        |
| ---------------------------- | --------------------------- | --------------------------- |
| 灰底页面（`--color-blueGrey-200`） | **白底** `Card color="white"` | **灰底** `Card color="grey"`  |
| 白底白卡                         | **灰底** `Card color="grey"`  | **白底** `Card color="white"` |
| 灰底 Card（二级灰）                 | **白底** `Card color="white"` | 一般不再嵌套                      |


⚠️ **嵌套深度建议 ≤ 3 层**，过深会丢失视觉层级，应改为 Tab/折叠结构。

⛔ **禁止**：

- ❌ 白底白卡（直接糊在一起，看不出层级）
- ❌ 任何子层用阴影区分（白卡禁止阴影）

---

## 8. 弹窗 / 抽屉 / 浮层尺寸规则


| 场景                    | 推荐尺寸                                  |
| --------------------- | ------------------------------------- |
| Confirm Dialog（确认/告警） | `width: 400-480px`                    |
| Form Dialog（短表单）      | `width: 560-640px`                    |
| Form Drawer（长表单）      | `width: 560px`（侧滑）                    |
| 详情抽屉                  | `width: 720px` 或 `min(720px, 60vw)`   |
| 复杂任务弹窗（含 Tab）         | `width: 880-960px`、`max-height: 80vh` |


**通用约束**：

- ✅ 弹窗内必须 `max-height: 80vh` + 内部 `overflow-y-auto`，否则在小高度屏会被截断
- ✅ 抽屉宽度优先用 `min(WIDTH, 90vw)`，避免在窄屏完全遮挡

---

## 9. Z-index 规范（统一 token 化）

**只允许使用以下 6 档**，禁止 `z-[999]`、`z-[9999]`：


| 层级           | z-index | Tailwind   | 用途                                   |
| ------------ | ------- | ---------- | ------------------------------------ |
| 默认           | 0       | `z-0`      | 页面内容                                 |
| 吸顶           | 10      | `z-10`     | Topbar / sticky 表头                   |
| Dropdown     | 20      | `z-20`     | 下拉菜单、Tooltip（普通页面层）                  |
| 抽屉           | 1000    | `z-[1000]` | drawer overlay + panel（panel 用 1010） |
| 弹窗           | 2000    | `z-[2000]` | dialog overlay + panel（panel 用 2010） |
| Toast        | 3000    | `z-[3000]` | 全局通知                                 |
| 弹窗内 Dropdown | 2050    | `z-[2050]` | 弹窗里的下拉/Tooltip（高于 panel）             |


**层级设计原则**：

- 同类组件之间隔 1000：避免互相干扰
- overlay vs panel 隔 10：让 panel 永远在 overlay 之上
- 弹窗内 dropdown 在 panel 之上 +40：保证可见

---

## 10. 滚动条样式

- ⛔ **不要**自定义滚动条样式（`::-webkit-scrollbar`），保持系统默认
- ✅ 如果设计稿要求美化滚动条，必须使用 TFDS 提供的全局工具类（`tfds-scrollbar-thin`，由 `theme.css` 注入）

---

## 11. 自适应自检 Checklist（生成完整页后必查）

把浏览器从 **1920px → 1280px → 1024px → 768px** 缩，逐步检查：


| 检查项               | 失败信号                                            | 修复                                                                                                                                             |
| ----------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 横向溢出              | 出现底部横向滚动条                                       | 父容器加 `min-w-0`、子项允许收缩                                                                                                                          |
| 整页滚动              | 页面整体上下滚（非内容区滚）                                  | 根容器改 `h-dvh + overflow-hidden`，内容区改 `flex-1 min-h-0 overflow-y-auto`                                                                           |
| 输入框被顶走            | ChatInput 滚出视口                                  | 输入框外层 `shrink-0`，不在 overflow 容器内                                                                                                               |
| 网格挤压              | 卡片缩到不可读                                         | grid 改 `auto-fit + minmax(min(XX, 100%), 1fr)`                                                                                                 |
| 双栏断裂              | 左栏被压成 0 宽                                       | 左栏 `shrink-0 w-[XXXpx]`，右栏 `flex-1 min-w-0`                                                                                                    |
| **白卡左右大空白**       | 白卡与浏览器边缘间距明显大于 16px，看着像居中留白；或 NavBar 右侧多出一段灰底空隙 | ① 通用页面检查 `main` padding 默认 `p-4`（不是 `p-2` / `px-6` / `px-8`）；② 基础页面框架检查是否使用 `PAGE_FRAME_CONTENT_MARGIN / PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN`；③ 删除 `main` 内的居中容器（`max-w-* mx-auto`）；④ 通用页面白卡 div 不加 `mx-*`，基础页面框架只用共享 margin token（详见 §1.2） |
| **内容区不撑全宽**       | 某层容器宽度跟随内容，没撑满父容器                               | 检查横向 `flex` 父容器有 `w-full` + `min-w-0`，各子项有 `flex-1 min-w-0`（弹性）或 `shrink-0 w-[Npx]`（固定），不能漏 `min-w-0`                                          |
| 横向大卡不可调宽          | 左右大白卡都写死宽度，用户无法按工作流分配空间                         | 按 §3.2 / §3.4 / §12.2 改造：辅助大卡外层容器提供宽度拖拽，主卡保持 `flex-1 min-w-0`，拖拽把手外层 `overflow-visible`；TagBar 自身不写 `resizable / onWidthChange`                                         |
| 表格行错乱             | 表格高度无限撑开                                        | 表格父容器 `flex-1 min-h-0 overflow-auto`                                                                                                           |
| 弹窗被裁              | 高度超出视口看不到底部                                     | 弹窗加 `max-height: 80vh + overflow-y-auto`                                                                                                       |
| 文字溢出              | 长字符把容器撑爆                                        | 文本节点加 `truncate` 或 `break-words`，父容器加 `min-w-0`                                                                                                |
| 白叠白               | 白卡里直接放白 Card                                    | 内层 Card 改 `color="grey"`                                                                                                                       |
| Tabs / 筛选栏 / 按钮组溢出 | 控件超出卡片右边界或把页面撑出横向滚动 | Tabs 保持默认 `overflow="auto"`；父级补 `min-w-0 max-w-full overflow-hidden`；筛选和操作区允许 wrap，超出动作收进更多菜单 |
| 密集内容弱对比 | 表格 / 表单 / 代码 / 日志放在渐变或透明背景上，文字发虚 | 改回稳定白底、组件指定代码背景或灰底 Card；AI 渐变只用于 AI 语义内容，不用于编辑和数据密集区 |
| 整页有外圈圆角           | 灰底没贴浏览器边                                        | 移除根容器 `rounded` 与 `border`                                                                                                                     |
| Playground 多面板整页滚 | 调参数得滚下去看结果 / 写 prompt 得滚回去看输入                   | 按 § 3.4 / § 12.7 改造：根容器 `h-dvh + overflow-hidden`、main `overflow-hidden`、面板正文 `flex-1 min-h-0 overflow-y-auto`                                 |
| 子面板互相挤压（A 撑死 B）   | 上下两块卡片其中一块完全看不见 / 高度不对                          | 父 `flex-col gap-4`（纵向叠放 16px）；定高块 `shrink-0`，弹性块 `flex-1 min-h-0` + 内部 `overflow-y-auto`                                                                  |
| 面板标题被挤变形          | 内容多时面板 header 被挤压成单行 / 标题被压缩                    | 面板 header 永远 `shrink-0`，标题区 `min-w-[120px] flex-1`，操作区 `shrink-0 flex-wrap`，面板正文 `flex-1 min-h-0 overflow-y-auto`                              |
| 标题竖排 / 逐字换行       | “原始信息只读”变成一字一行，或出现 `writing-mode` / `break-all` | 按 §3.4.1：删除窄宽度和竖排样式，标题使用 `<FormTitle />` 横排；窄面板 `showDescription={false}`，右侧 Tabs/按钮换行或移入正文                                                    |
| TextArea 不撑满卡片    | System Prompt 只占 3-5 行，下方大片空白，长文本滚动不在控件内        | 主编辑区按 §3.5：header `shrink-0`，body `flex-1 min-h-0 overflow-hidden`，TextArea 写 `fillHeight resize="none" className="flex-1 min-h-0 w-full"`     |
| 文字被卡片截断           | 标题描述 / 状态说明 / 字段说明右侧被切掉，既没换行也没省略号               | 按 GLOBAL_DESIGN_RULES §8：核心说明 `whitespace-normal break-words` 完整展示；表格/文件名/ID 等非核心长文本 `truncate/line-clamp` 时必须包 `<Tooltip content={fullText}>` |
| 页面 header 被错误包白卡  | 顶部出现一张和内容卡同等大小的"标题白卡"，主从混乱、气息感丢失                | 按 § 1.5：页面 header（标题 + 描述 + 主次操作）**直接坐灰底**，只有真正的工作区内容才包白卡                                                                                      |
| 白卡被灰描边圈死          | 多张白卡共屏时灰边互相干扰，气息感丢失，看着像旧式 admin 后台              | 删除最外层白卡上的 `border` / `outline` / `ring`——白卡靠"灰底 vs 白卡的明度差 + 12px 圆角"承担层级，⛔ 严禁加描边（详见 GLOBAL_DESIGN_RULES § 5.5）                                 |


---

## 12. 黄金骨架代码（拷贝即用）

### 12.1 标准列表页（单白卡）

适用：变量管理、任务列表、配置列表等。

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <Topbar />

  <main className="flex-1 min-h-0 flex flex-col p-4 gap-4 overflow-hidden">
    {/* 单一白卡 */}
    <div
      className="flex-1 min-h-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      {/* 标题栏 */}
      <div className="shrink-0 px-6 py-4">
        <FormTitle variant="level-1" title="变量管理" showDescription={false} />
      </div>

      {/* 筛选栏 */}
      <div
        className="shrink-0 px-6 py-3 flex items-center gap-3"
        style={{ borderTop: '1px solid var(--color-border-default)' }}
      >
        <Input placeholder="搜索变量名" style={{ width: 240 }} />
        <Select placeholder="状态" style={{ width: 160 }} options={...} />
      </div>

      {/* 表格（内部滚动） */}
      <div className="flex-1 min-h-0 overflow-auto">
        <Table ... />
      </div>

      {/* 分页 */}
      <div
        className="shrink-0 px-6 py-3"
        style={{ borderTop: '1px solid var(--color-border-default)' }}
      >
        <Pagination ... />
      </div>
    </div>
  </main>
</div>
```

### 12.2 双白卡分栏页

适用：MCP 工具管理、知识库分类、左侧维度筛选 + 右主表。

```jsx
const [leftCardWidth, setLeftCardWidth] = useState(260);

<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <Topbar />

  <main className="flex-1 min-h-0 flex flex-row overflow-hidden min-w-0" style={{ margin: PAGE_FRAME_CONTENT_MARGIN, gap: PAGE_FRAME_PANEL_GAP }}>
    {/* 左白卡：外层容器级拖拽调宽 */}
    <aside
      className="relative shrink-0 flex flex-col overflow-visible"
      style={{ width: `${leftCardWidth}px`, background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      <TagBar
        className="!border-r-0"
        style={{ width: '100%', background: 'transparent' }}
        collapsed={leftCollapsed}
        onCollapsedChange={setLeftCollapsed}
      />
      <div
        role="separator"
        aria-label="调整标签栏白卡宽度"
        className="absolute top-0 bottom-0 -right-[6px] z-20 w-3 cursor-col-resize"
        onPointerDown={handleLeftCardResizePointerDown}
      />
    </aside>

    {/* 右白卡：弹性 */}
    <section
      className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      {/* 主内容继续按列表页骨架 */}
    </section>
  </main>
</div>
```

### 12.3 IM 会话页

适用：客服会话、AI 对话、Copilot。背景必须先按场景判断：常规 IM / 客服进线 / 人工接待默认白色会话背景；AI 问答 / 执行进度 / Copilot / 流程跟进优先浅灰背景。气泡色随背景定：灰底用左灰右白，白底用左灰右绿或右 AI，禁止白底上叠无描边白气泡。

#### 12.3.1 左侧 ConversationList（可拖拽 & 可收起为纯头像）布局铁律

当页面存在多线程/多工单队列时，左侧用 `ConversationList` 承载对象切换；它支持拖拽调整宽度，并可收起为纯头像（宽度约 88px）。**收起时右侧主内容区必须自动撑宽**，这是 IM 工作台的硬体验门槛。

- ✅ 正确：外层用 **flex 两栏**，左栏 `shrink-0`，右栏 `flex-1 min-w-0`
- ⛔ 禁止：把左栏写成固定宽度网格列（如 `grid-cols-[400px_1fr]`）或外层固定 `w-[400px]`，否则会出现“内容缩了但列不缩”的大空白，右侧无法自适应扩展
- ✅ 若必须用 grid：左列必须是 `auto`（`grid-cols-[auto_1fr]`）
- ✅ 当 `ConversationList variant="card"` 作为客服工作台左侧卡片列表时，卡片消息区是右侧 `IMConversationPattern` 完整气泡流的缩小窗口：若右侧有 20 条可展示气泡，左侧卡片必须通过固定高度 + `overflow-y-auto` 内滚查看同一批 20 条气泡
- ⛔ 禁止把左侧卡片消息流当摘要：不要 `.slice()` 只取最后几条、不要只展示最近 3-4 条、不要改写成营销摘要或状态总结；左侧每条气泡的文案、角色和气泡颜色语义必须与右侧 IM 一一对应
- ✅ 角色/颜色映射必须保持同源：`user` → 左灰 / `incomingTone="grey"`，`bot` → AI 渐变 / `outgoingTone="ai"`，`agent` → 人工客服浅青 / `outgoingTone="default"`；左侧允许隐藏 `system` 提示、头像、时间或降低字号，但不允许减少气泡消息条数

推荐骨架（直接照抄）：

```jsx
<main className="flex-1 min-h-0 flex overflow-hidden">
  <aside className="shrink-0 h-full min-h-0 overflow-hidden">
    <ConversationList />
  </aside>

  <section className="flex-1 min-w-0 min-h-0 flex flex-col overflow-hidden">
    {/* 右侧主工作区：白卡/消息/表单/详情按各自骨架自滚动 */}
  </section>
</main>
```

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <Topbar />

  <main className="flex-1 min-h-0 flex flex-col overflow-hidden">
    {/* 消息区：自身滚动 */}
    <div className="flex-1 min-h-0 overflow-hidden">
      <IMConversationPattern messages={messages} />
    </div>

    {/* 输入框：吸底（不在滚动容器内） */}
    <div className="shrink-0 px-6 pb-4">
      <ChatInput value={value} onChange={setValue} onSend={handleSend} />
    </div>
  </main>
</div>
```

### 12.4 网格工作台 / 入口聚合首页（**仅** Dashboard 类）

适用：产品首页、功能入口、KPI 看板——**用户在这一屏只是堆叠浏览**，没有面板内编辑/输入/对照工作流。

⚠️ **何时不要用本骨架**（高频误用）：

- ❌ Model Playground、Prompt Editor、API 调试器、规则编辑器 → 改用 § 3.4 / § 12.7
- ❌ 任何"输入区 + 输出区 + 参数区"对照工作的页面 → 改用 § 3.4 / § 12.7

**判别口诀**：本骨架用 `<main className="... overflow-y-auto">` 让整页滚动；如果你的页面里**有任何一张卡片承担"工作区"角色（用户在面板内输入/编辑/调参/查看结果）**，整页滚就是错的，必须改用 § 3.4。

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <Topbar />

  <main className="flex-1 min-h-0 overflow-y-auto">
    <div className="flex flex-col gap-4 p-4">

      {/* KPI 区：自适应网格 */}
      <section
        className="grid gap-4"
        style={{
          gridTemplateColumns: 'repeat(auto-fit, minmax(min(240px, 100%), 1fr))',
        }}
      >
        {kpis.map(k => (
          <div
            key={k.id}
            className="flex flex-col gap-2"
            style={{
              background: 'var(--color-surface)',
              borderRadius: 'var(--radius-lg)',
              padding: 'var(--spacing-6)',
            }}
          >
            {/* KPI 卡内容 */}
          </div>
        ))}
      </section>

      {/* 功能入口区：自适应网格 */}
      <section
        className="grid gap-4"
        style={{
          gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))',
        }}
      >
        {entries.map(e => <Card key={e.id} color="white">{...}</Card>)}
      </section>
    </div>
  </main>
</div>
```

### 12.5 沉浸式详情/编辑器页（纯白底，非灰底分区）

适用：富文本编辑器、文档详情、Prompt 编排器、长篇配置页。

⚠️ **不是**所有 B 端页都是「灰底+白卡」；编辑器型页面应**沉浸白底**：

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-surface)' }}  /* ← 白底，不是灰底 */
>
  <Topbar />

  {/* 编辑器主区，无白卡分隔 */}
  <main className="flex-1 min-h-0 flex flex-row overflow-hidden">
    {/* 左侧大纲（可选，浅灰底） */}
    <aside
      className="shrink-0 w-[280px] overflow-y-auto"
      style={{ background: 'var(--color-blueGrey-100)' }}
    >
      {/* 大纲 */}
    </aside>

    {/* 中部内容：宽屏可加 max-w 防止单行过长 */}
    <section className="flex-1 min-w-0 overflow-y-auto">
      <article className="max-w-[960px] mx-auto px-8 py-10">
        {/* 富文本/编辑器 */}
      </article>
    </section>

    {/* 右侧元数据面板（可选） */}
    <aside
      className="shrink-0 w-[320px] overflow-y-auto"
      style={{ borderLeft: '1px solid var(--color-border-default)' }}
    >
      {/* 元数据/属性 */}
    </aside>
  </main>
</div>
```

### 12.6 NavBar + Topbar 双层导航

适用：产品级三段交叉布局（左侧 NavBar 固定 + 顶 Topbar + 主区）。

**先决条件**：

- 命中 `NavBar` 时，优先套这一类 app shell 骨架，而不是通用纵向页面骨架
- `NavBar` 始终在最左侧，`Topbar` 只能放在右侧主区顶部
- `NavBar` 不进白卡；白卡只出现在右侧主区内部

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-row overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  {/* 左侧 NavBar：固定宽，全高 */}
  <NavBar className="shrink-0" />

  {/* 右侧主区：纵向三段 */}
  <div className="flex-1 min-w-0 flex flex-col overflow-hidden">
    {/* 顶 Topbar */}
    <header className="shrink-0">
      <Topbar />
    </header>

    {/* 中部主内容 */}
  <main className="flex-1 min-h-0 flex flex-col p-4 gap-4 overflow-hidden">
      {/* 这里嵌套 § 12.1 / 12.2 / 12.4 任意骨架 */}
    </main>
  </div>
</div>
```

### 12.7 三栏 Playground / 多面板工作台（IDE 型）

适用：Model Playground、Prompt Editor、API 调试器、规则编辑器——多个独立工作区面板共享一屏，**整页绝不滚，每张面板独立全高 + 独立滚动**。

```jsx
<div
  className="h-dvh w-full min-w-0 flex flex-col overflow-hidden"
  style={{ background: 'var(--color-blueGrey-200)' }}
>
  <Topbar />

  {/* ✅ 页面 header：直接坐灰底，绝不包白卡（参照 § 1.5） */}
  <header className="shrink-0 px-4 pt-3 pb-2 flex items-center justify-between gap-3">
    <div className="flex items-center gap-3 min-w-0">
      {/* 可选：返回按钮 / 面包屑 */}
      <FormTitle
        variant="form"
        title="Model Playground"
        titleSuffix={<Tag variant="success" size="m">TFDS</Tag>}
        description="保持原始工作台分区不变，使用 TFDS 组件重构交互与布局容器"
        showDescription
      />
    </div>
    <div className="shrink-0 flex items-center gap-2">
      <Button variant="outline-black">查看源稿</Button>
      <Button variant="primary">导入信息</Button>
    </div>
  </header>

  {/* 主区横向三栏：左 Workspace + 中 Conversation + 右 Settings/Result（上下叠） */}
    <main className="flex-1 min-h-0 flex flex-row px-4 pb-4 gap-2 overflow-hidden min-w-0">

    {/* ── 左面板：Prompt Workspace ── */}
    <section
      className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      <header
        className="shrink-0 px-6 py-4 flex items-center justify-between"
        style={{ borderBottom: '1px solid var(--color-border-default)' }}
      >
        {/* ⚠️ 面板/卡内 section 标题永远用 FormTitle level-2，⛔ 不要手搓 div+h3 */}
        <FormTitle
          variant="level-2"
          title="Prompt Workspace"
          description="结构化信息支持组件化编辑。"
        />
        {/* 内容区 / 白卡内 Tabs 默认 SM；仅平台顶部 header 可用 MD/LG */}
        <Tabs variant="segment" size="sm" items={[...]} />
      </header>
      {/* 面板正文：标题固定；主编辑控件撑满剩余高度并在控件内部滚 */}
      <div className="flex-1 min-h-0 overflow-hidden px-6 py-4 flex flex-col gap-4">
        <section className="flex-1 min-h-0 flex flex-col overflow-hidden">
          <header className="shrink-0 pb-3">
            <FormTitle variant="level-2" title="System Prompt" description="使用 TFDS TextArea 重构编辑区。" showDescription />
          </header>
          <TextArea variant="code" fillHeight resize="none" className="flex-1 min-h-0 w-full" />
        </section>
      </div>
    </section>

    {/* ── 中面板：Conversation ── */}
    <section
      className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden"
      style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
    >
      <header className="shrink-0 px-6 py-4">
        <FormTitle
          variant="level-2"
          title="Conversation"
          titleSuffix={<Tag variant="grey" size="m">0 条消息</Tag>}
          description="消息区与底部输入区统一重构为 TFDS 会话交互风格。"
        />
      </header>
      <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-4">
        {/* IM/Chat 消息流 */}
      </div>
    </section>

    {/* ── 右栏：上下两个子面板 ── */}
    <aside className="shrink-0 w-[360px] flex flex-col gap-4 overflow-hidden">
      {/* 上：Model Settings — 高度跟随表单内容（shrink-0） */}
      <div
        className="shrink-0 flex flex-col overflow-hidden"
        style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
      >
        <header className="shrink-0 px-6 py-4">
          <FormTitle variant="level-2" title="Model Settings" showDescription={false} />
        </header>
        <div className="px-6 pb-6 flex flex-col gap-4">
          {/* InputNumber / Select / Switch 等参数 */}
        </div>
      </div>

      {/* 下：Run Result — 占满剩余空间（flex-1 min-h-0），内部独立滚动 */}
      <div
        className="flex-1 min-h-0 flex flex-col overflow-hidden"
        style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-lg)' }}
      >
        <header className="shrink-0 px-6 py-4">
          <FormTitle variant="level-2" title="Run Result" showDescription={false} />
        </header>
        <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-6">
          {/* 模型参数 / Input / Output / Assistant Tabs + JSON 展示 */}
        </div>
      </div>
    </aside>
  </main>
</div>
```

**自检要点**（按上到下逐层检查嵌套）：

```
h-dvh + overflow-hidden                      ← 根容器永不滚
  └─ main: flex-1 min-h-0 + overflow-hidden  ← 主区永不滚
       ├─ section（横向子项）
       │    └─ flex-1 min-h-0 min-w-0 + flex-col + overflow-hidden
       │         ├─ header: shrink-0
       │         └─ body: flex-1 min-h-0 overflow-y-auto   ← 真正滚动层
       └─ aside（横向子项）
            └─ shrink-0 w-[Npx] + flex-col + gap
                 ├─ 子面板 A（高度跟随内容）: shrink-0
                 └─ 子面板 B（撑满剩余）: flex-1 min-h-0 + overflow-hidden
                      ├─ header: shrink-0
                      └─ body: flex-1 min-h-0 overflow-y-auto
```

⛔ 如果上述任一层出现 `overflow-y-auto` 写在错误位置（如 main、section 头部、wrapper）或漏写 `min-h-0`，整页就会一起滚——这正是 Model Playground 高度问题的根因。
