关于 joelclaw-web
joelclaw-web 是 joelclaw.com Next.js 应用的维护指南,涵盖内容管理、发布、设计和基础设施页面的安全要求,供网站贡献者使用。它以 Convex 作为运行时内容来源,并强调谨慎处理公开页面中的基础设施信息。
主要功能
- 01以 Convex 为首要内容来源
- 02文章草稿与发布流程
- 03内容重新验证与检查
- 04网站设计和导航规范
- 05隐藏基础设施敏感信息
适用场景
- 01撰写或发布文章
- 02更新 ADR 和发现内容
- 03维护网络状态页并避免泄露私密信息
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ joelhooks/ joelclaw --skill joelclaw-web安装后在 Claude Code 中输入/joelclaw-web确认已加载。
附带文件agents/openai.yamlassets/small-logo.svg
详细介绍
joelclaw.com Web 应用
apps/web/ 中的 Next.js 应用,位于 joelclaw monorepo(~/Code/joelhooks/joelclaw/)内。
推送到 main 时部署到 Vercel。深色主题,简约、偏系统风格的美学。
OPSEC 规则
绝不要在公开页面上暴露真实基础设施详情。
- 节点/主机名:使用 Stephen King 宇宙中的别名,绝不要使用真实的 tailnet 主机名
- 端口:不要公开任何服务的端口号
- 用户名:从服务标识符中移除
com.joel.或类似前缀 - IP/子网:绝不显示真实 IP 地址或 CIDR 范围
- 网络设备品牌/型号:使用泛称(例如“NAS”,而不是“Synology DS1821+”)
- Tailscale:可以提及它是网状 VPN 产品,但不要透露 tailnet 名称或节点 IP
公开图表使用通用角色名称,例如控制平面、归档、开发客户端、中继和路由器。不要将真实主机名伪装成虚构别名后公开;私有拓扑边界仍然适用。
内容模型(Convex 优先)
规范的运行时内容存储在 Convex contentResources 中:
article:<slug>(type = article)adr:<slug>(type = adr)discovery:<slug>(type = discovery)
apps/web/content/ 下的文件系统内容仅用于播种/回填,并非运行时数据源。
运行时读取策略:
apps/web/lib/posts.ts→ Convex 优先的文章(article:*)apps/web/lib/adrs.ts→ Convex 优先的 ADR(adr:*)apps/web/lib/discoveries.ts→ Convex 优先的发现内容(discovery:*)apps/web/lib/convex-api.ts→ 已检查的面向 Web 的 Convex 函数契约,以及运行时anyApi代理- 绝不要添加指向同级私有
joelclaw-api检出目录的link:依赖。Vercel 只会克隆此仓库。 - 可选的本地备用方案(仅限非生产环境):
JOELCLAW_ALLOW_FILESYSTEM_POSTS_FALLBACK=1(文章)JOELCLAW_ALLOW_FILESYSTEM_CONTENT_FALLBACK=1(ADR/发现内容)
文章字段仍与 MDX frontmatter 结构保持一致:
---
title: "Post Title"
type: "article" | "essay" | "note" | "tutorial"
date: "2026-02-19T11:00:00" # ISO datetime, NOT just date
updated: "2026-02-19T14:30:00" # optional, bumps sort position
description: "One-liner for cards and meta"
tags: ["tag1", "tag2"]
draft: true # optional, hides from prod
source: "https://..." # optional, for video-notes
channel: "Channel Name" # optional, for video-notes
duration: "00:42:02" # optional, for video-notes
---
排序:文章按 updated ?? date 降序排列。使用完整的 ISO 日期时间(而非单独的日期),以确保排序确定。设置 updated 会将文章移至顶部,但不会更改其原始发布日期。
Slug:根据 Convex 中的 fields.slug 派生(resourceId = article:<slug>)。
硬触发工作流
write article about X
- 根据标题/主题生成 slug。
- Upsert
contentResources,设置resourceId = article:<slug>、type = "article",将完整 MDX 正文写入fields.content,并设置fields.draft = true。 - 将
fields.date设为当前 ISO 时间戳。 - 返回 slug 和草稿预览链接(如果开发环境中草稿可见,则链接为
/<slug>)。
publish article <slug>
- 从 Convex 读取
article:<slug>。 - Patch/upsert,设置
fields.draft = false和fields.updated = now。 - 通过
POST /api/revalidate重新验证所有受影响的页面:- 标签:
post:<slug>、article:<slug>、articles - 路径:
/、/<slug>、/<slug>.md、/<slug>/md、/feed.xml、/sitemap.md
- 标签:
- 验证
/、/<slug>、/<slug>.md和/feed.xml均包含已发布的文章。
媒体嵌入
当 YouTube 视频能提供背景信息时,务必在 /cool 发现内容和文章中嵌入视频。使用 <YouTube id="VIDEO_ID" /> MDX 组件(.mdx 文章和 .md 发现内容均可使用)。从 URL 中提取视频 ID(youtube.com/watch?v=VIDEO_ID)。将嵌入内容放在相关章节靠前的位置,位于文字讨论之前。
写作风格
撰写正文时使用规范的 joel-writing-style skill。主要特点:直接;只有观点确实属于 Joel 时才使用第一人称;策略性地使用脏话;段落简短;强调内容加粗;对话式但具有技术性。绝不要使用企业套话,也绝不要捏造 Joel 的信念或哲学。
设计系统
- 主题:深色(
bg-[#0a0a0a]),中性灰,--color-claw: #ff1493(亮粉色点缀) - 字体:Geist Sans(正文)、Geist Mono(代码/数据)、Dank Mono(带连字的代码块)
- 内容宽度:
max-w-2xl(672px)——特意设得较窄,便于阅读 - 页眉:单行布局——左侧为爪子图标和“JoelClaw”,右侧为导航链接和搜索。页眉中不显示标语。
- 导航项:Writing(
/)、Cool(/cool)、ADRs(/adrs)、Network(/network) - 当前导航项:当前项显示为白色文本,非当前项显示为 neutral-500,通过
usePathname()检测 - 搜索:使用 pagefind 的 ⌘K 对话框,带有按类型区分的图标/徽章
- 移动端:通过
MobileNav组件显示全屏覆盖式导航 - 代码块:Catppuccin Macchiato 主题,rehype-pretty-code
- 边注:Tufte 风格的 CSS 边注(纯 CSS,无 JS)
关键文件
| 文件 | 用途 |
|---|---|
app/layout.tsx | 根布局、字体、元数据、页脚 |
app/page.tsx | 首页(文章列表) |
app/[slug]/page.tsx | 文章详情页 |
app/adrs/page.tsx | ADR 列表 |
app/adrs/[slug]/page.tsx | ADR 详情(移除 H1,避免标题重复) |
app/cool/page.tsx | Cool/发现内容列表 |
app/network/page.tsx | 基础设施状态页 |
components/site-header.tsx | 带当前导航项标记的页眉(客户端组件) |
components/mobile-nav.tsx | 移动端覆盖式导航 |
components/search-dialog.tsx | ⌘K 搜索 |
lib/posts.ts | 从 Convex 加载文章(article:*) |
lib/adrs.ts | 从 Convex 加载 ADR(adr:*) |
lib/discoveries.ts | 从 Convex 加载发现内容(discovery:*) |
lib/convex-api.ts | 可部署的运行时代理及类型化的 Convex 公共接口 |
lib/constants.ts | 站点名称、URL、标语 |
lib/claw.ts | 爪子图标的 SVG 路径 |
ADR 显示规则
- ADR 的运行时数据源为 Convex(
contentResources,其中resourceId = adr:<slug>) - Vault 同步仍会更新
apps/web/content/adrs/下的仓库快照 - 使用以下命令将项目快照写入 Convex:
bun scripts/seed-adrs-discoveries.ts - 详情页(
app/adrs/[slug]/page.tsx)会移除 Markdown 内容中的 H1,因为页面已带 ADR 编号前缀显示标题 - 正则表达式:
content.replace(/^#\s+(?:ADR-\d+:\s*)?.*$/m, "").trim()
添加新文章
- 在 Convex 中起草(
contentResources.upsert,resourceId = article:<slug>,draft: true)。 - 在
date字段中使用 ISO 日期时间。 - 如需添加图片,将图片放入
apps/web/public/images/<slug>/,并在 MDX 中通过/images/<slug>/...引用。 - 将
draft设为false以发布,然后重新验证标签和路径(post:<slug>、article:<slug>、articles、/、/<slug>、/<slug>.md、/<slug>/md、/feed.xml、/sitemap.md)。 - 验证路由、Markdown 对应页、首页和 Feed 的内容是否一致。
回填脚本:
scripts/seed-articles.ts:用于文章资源scripts/seed-adrs-discoveries.ts:用于 ADR 和发现内容资源
Network 页面
Network 页面(app/network/page.tsx)使用别名显示真实基础设施。更新时:
- 检查实际系统状态(
kubectl get pods、tailscale status、launchctl print等) - 遵守 OPSEC 规则——为所有主机名设置别名,并移除端口/IP/用户名
- 将数据数组放在文件顶部,便于更新
- 状态圆点:绿色(Online),带有 ping 动画;黄色(Idle);灰色(Offline)