Claudian 样式体系:模块化 CSS、确定性构建与 Obsidian 主题集成规范
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
本文以 Claudian 的 CSS 风格指南为核心,系统讲解该 Obsidian 插件中src/style/目录的模块化组织方式、根styles.css的确定性构建流程、按钮与表单控件的强制基线样式,以及与 Obsidian 宿主环境集成时的主题检测、层级和滚动布局陷阱。读完后,你可以准确地把新样式模块挂入构建链、按 BEM-lite 规范命名选择器,并避免在 Obsidian 宿主环境中出现样式泄漏。
样式体系总览:按 UI 所有权划分目录
风格指南明确规定:src/style/包含模块化 CSS,最终构建为根目录的styles.css。各目录的职责划分如下:
| 区域 | 职责 |
|---|---|
base/ | 变量、容器原语、动画,以及全局可见性行为 |
components/ | 可复用的聊天界面,如消息、输入框、标签页、导航、历史/会话管理、状态、上下文、引用和工具输出 |
toolbar/ | 输入区和 provider 选项控制组件 |
features/ | 与具名功能工作流耦合的样式,如上下文、diff、内联编辑、命令等 |
modals/ | 模态框专属布局 |
settings/ | 共享设置外壳和 provider 设置模块 |
accessibility.css | 跨功能的可访问性适配 |
index.css | 完整的模块清单与确定性构建顺序 |
指南给出一条关键决策原则:按 UI 所有权选择目录,而不是按选择器恰好出现在哪个屏幕上。共享的视觉原语放入components/,行为相关(behavior-specific)的选择器留在其所属的功能目录中。当前 src/style/index.css 实际注册了 40 余个模块,从base/variables.css开始,依次经过 components、toolbar、features、modals、settings,最后以base/visibility.css收尾。
架构边界:CSS 不是状态源
风格指南中的 Boundaries 一节定义了四条硬性边界,这也是理解整个样式架构的前提:
- TypeScript 拥有语义与生命周期状态。CSS 可以渲染 class 和 data 属性,但不能被当作状态转换的真相源(source of truth)。
- 功能样式不得把 provider 行为泄漏进共享选择器。provider 变体必须使用由 UI 配置显式提供的 provider class 或 data 属性。
- 对 Obsidian 宿主选择器的覆盖保持窄范围。当 Claudian 容器能够限定作用域时,禁止全局重定义宿主 class。
- 根
styles.css是生成产物,永远不要直接编辑它。
第 2 条在源码中可以得到印证:src/style/base/variables.css 为每个 provider 定义了独立的品牌色 token(如--claudian-brand-claude、--claudian-brand-codex、--claudian-brand-opencode等),并通过data-provider属性把别名 token--claudian-brand指向对应 provider 的值:
.claudian-container[data-provider="claude"] { --claudian-brand: var(--claudian-brand-claude); --claudian-brand-rgb: var(--claudian-brand-claude-rgb); }也就是说,provider 差异被封装在 CSS 自定义属性层面,共享组件只需引用--claudian-brand,无需感知具体 provider——这正是"provider 变体用显式 data 属性"这条边界规则的具体落地方式。
构建链:styles.css如何被生成
构建命令
指南的 Build Rules 一节给出了三条规则:
npm run build:css构建根styles.css;npm run dev和npm run build都会触发 CSS 构建;- 每一个新模块都必须注册进
index.css,否则 CSS 构建应当失败。
对照 package.json 中的 scripts 可以确认这一链路的实际形态:
build:css执行node scripts/build-css.mjs;dev被定义为npm run build:css && node esbuild.config.mjs,即每次启动 esbuild 监听前先构建 CSS;build执行node scripts/build.mjs production,该脚本内部同样会先调用 scripts/build-css.mjs 再运行 esbuild 生产构建。
构建产物会随插件一起被安装:esbuild.config.mjs 中的copyToObsidian插件在构建结束后,把main.js、manifest.json和styles.css三个文件复制到OBSIDIAN_VAULT环境变量指向的 vault 下的.obsidian/plugins/claudian目录——这说明根styles.css是插件分发的核心产物之一,"不要手改生成物"的规则由此更加严格。
构建脚本的工作机制
scripts/build-css.mjs 并非简单地拼接目录,而是实现了指南中"未注册模块必须让构建失败"的强制约束。其核心逻辑:
解析模块顺序(build-css.mjs 的
getModuleOrder):用正则/^\s*@import\s+(?:url\()?'"['"]\)?\s*;/gm扫描src/style/index.css,按@import出现顺序取出模块清单。index.css文件顶部也注释了这一约定:"CSS module order. scripts/build-css.mjs reads these @import lines."。逐模块拼接:每个被引入的文件会以注释头(模块相对路径)包裹后追加到输出,最终在文件头部写入
/* Claudian Plugin Styles */ /* Built from src/style/ modules */标识,并写入根styles.css。三类错误都会使构建以退出码 1 失败(build-css.mjs):
- 非法
@import:路径逃出src/style/目录(解析后以..开头)或不以.css结尾; - 缺失文件:
@import指向的文件不存在; - 未登记文件:脚本递归遍历
src/style/下所有.css文件(排除index.css),任何未被index.css引入的文件都会触发 "Unlisted CSS files" 错误。
- 非法
第三条是整个体系的关键防线:新增一个.css文件却忘记注册,构建不会静默忽略,而是直接报错。这保证了index.css与实际文件集合永远一致。
确定性顺序与无!important约定
index.css 的结尾两行值得注意:
/* Utilities imported last to preserve cascade priority without importance flags */ @import "./base/visibility.css";可见性工具类被刻意放在所有模块之后,靠级联顺序(cascade order)而非优先级标记取胜。这与 stylelint.config.mjs 中唯一启用的规则declaration-no-important: [true]形成呼应——项目级 lint 直接禁止!important,开发者只能通过调整模块注册顺序来控制层叠优先级。这一约束配合npm run lint:css(即stylelint "src/style/**/*.css")在 CI 和日常开发中持续生效。
命名与 token 约定
指南的 Conventions 一节规定:
- Claudian 自有 class 一律使用
.claudian-前缀; - 共享的 Obsidian 宿主选择器和通用状态 class 可以保留无前缀形式;
- 推荐BEM-lite命名:
.claudian-{block}、.claudian-{block}-{element}、.claudian-{block}--{modifier}; - 使用 Obsidian 的 CSS 变量,如
--background-*、--text-*、--interactive-*,让样式自动适配宿主主题; - 代码块使用
var(--font-monospace)。
从 src/style/base/variables.css 的结构还可以看到 token 的层次:.claudian-container作用域内定义品牌色、错误色(--claudian-error)、压缩提示色(--claudian-compact)等语义 token,并按 provider 拆分为独立命名 token 后通过data-provider别名聚合。body.theme-light .claudian-container下还有一组浅色主题的覆写(例如--claudian-brand-codex在浅色主题下从#d0d0d0变为#000000),说明深色/浅色适配是通过重定义 token 完成的,而不是在组件里写:root级别的主题分支。
基础元素规则:按钮与表单控件的强制基线
指南的 Specific Element Rules 一节要求:所有 Claudian 自有的下列元素实例都必须使用规定的基线样式;偏离只允许通过显式的语义修饰符或 surface 修饰符实现,选择器顺序、嵌套层级和继承自 Obsidian 的样式都不构成豁免。
按钮
完整规则继承自指南:
- 所有按钮静止态使用
border: 0、background: transparent、box-shadow: none、color: var(--text-muted); - Hover 与
focus-visible使用color: var(--text-normal),同时保持无边框、透明背景、无 box shadow;如需填充式(filled)hover 或 focus 表面,必须使用显式修饰符; - 禁用态按钮使用
color: var(--text-faint)和cursor: default,且不得保留任何 hover、focus 或 active 强调; - 按钮内 SVG 继承
currentColor;其宽高由按钮的基线选择器声明,需要不同图标尺寸时必须使用显式修饰符; - 必须把完整的基线样式应用到 Claudian 按钮 class 的 hover、focus、active、disabled 全部选择器上,确保 Obsidian 无法在任何状态下恢复原生按钮外观(native button chrome)。
最后一条点明了规则动机:Obsidian 宿主 CSS 可能给button元素应用默认样式,如果只写静止态基线,交互态下宿主样式会"回潮",因此在每个状态选择器上重复声明基线是强制要求,而非冗余。
输入框与文本域
- 所有独立(standalone)input/textarea 使用
min-width: 0、border: 1px solid var(--background-modifier-border)、background: var(--background-modifier-form-field)、box-shadow: none、color: var(--text-normal)以及继承的 UI 字体; - Hover 保持基线边框、背景与 box shadow;Focus 使用
outline: none和border-color: var(--interactive-accent),且不得引入浏览器或 Obsidian 的 box shadow; - Placeholder 使用
color: var(--text-muted);禁用控件使用color: var(--text-faint)和cursor: default; - 嵌入在包装器(wrapper)中的 input/textarea 在所有交互状态下使用
border: 0、background: transparent、box-shadow: none;边框、背景、圆角和焦点处理完全归包装器所有; - textarea 默认
resize: none和overflow-y: auto;需要可拖拽调整的 textarea 必须使用显式修饰符并限定尺寸; - Error、warning、只读及语义模式处理必须通过显式修饰符或作用域内的自定义属性实现,并且要覆写所有受影响的交互状态。
Obsidian 集成陷阱(Gotchas)
指南末尾的 Gotchas 一节记录了四条与宿主环境直接相关的坑:
- 主题检测:Obsidian 使用
body.theme-dark和body.theme-light标识当前主题。主题相关的 token 覆写(如 variables.css 中的body.theme-light .claudian-container选择器)都依赖这一约定。 - 模态框层级:模态框的
z-index必须大于1000才能盖在 Obsidian UI 之上。 - 会话管理器布局的作用域:持久会话管理器的布局规则必须限定在
.claudian-session-sidebar或.claudian-wide-session-layout之下;单面板历史菜单虽然共享条目原语(item primitives),但必须保留自己的尺寸、tab 状态标签和操作按钮。 - 滚动所有权与
min-height: 0:会话管理器的 pinned 列表与会话列表是相互独立的滚动主体(independent scroll owners)。必须让min-height: 0沿 flex 祖先链保持传递,否则 sticky 头部和有限高度区域会裁剪(clip)或叠压(overlap)内容。这是 flex 布局中经典的滚动容器陷阱:flex 子项默认的min-height: auto会阻止其收缩到内容高度以下,导致内部滚动失效。
验证与维护路径
整条风格指南不是仅靠约定执行,而是有多层可验证的机制兜底:
- 构建期:scripts/build-css.mjs 对未登记模块、非法导入和缺失文件三类问题硬失败,见上文构建链一节;
- Lint 期:package.json 中
lint:css脚本执行stylelint "src/style/**/*.css",且 stylelint.config.mjs 启用了declaration-no-important,从工具层面保证"无!important、靠级联顺序控优先级"的约定; - 测试期:tests/integration/build/build.test.ts 以集成测试验证构建脚本转发参数时不会把参数当作 shell 命令执行(防止命令注入),保证构建链路在自动化环境下可信;
- 入口约定:src/style/CLAUDE.md 通过
@AGENTS.md引用形式指向 src/style/AGENTS.md,即本指南全文,供编码代理在该目录下工作时加载同样的样式约束。
小结
Claudian 的样式体系可以用三句话概括:目录按 UI 所有权划分、index.css是唯一的模块注册表且未登记即构建失败、CSS 只负责渲染而状态永远归 TypeScript 所有。在此之上,按钮与表单控件的强制基线、.claudian-前缀与 BEM-lite 命名、data-providertoken 别名机制,以及针对 Obsidian 主题检测、模态框z-index和 flex 滚动链的专项注意事项,共同构成了一套可以在 Obsidian 宿主环境中长期维护的样式规范。新增样式模块时的标准动作是:在正确的所有权目录下创建文件,在 index.css 中按语义位置注册@import,运行npm run build:css与npm run lint:css确认通过。
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考