scratchpad
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseScratchpad (internal test-bed) — agent-first
Scratchpad(内部测试床)——Agent友好型
Bootstrap an operator-only scratchpad: one internal page inside the real app
where the user looks at and fiddles with their own work-in-progress — judging it
by taste and eye, not by assertion. You read the repo, detect its
conventions, and write the gate, route, and first sections tailored to that
stack. The whole point is that it reuses the real code, never a fork of it,
so what the user eyeballs is exactly what ships.
快速搭建仅限操作人员使用的Scratchpad:真实应用内的一个内部页面,供用户查看和调试正在开发的内容——通过视觉和主观判断,而非断言校验。你将读取代码仓库、检测其约定,并针对该技术栈编写权限校验、路由和初始分区。核心优势在于它复用真实代码,绝不使用副本,因此用户看到的内容与最终发布的完全一致。
What a scratchpad actually is
Scratchpad的本质
A single internal page inside the real app for judging your own work-in-progress
— not a public feature, not a test suite, not a separate tool like
Storybook. It's the in-app equivalent of a workbench: you keep it because the
things you want to judge only render correctly inside the real app (real auth,
real theme tokens, real data, real link-building, real rendering pipeline), and
you want to judge them by taste and by eye.
It answers questions a test can't:
- "Does this look right?" — a generated OG/social card, a rendered email, an avatar, an animated micro-interaction, an empty state. Frame it at its true size and just look.
- "Which variant do I like?" — every theme/palette/size side by side, or live sliders you drag until it feels right. Judgment, not correctness.
- "Is this config wired up?" — an env-readiness panel, a "what does the service account actually see" debug dump, the raw output of a matcher before a threshold is applied.
- "Does this flow really work end-to-end?" — a button that calls the real library function with placeholder data and shows what comes back, so you exercise the pipeline without going through the whole product UI or waiting on a real external event.
The defining trait: it reuses the real code, never a fork of it. You import
the same components the real pages use, call the same library function production
calls, hit the same rendering route. The moment a scratchpad reimplements the
thing it's previewing, it has started lying to you.
真实应用内的单个内部页面,用于判断正在开发的内容——不是面向公众的功能,不是测试套件,不是Storybook这类独立工具。它相当于应用内的工作台:之所以需要它,是因为某些内容只有在真实应用环境中才能正确渲染(真实认证、真实主题令牌、真实数据、真实链接构建、真实渲染流水线),而你需要通过视觉和主观感受来判断它们。
它能解答测试无法回答的问题:
- 「这个看起来对吗?」——生成的OG/社交卡片、渲染的邮件、头像、动画微交互、空状态。以真实尺寸展示,直接查看即可。
- 「我更喜欢哪个变体?」——将所有主题/调色板/尺寸并排展示,或使用实时滑块调整到满意状态。这是主观判断,而非正确性校验。
- 「这个配置是否已正确连接?」——环境就绪面板、「服务账户实际能看到什么」的调试信息、阈值应用前匹配器的原始输出。
- 「这个流程真的能端到端运行吗?」——一个按钮,使用占位数据调用真实的库函数并展示返回结果,让你无需遍历整个产品UI或等待真实外部事件即可测试流水线。
核心特征:复用真实代码,绝不使用副本。你导入真实页面使用的相同组件,调用生产环境使用的相同库函数,访问相同的渲染路由。一旦Scratchpad重新实现了它要预览的内容,它就开始提供错误信息了。
When to reach for one
适用场景
- You keep spinning up throwaway routes/scripts to look at one visual, then deleting them. Give them a permanent home instead.
- A thing renders differently in isolation (Storybook, a static mock) than in the real app — so isolation lies. The scratchpad lives in the app.
- You're tuning something by feel — spacing, motion timing, a color ramp, a match threshold — and need a tight look → tweak → look loop.
- You want to trigger a real side-effecting pipeline (an email round-trip, a webhook handler, a Slack alert) on demand with fake data, without waiting for the real trigger.
- 你不断临时创建路由/脚本查看某个视觉效果,然后删除它们。不如给这些内容一个永久的家。
- 某个内容在隔离环境(Storybook、静态模拟)中的渲染效果与真实应用不同——隔离环境会提供错误信息。Scratchpad存在于应用内部。
- 你正在凭感觉调整某些内容——间距、动画时长、色彩渐变、匹配阈值——需要一个紧凑的查看→调整→查看循环。
- 你想按需触发真实的有副作用的流水线(邮件往返、Webhook处理器、Slack告警),使用假数据,无需等待真实触发条件。
When NOT to (say so if the user asks for these)
不适用场景(用户询问时需明确说明)
- Anything users should see. It's operator-only, forever.
- Assertions. If the answer is pass/fail and machine-checkable, write a test instead. A scratchpad is for the judgments a test can't make.
- Load-bearing admin operations. If you find yourself doing real production work from it (issuing refunds, editing live records), that's an admin panel — build that properly with audit trails. The scratchpad is for looking and fiddling; its writes target dev/dummy data.
- 任何用户可见的内容。它永远仅限操作人员使用。
- 断言校验。如果答案是通过/失败且可机器校验,请编写测试用例。Scratchpad用于测试无法完成的主观判断。
- 核心管理操作。如果你发现自己通过它执行真实的生产工作(处理退款、编辑实时记录),那应该是管理面板——请正确构建带审计追踪的管理面板。Scratchpad仅用于查看和调试;其写入操作仅针对开发/虚拟数据。
Bootstrap workflow
快速搭建流程
Run these in order. Gate before anything else — the page exposes internal
previews, debug dumps of real data, and buttons that call real code, so it must
be locked down before it renders a single byte of data.
按以下顺序执行。先做权限校验——该页面暴露内部预览、真实数据的调试信息,以及调用真实代码的按钮,因此在渲染任何数据之前必须锁定访问权限。
1. Detect the stack
1. 检测技术栈
Answer these from the repo (grep/read, don't assume). The Framework mapping
table near the end translates each answer into concrete syntax.
- How is a page/route defined? (file-based router, a route registry, a controller + template, a single-page app view.)
- What's the existing auth / session helper? Find how other protected pages
get the current user (e.g. a /
requireUser()/getSession()helper, middleware, or a decorator). Reuse it — do not invent a new one.currentUser() - Where are env vars read, and what's the "is production" signal?
(,
NODE_ENV,APP_ENV, a settings module, etc.)import.meta.env.PROD - How does this stack keep a view always-fresh / uncached? So edits to the thing being previewed show up on a plain refresh.
- How does it mark code as server-only (never bundled to the client), if it's a stack where that distinction exists?
- What's the static/asset dir, if a section needs to serve a request-scoped preview (e.g. an image route).
从代码仓库中获取以下信息(通过搜索/读取,不要假设)。末尾的框架映射表会将每个答案转换为具体语法。
- 页面/路由如何定义?(基于文件的路由、路由注册表、控制器+模板、单页应用视图)
- **现有认证/会话助手是什么?**找到其他受保护页面如何获取当前用户(例如/
requireUser()/getSession()助手、中间件或装饰器)。复用它——不要自行开发新的认证逻辑。currentUser() - 环境变量在哪里读取?「是否为生产环境」的信号是什么?(、
NODE_ENV、APP_ENV、设置模块等)import.meta.env.PROD - **该技术栈如何保持视图始终最新/不缓存?**以便预览内容的修改在刷新后立即显示。
- 如果该技术栈存在前后端代码区分,如何标记代码为仅服务器端(永远不打包到客户端)?
- **静态/资源目录在哪里?**如果某个分区需要提供请求作用域的预览(例如图片路由)。
2. Idempotency check
2. 幂等性检查
Look for an existing scratchpad route ( and check the router). If one already exists, don't
rebuild the scaffold — add a section to its array (see step 5) and
stop. Only build the scaffolding (steps 3–4, 6) when there is none.
grep -ri "scratchpad" --include=*.{ts,tsx,js,jsx,py,rb,go}sections查找现有Scratchpad路由(并检查路由配置)。如果已存在,不要重新构建脚手架——向其数组添加一个分区(见步骤5)即可停止。仅当不存在时才构建脚手架(步骤3–4、6)。
grep -ri "scratchpad" --include=*.{ts,tsx,js,jsx,py,rb,go}sections3. Build the access gate FIRST
3. 首先构建访问权限校验
Put the rule in exactly one place and import it everywhere (the page's
redirect and every request-scoped preview route). Open in local dev; locked to an
operator allowlist in production; checked server-side and kept off the client
bundle. See The access gate below for the full pattern and rules. Do this
before the route renders any data.
将规则放在唯一位置并在所有地方导入(页面的重定向逻辑和每个请求作用域的预览路由)。本地开发环境开放访问;生产环境仅限允许列表中的操作人员访问;在服务器端校验,且不打包到客户端。查看下方的访问权限校验获取完整模式和规则。在路由渲染任何数据之前完成此步骤。
4. Create the route + section switcher
4. 创建路由 + 分区切换器
One route (e.g. ), structured as a section switcher: a sidenav
or tabs that flips between independent sections, route-driven by a query param
() so a view is linkable and survives refresh. The page holds a
array of ; the switcher just shows/hides the
active one. Render the route always-fresh (no cache) so edits show on
refresh. See Minimal scaffold below.
/scratchpad?tab=<id>sections{ id, label, node }一个路由(例如),结构为分区切换器:侧边栏或标签页,在独立分区之间切换,通过查询参数()驱动路由,以便视图可链接且刷新后保持状态。页面包含一个数组,每个元素为;切换器仅显示/隐藏当前激活的分区。路由渲染设置为始终最新(无缓存),以便修改在刷新后立即显示。查看下方的最小脚手架。
/scratchpad?tab=<id>sections{ id, label, node }5. Seed 1–2 real sections
5. 初始化1–2个真实分区
Don't start empty. Pick from what the user is actually building right now (see
What to seed it with). For momentum, start with one visual preview and
one config/readiness panel — the two cheapest, highest-signal archetypes —
then add a variant board or live tester as the need appears. Each section imports
the real components/libs, not copies.
不要从空页面开始。根据用户当前正在构建的内容选择(见初始化内容建议)。为了快速启动,先添加一个视觉预览分区和一个配置/就绪面板分区——这两个是成本最低、信号最强的类型——然后根据需要添加变体面板或实时测试器。每个分区都导入真实组件/库,而非副本。
6. Add the dev-only entry link
6. 添加仅开发环境可见的入口链接
Reach the scratchpad from a dev-only link strip that renders nothing in
production — a tiny pinned corner element, not a nav menu item. No public entry
point ever.
通过仅在开发环境显示的链接条访问Scratchpad——一个固定在角落的小元素,而非导航菜单项。永远不要有公开入口。
7. Verify
7. 验证
Load it in the running dev app (use the project's dev server, or the or
skill if present). Confirm:
/run/verify- A seeded section renders with real data/components.
- The gate redirects/404s when you simulate a non-operator (e.g. temporarily return a non-allowlisted email, or unset the dev signal) — it must fail closed, not render-then-hide.
- Any request-scoped preview route also enforces the gate.
Then report to the user what you scaffolded, which sections you seeded, and the
one-liner for adding the next section.
在运行的开发应用中加载它(使用项目的开发服务器,或现有/技能)。确认:
/run/verify- 初始化的分区使用真实数据/组件渲染。
- 当模拟非操作人员时(例如临时返回不在允许列表中的邮箱,或关闭开发环境信号),权限校验会重定向/返回404——必须默认拒绝访问,而非渲染后隐藏。
- 任何请求作用域的预览路由也会执行权限校验。
然后向用户报告你搭建了什么、初始化了哪些分区,以及添加下一个分区的单行代码。
What to seed it with (discovery)
初始化内容建议(发现逻辑)
Turn "what are you building right now?" into concrete first sections by scanning
the repo:
| You find in the repo… | Seed this archetype |
|---|---|
| Recently-changed UI components; anything with theme/size/state variants | Variant board or Visual preview of the real component |
| A route/function that renders an image, OG/social card, or email template | Visual preview at true dimensions + an in-context mock |
| Live flow tester (read-only) and/or the raw pre-threshold output |
Many | Config / readiness panel (booleans only) |
| An integration whose product UI filters what it shows (permissions, access) | Debug dump of the raw, unfiltered truth the integration sees |
Good defaults when unsure: ask the user "what are you tuning or eyeballing right
now?" and map their answer to the closest archetype, or just seed a visual
preview of the component they most recently touched (from /).
git loggit diff通过扫描代码仓库,将「你当前正在构建什么?」转换为具体的初始分区:
| 在代码仓库中发现… | 初始化该类型分区 |
|---|---|
| 最近修改的UI组件;任何带有主题/尺寸/状态变体的内容 | 变体面板或真实组件的视觉预览 |
| 渲染图片、OG/社交卡片或邮件模板的路由/函数 | 真实尺寸的视觉预览 + 上下文模拟 |
| 实时流程测试器(只读)和/或阈值应用前的原始输出 |
代码中分散的大量 | 配置/就绪面板(仅显示布尔值) |
| 产品UI会过滤显示内容的集成(权限、访问控制) | 集成实际看到的原始未过滤信息的调试输出 |
不确定时的默认方案:询问用户「你当前正在调整或查看什么内容?」,将其答案映射到最接近的类型,或直接初始化用户最近修改的组件的视觉预览(来自/)。
git loggit diffThe access gate (non-negotiable — get this right first)
访问权限校验(不可协商——首先做好这一点)
A scratchpad exposes internal previews, debug dumps of real data, and buttons
that call real code. It must be open in local dev, locked to an operator
allowlist in production, checked server-side, and kept off the client bundle so
it can never be reasoned about or bypassed from client code.
One place, imported everywhere (pseudocode — see the mapping table for your
stack's real syntax):
// scratchpad-access — the SINGLE source of truth for who's allowed.
// Mark this module server-only so it can never be bundled to the client.
const OPERATOR_EMAILS = new Set([
"you@example.com",
// …operator accounts
])
// Open in local dev; allowlisted operators only in production.
function scratchpadAllowed(email):
if (not running in production) return true
return email is present AND OPERATOR_EMAILS has email.trim().lowercased()Gate rules:
- Fail closed. The page redirects (or 404s) a non-allowed user before any data loads; request-scoped routes return 404 (a 404 doesn't even admit the route exists — prefer it over 403). Never render then hide.
- The gate lives above data fetching. Check identity first; only then touch the DB or external services.
- No public nav entry. Reach it only from the dev-only link strip that renders nothing in production.
- Reuse the existing identity helper. Get the current user from whatever the rest of the app already uses; don't roll your own session logic.
Scratchpad暴露内部预览、真实数据的调试信息,以及调用真实代码的按钮。它必须本地开发环境开放访问,生产环境仅限允许列表中的操作人员访问,在服务器端校验,且不打包到客户端,因此客户端代码无法推断或绕过权限。
唯一的权限源,在所有地方导入(伪代码——查看框架映射表获取你的技术栈的真实语法):
// scratchpad-access — 权限校验的唯一可信源。
// 标记该模块为仅服务器端,使其永远不会打包到客户端。
const OPERATOR_EMAILS = new Set([
"you@example.com",
// …操作人员账户
])
// 本地开发环境开放访问;生产环境仅限允许列表中的操作人员。
function scratchpadAllowed(email):
if (not running in production) return true
return email is present AND OPERATOR_EMAILS has email.trim().lowercased()权限校验规则:
- 默认拒绝访问。非授权用户访问页面时,在加载任何数据之前重定向(或返回404);请求作用域的路由返回404(404甚至不会承认路由存在——优先使用404而非403)。永远不要先渲染再隐藏。
- 权限校验在数据获取之前。先检查身份;之后再访问数据库或外部服务。
- 无公开导航入口。仅通过仅在开发环境显示的链接条访问——该链接条在生产环境不渲染。
- 复用现有身份助手。从应用其他部分使用的身份系统获取当前用户;不要自行开发会话逻辑。
Section archetypes
分区类型
A scratchpad is a mix of these. Each has its own discipline.
Scratchpad由这些类型的分区组成。每种类型都有其规范。
1. Visual preview — "does it look right?"
1. 视觉预览——「这个看起来对吗?」
Render the real artifact at its true dimensions, plus a small in-context mock
so you catch how it reads at thumbnail size. Footer every visual with an "Open
raw ↗" link and "Edit at " so you can jump straight to
the code that draws it.
<path/to/source>- OG/social cards: frame at the real aspect ratio (e.g. 1.91:1), then a chat-bubble unfurl mock beside it.
- Emails: render the real template with sample props.
- Host-aware: if links/images resolve against the request host (custom domains, whitelabel), build them from the incoming host so the preview shows the right domain, not a hardcoded one.
以真实尺寸渲染真实内容,加上一个小的上下文模拟,以便你能看到它在缩略图尺寸下的显示效果。每个视觉内容底部添加**「打开原始链接 ↗」和「在编辑」**链接,让你可以直接跳转到绘制该内容的代码。
<path/to/source>- OG/社交卡片:以真实宽高比(例如1.91:1)展示,旁边显示聊天气泡展开模拟。
- 邮件:使用示例属性渲染真实模板。
- 感知宿主环境:如果链接/图片根据请求宿主(自定义域名、白标)解析,从传入的宿主构建它们,以便预览显示正确的域名,而非硬编码的域名。
2. Variant board — "which one do I like?"
2. 变体面板——「我更喜欢哪个?」
The taste engine. Two flavors — use whichever fits:
- Grid of variants rendered side by side — every theme, palette, size, or state at once — so you compare at a glance instead of toggling one control and forgetting the last.
- Live controls (sliders, toggles, number inputs) wired to a single live instance of the real component, so you drag until it feels right. Re-import the actual production component so the tuning transfers 1:1.
Include the awkward overlays and edge states — the badge that only sometimes
shows, the longest possible title, the empty case. Those are exactly what you
can't judge from the happy path.
Give live-controls boards a "Copy config as JSON" button (see Make tuned
state copy-out-able below) — once a variant feels right, the exact prop/config
object is one click from being pasted back into the real code.
主观判断工具。两种形式——选择适合的一种:
- 变体网格:并排渲染所有主题、调色板、尺寸或状态——让你一眼就能比较,而非切换某个控件后忘记之前的效果。
- 实时控件(滑块、开关、数字输入框):连接到真实组件的单个实时实例,让你调整到满意状态。重新导入实际的生产组件,确保调整的内容可以1:1同步到生产环境。
包含特殊场景和边缘状态——仅偶尔显示的徽章、最长的标题、空状态。这些正是你无法从常规场景中判断的内容。
为实时控件面板添加**「复制配置为JSON」按钮(见下方让调整后的状态可复制**)——一旦某个变体看起来合适,精确的属性/配置对象只需点击一次即可粘贴回真实代码中。
3. Config / readiness panel — "is this wired up?"
3. 配置/就绪面板——「这个是否已正确连接?」
A compact status list. Booleans only — never render secret values. Show
/ / with a one-line note on
what each key powers and what breaks without it.
✓ set○ optional, unset✗ required, MISSING// render booleans; NEVER the values themselves
env = {
xai: is XAI_API_KEY set?,
firecrawl: is FIRECRAWL_API_KEY set?,
}Also good here: a raw, unfiltered debug dump of what an integration actually
sees (e.g. every file a service account can access) — precisely because the real
product UI filters it. The scratchpad is where you see the unfiltered truth.
For tunable (non-secret) config — feature flags, thresholds, layout knobs — pair
the panel with a "Copy config as JSON" button so a tweaked config is one
click from being handed back to an agent to apply (see below). Never include
secret values in the copied blob.
一个紧凑的状态列表。仅显示布尔值——永远不要渲染敏感值。显示//,并附带一行说明每个键的作用以及缺失时会影响什么功能。
✓ 已设置○ 可选,未设置✗ 必填,缺失// 仅渲染布尔值;绝不显示值本身
env = {
xai: XAI_API_KEY 是否已设置?,
firecrawl: FIRECRAWL_API_KEY 是否已设置?,
}这里也适合展示集成实际看到的原始未过滤调试信息(例如服务账户可访问的所有文件)——正是因为真实产品UI会过滤这些信息。Scratchpad是你查看未过滤真相的地方。
对于可调(非敏感)配置——功能标志、阈值、布局旋钮——将面板与**「复制配置为JSON」**按钮配对,以便调整后的配置只需点击一次即可交给Agent应用(见下方)。复制的内容中永远不要包含敏感值。
4. Live flow tester — "does the real pipeline work?"
4. 实时流程测试器——「真实流水线是否正常工作?」
A button (or small form) that calls the real library function with
placeholder data and shows the result — so you exercise a pipeline without
clicking through the whole product or waiting on a real external trigger.
- Default to read-only, and say so in the UI: "runs the real matcher, writes nothing." Most testers should compute and display without persisting.
- If a tester writes or fires a side effect, be loud about it: label it, target a dev/dummy tenant or dry-run address, and never touch real user data. (E.g. an "inbound email loop test" that sends a real message through the real webhook → route → ingest, explicitly writing into the dev tenant.)
- Show the seam to the real product: a short "Real flow: go to X → do Y" note under each tester, mapping the isolated test back to where a user hits it. This keeps the scratchpad honest about what it's a proxy for.
- Surface the raw truth before product-side massaging: e.g. show a matcher's full ranked tail with sub-threshold rows dimmed under a dashed line, so you can calibrate the threshold — something the product view (which hides them) can't help you do.
- Make the inputs copy-out-able too: a "Copy inputs as JSON" button on the tester's form means the exact payload that produced an interesting result can be captured for a fixture, a bug report, or handing to an agent to wire in.
一个按钮(或小型表单),使用占位数据调用真实库函数并展示结果——让你无需遍历整个产品或等待真实外部事件即可测试流水线。
- 默认只读,并在UI中明确说明:「运行真实匹配器,不写入任何数据。」大多数测试器应仅计算并显示结果,不持久化。
- 如果测试器会写入或触发副作用,需醒目标记:添加标签,目标为开发/虚拟租户或试运行地址,永远不要触碰真实用户数据。(例如「入站邮件循环测试」,通过真实Webhook→路由→摄取发送真实消息,明确写入开发租户。)
- 显示与真实产品的关联:每个测试器下方添加简短的「真实流程:前往X→执行Y」说明,将隔离测试映射回用户实际使用的场景。这能让Scratchpad始终明确它是真实场景的代理。
- 展示产品处理前的原始真相:例如显示匹配器的完整排序结果,将低于阈值的行变暗并放在虚线下方,让你可以校准阈值——这是产品视图(会隐藏这些内容)无法帮助你完成的。
- 让输入内容也可复制:测试器表单上的「复制输入为JSON」按钮意味着产生有趣结果的精确负载可以被捕获为测试数据、错误报告,或交给Agent接入代码。
Make tuned state copy-out-able (round-trip to code)
让调整后的状态可复制(同步回代码)
The scratchpad's real payoff is closing the loop: tune by eye → capture the
exact config → hand it to an agent → the agent writes it into production. Every
board/panel/tester whose state you're adjusting should expose a "Copy as JSON"
button that serializes its current state (chosen variant, slider/toggle values,
the config object, the tester's input payload) to the clipboard as a clean,
paste-ready object.
Why it matters: without it, the human eyeballs a good result, then has to
describe it back ("a bit more padding, warmer accent") and the agent guesses.
With it, the human copies the precise object and the agent applies it verbatim —
no translation loss, no round-trips.
Discipline:
- Copy the real shape. Serialize the actual prop/config object the production
component or lib consumes — same keys, same types — so it pastes in with zero
reshaping. If the component takes , copy exactly that.
{ radius, accent, density } - Never copy secrets. Config-panel copies include tunable, non-sensitive keys only — never env values or credentials. (Booleans-only still holds for the readiness view; the copy button is for the tunable config beside it.)
- Make it obvious where it goes. Pair the button with the "Edit at
" footer so the destination for the pasted object is one click away.
<source> - Optional niceties: a "Copy as JSON" plus a "Copy as code" (the JSX/call-site snippet with the props inlined), and a matching paste/import field so a config handed back by an agent can be loaded straight into the controls to preview before it lands. Round-tripping both directions makes the human↔agent handoff instant.
// each controlled section keeps its live state in one object…
config = { radius: 12, accent: "#7c3aed", density: "compact" }
// …and a button serializes THAT object, verbatim, to the clipboard:
onCopy: copyToClipboard(JSON.stringify(config, null, 2))
// -> the user pastes it to an agent: "apply this to <Card>" → agent writes it in.Scratchpad的真正价值在于闭环:视觉调整→捕获精确配置→交给Agent→Agent将其写入生产环境。任何你调整状态的面板/测试器都应提供**「复制为JSON」按钮,将其当前**状态(选择的变体、滑块/开关值、配置对象、测试器的输入负载)序列化为干净、可直接粘贴的对象到剪贴板。
为什么这很重要:没有它,用户看到一个好的结果后,必须描述它(「多一点内边距,暖色调强调色」),Agent只能猜测。有了它,用户复制精确的对象,Agent可以直接应用——没有信息丢失,无需来回沟通。
规范:
- 复制真实结构。序列化生产组件或库实际使用的属性/配置对象——相同的键、相同的类型——以便粘贴后无需调整结构。如果组件接受,就复制完全相同的结构。
{ radius, accent, density } - 永远不要复制敏感信息。配置面板的复制内容仅包含可调、非敏感的键——永远不要包含环境值或凭据。(就绪视图仍保持仅显示布尔值;复制按钮用于旁边的可调配置。)
- 明确目标位置。将按钮与「在编辑」底部链接配对,以便粘贴对象的目标位置只需点击一次即可到达。
<source> - 可选优化:除了「复制为JSON」,还可以添加「复制为代码」(内联属性的JSX/调用代码片段),以及对应的粘贴/导入字段,以便Agent返回的配置可以直接加载到控件中预览,再写入代码。双向同步让用户与Agent的协作瞬间完成。
// 每个受控分区将其实时状态保存在一个对象中…
config = { radius: 12, accent: "#7c3aed", density: "compact" }
// …按钮将该对象原样序列化为剪贴板内容:
onCopy: copyToClipboard(JSON.stringify(config, null, 2))
// -> 用户将其粘贴给Agent:「将此应用到<Card>」 → Agent直接写入代码。Framework mapping
框架映射表
Translate the framework-agnostic pattern above into the user's actual stack. This
is a translation aid, not a prescription — match whatever the repo already does.
| Capability | Server-rendered app + file router | SPA + separate API | Full-stack meta-framework | Non-web (CLI / notebook / desktop) |
|---|---|---|---|---|
| Route | A file under the router dir ( | A dev-only client route + a gated API route that returns the data | A page route (loader/action or RSC) | A hidden subcommand / a dev-only notebook / a debug window |
| Server-only guard | An import that errors if bundled client-side (e.g. | Keep the gate on the API side; the client never holds the allowlist | Same server-only import + server components/actions | N/A — it already runs locally/privately |
| Always-fresh render | Disable caching for the route (e.g. | Client refetches on load; no CDN caching on the API route | Route-level no-store / dynamic | Re-run the command / re-execute the cell |
| Static/asset dir | | The API serves the bytes | | Write to a temp file and open it |
| Env access | | Read on the API side only; expose booleans to the client | | |
| Identity / "is prod" | Existing | Session cookie/JWT verified on the API + | Existing session helper + env flag | Local user; a |
将上述与框架无关的模式转换为用户实际使用的技术栈。这是一个转换参考,而非硬性规定——匹配代码仓库已有的实现方式。
| 能力 | 服务端渲染应用 + 文件路由 | SPA + 独立API | 全栈元框架 | 非Web(CLI / 笔记本 / 桌面应用) |
|---|---|---|---|---|
| 路由 | 路由目录下的文件( | 仅开发环境可见的客户端路由 + 受权限控制的API路由(返回数据) | 页面路由(loader/action或RSC) | 隐藏子命令 / 仅开发环境可见的笔记本 / 调试窗口 |
| 仅服务器端校验 | 导入会在客户端打包时报错的模块(例如 | 权限校验放在API端;客户端永远不会持有允许列表 | 相同的仅服务器端导入 + 服务器组件/动作 | 不适用——它已在本地/私有环境运行 |
| 始终最新渲染 | 禁用路由缓存(例如 | 客户端加载时重新获取数据;API路由不启用CDN缓存 | 路由级别的no-store / dynamic | 重新运行命令 / 重新执行单元格 |
| 静态/资源目录 | | API提供字节数据 | | 写入临时文件并打开 |
| 环境变量访问 | 服务器端使用 | 仅在API端读取;向客户端暴露布尔值 | 服务器端使用 | |
| 身份 / 是否为生产环境 | 现有 | API端验证会话Cookie/JWT + | 现有会话助手 + 环境标志 | 本地用户; |
The rules that make it good (not a junk drawer)
使其好用的规则(避免成为杂物箱)
- Reuse, never fork. Import the real components and call the real libs. A scratchpad that reimplements what it previews will drift and lie. This is the whole point.
- Read-only by default; loud and dev-scoped when not. Looking is free; writing is a deliberate, labeled exception aimed at dummy data.
- Gate server-side, fail closed, no public entry. Do this before anything else.
- Never leak secrets. Config panels show booleans and notes, never values.
- Every section is self-describing. A title + one-sentence subtitle that says what this is, where it comes from (name the source file), and what it maps to in the real product. Six months later you should understand a section without reading its code.
- Point back to source. "Edit at " footers turn eyeballing into a fast look → jump → tweak → refresh loop. 6b. Make tuned state copy-out-able. Any board/panel/tester you adjust gets a "Copy as JSON" button that serializes its current state in the real object shape — so a good result round-trips straight back into the code (or to an agent to apply). Never copy secrets.
path/to/source - Additive and cheap to extend. Adding a section is appending one array entry. Keep that true; if a section grows a lot of logic, push it into its own component and keep the page a table of contents.
- Always-fresh / no-cache so what you see always reflects the current code.
- It's allowed to be scrappy — but not dishonest. Ugly controls, placeholder copy, sample logos: all fine. Reimplemented behavior, leaked prod data, or silent writes to real records: never.
- 复用,绝不复制。导入真实组件并调用真实库。重新实现预览内容的Scratchpad会与生产环境脱节,提供错误信息。这是核心原则。
- 默认只读;写入时需醒目标记并限定在开发环境。查看是无风险的;写入是经过深思熟虑的、有标记的操作,仅针对虚拟数据。
- 服务器端校验,默认拒绝访问,无公开入口。首先做好这一点。
- 永远不要泄露敏感信息。配置面板仅显示布尔值和说明,绝不显示值。
- 每个分区都是自描述的。标题 + 一句话副标题,说明该分区是什么、来自哪里(源文件名)、对应真实产品中的哪个部分。六个月后你无需阅读代码就能理解分区的用途。
- 指向源代码。「在编辑」底部链接将视觉查看转换为快速的查看→跳转→调整→刷新循环。 6b. 让调整后的状态可复制。任何你调整的面板/测试器都要有「复制为JSON」按钮,以真实对象结构序列化其当前状态——让好的结果可以直接同步回代码(或交给Agent应用)。永远不要复制敏感信息。
path/to/source - 可扩展且成本低。添加一个分区只需向数组中添加一个元素。保持这一点;如果某个分区逻辑变得复杂,将其拆分为独立组件,页面仅作为目录。
- 始终最新 / 无缓存,以便你看到的内容始终反映当前代码。
- 可以简陋,但不能不诚实。丑陋的控件、占位文本、示例Logo:都可以接受。重新实现的行为、泄露的生产数据、静默写入真实记录:绝对不允许。
Anti-patterns
反模式
| Don't | Because |
|---|---|
| Reimplement the thing you're previewing | It drifts from production and the scratchpad silently lies about what ships |
| Render the page, then hide it from non-operators client-side | The data already left the server; gate before fetching, fail closed |
| Print secret/env values in a config panel | One screenshot leaks a key; show booleans only |
| Write to real records "just to test" | That's an admin action without audit trails; target a dev/dummy tenant and label it loudly |
| Let it sprawl into one giant scroll of un-labeled widgets | It becomes a junk drawer no one trusts; one section = one self-describing thing |
| Add a nav link / menu entry to reach it | Users find it; entry is a dev-only strip that renders nothing in prod |
| 不要做 | 原因 |
|---|---|
| 重新实现你要预览的内容 | 它会与生产环境脱节,Scratchpad会默默提供错误信息 |
| 先渲染页面,再在客户端隐藏非操作人员的内容 | 数据已经离开服务器;在获取数据之前做权限校验,默认拒绝访问 |
| 在配置面板中打印敏感/环境值 | 一张截图就会泄露密钥;仅显示布尔值 |
| 「只是测试」而写入真实记录 | 这是无审计追踪的管理操作;目标为开发/虚拟租户并醒目标记 |
| 让它变成一个堆满无标记小部件的长滚动页面 | 它会变成无人信任的杂物箱;一个分区 = 一个自描述的内容 |
| 添加导航链接 / 菜单项作为入口 | 用户会发现它;入口是仅在开发环境显示的链接条,生产环境不渲染 |
Build checklist
构建检查清单
- Access module: single source of truth, server-only, dev-open / prod-allowlist, imported by the page and every request-scoped route.
- Page redirects (or 404s) non-operators before loading data; routes return 404.
- Dev-only link strip (renders nothing in prod) to reach it — no real nav entry.
- Route-driven section switcher (),
?tab=array, always-fresh rendering.sections - Each section: title + subtitle (what / where-from / maps-to) and, for
visuals, an "Edit at " footer.
<source> - Config panels expose booleans only (readiness view); tunable config beside it gets a "Copy as JSON" button in the real object shape, secrets excluded.
- Variant boards and testers you adjust expose "Copy as JSON" (and optionally a paste/import field) so tuned state round-trips back to code / to an agent.
- Testers default read-only and say so; any writer targets dummy data and is clearly labeled.
- Interactive widgets import the real production components/libs, not copies.
- Host-aware previews if links resolve per-host.
- 权限模块:唯一可信源,仅服务器端,开发环境开放/生产环境仅限允许列表,被页面和每个请求作用域的路由导入。
- 页面在加载数据之前重定向(或返回404)非操作人员;路由返回404。
- 仅开发环境可见的链接条(生产环境不渲染)作为入口——无真实导航入口。
- 路由驱动的分区切换器(),
?tab=数组,始终最新渲染。sections - 每个分区:标题 + 副标题(是什么/来自哪里/对应产品中的哪个部分),对于视觉内容,底部有「在编辑」链接。
<source> - 配置面板仅暴露布尔值(就绪视图);旁边的可调配置有「复制为JSON」按钮,使用真实对象结构,不包含敏感信息。
- 你调整的变体面板和测试器有「复制为JSON」按钮(可选粘贴/导入字段),以便调整后的状态可以同步回代码 / 交给Agent。
- 测试器默认只读并明确说明;任何写入操作都针对虚拟数据并醒目标记。
- 交互控件导入真实生产组件/库,而非副本。
- 如果链接根据宿主解析,预览需感知宿主环境。
Minimal scaffold
最小脚手架
Framework-agnostic shape. Swap the bracketed mechanisms for your stack's real
syntax (see the mapping table).
// --- scratchpad-access module (server-only) -------------------------
mark-server-only
const OPERATOR_EMAILS = new Set([ "you@example.com" ])
function scratchpadAllowed(email):
if (not production) return true
return email present AND OPERATOR_EMAILS has email.trim().lowercased()
// --- the scratchpad page (server-rendered, always-fresh) ------------
configure route: always-fresh / no-cache
function ScratchpadPage(request):
user = <existing identity helper>() // reuse the app's auth
if (not scratchpadAllowed(user.email)):
return redirect("/") or 404 // gate BEFORE any data
sections = [
// { id, label, node } — one per thing you're eyeballing/fiddling with.
// Append here to add a section. Each node imports the REAL component/lib.
// { id: "og", label: "OG · default", node: <VisualPreview .../> },
// { id: "env", label: "Config", node: <ReadinessPanel .../> },
]
activeId = sections.find(s => s.id == request.query.tab)?.id ?? sections[0]?.id
return SectionSwitcher(sections, activeId) // sidenav/tabs; shows active node
// --- dev-only entry link (renders nothing in production) ------------
function DevLinks():
if (production) return nothing
return <pinned corner strip linking to /scratchpad>The rest is just sections. Start with one visual preview and one readiness panel;
add a variant board the first time you're choosing between looks, and a live
tester the first time you're tired of clicking through the whole product to test
one lib.
与框架无关的结构。将括号中的机制替换为你的技术栈的真实语法(查看框架映射表)。
// --- scratchpad-access模块(仅服务器端)-------------------------
mark-server-only
const OPERATOR_EMAILS = new Set([ "you@example.com" ])
function scratchpadAllowed(email):
if (not production) return true
return email present AND OPERATOR_EMAILS has email.trim().lowercased()
// --- Scratchpad页面(服务器端渲染,始终最新)------------
配置路由:始终最新 / 无缓存
function ScratchpadPage(request):
user = <现有身份助手>() // 复用应用的认证逻辑
if (not scratchpadAllowed(user.email)):
return redirect("/") or 404 // 加载任何数据之前做权限校验
sections = [
// { id, label, node } — 每个元素对应一个你要查看/调试的内容。
// 添加分区只需在此处追加。每个node导入**真实**组件/库。
// { id: "og", label: "OG · 默认", node: <VisualPreview .../> },
// { id: "env", label: "配置", node: <ReadinessPanel .../> },
]
activeId = sections.find(s => s.id == request.query.tab)?.id ?? sections[0]?.id
return SectionSwitcher(sections, activeId) // 侧边栏/标签页;显示当前激活的node
// --- 仅开发环境可见的入口链接(生产环境不渲染)------------
function DevLinks():
if (production) return nothing
return <固定在角落的链接条,指向/scratchpad>剩下的就是添加分区。从一个视觉预览和一个就绪面板开始;第一次需要在不同外观之间选择时添加变体面板,第一次厌倦了遍历整个产品测试某个库时添加实时测试器。