Workbench

添加说明、状态与操作按钮

用 Markdown 和配置 schema 添加管理界面,无需编写 React。

当页面只需要说明、最新状态、少量按钮或一次性表单时,使用 Content。你写 Markdown 和数据处理函数,Workbench 负责显示。 开始前,宿主应已启用 Workbench,Plugin 应能正常启动。

不要用 Content 做分页、progress/cancel、多份独立 loading state、lossless stream 或自定义组件。这些需求直接使用 View 标准配方。普通非敏感配置仍使用 configs.use() 的标准 Config UI。

最小路径

先在 src/workbench.ts 声明页面。slot 是 Markdown 中插入数据或按钮的位置;下面的 status 与 refresh 各对应一个 slot:

// src/workbench.ts
import * as v from 'valibot'
import { workbench } from '@pluxel/workbench'

const Status = v.object({
	connected: v.boolean(),
	queued: v.pipe(v.number(), v.integer(), v.minValue(0)),
})

export const ServiceWorkbench = workbench.define({
	overview: workbench.content({
		document: workbench.markdown(import.meta.url, './overview.md', {
			status: workbench.data(Status),
			refresh: workbench.action({ label: 'Refresh' }),
		}),
		placement: workbench.tab({ label: 'Overview' }),
	}),
})

Markdown 只决定 slot 的位置:

# Service

::slot[status]

::slot[refresh]

在 Plugin init() 中只发布一次,并为每个 data/action key 提供 exact binding:

override init() {
	this.ctx.workbench?.publish(ServiceWorkbench, {
		overview: () => ({
			load: () => ({ status: this.inspectService() }),
			actions: {
				refresh: async () => {
					await this.refreshService()
					return { ok: true, message: 'Refreshed' }
				},
			},
		}),
	})
}

这就是默认完整路径:不创建 RpcTarget、不管理 query cache,也不手动 reload。含 data 的 Content action settle 后,Framework 会 重新执行 load()。

需要把同一 Content 放在当前插件的侧栏或底部时,使用 workbench.panel() 作为 placement;Markdown、data/action 与 bindings 保持相同。

加入一次性表单

在 action 上声明 input,Workbench 就会显示并校验表单。默认点击按钮打开对话框;需要直接展开时加 form: 'embedded'。

const ProbeInput = v.object({
	timeoutMs: v.pipe(v.number(), v.integer(), v.minValue(100), v.maxValue(30_000)),
})

// 放进 workbench.markdown() 的 slots 对象
probe: workbench.action({ label: '测试连接', input: ProbeInput, form: 'embedded' })

在 Markdown 中加入 ::slot[probe],并在 actions.probe 中处理校验后的 { timeoutMs },返回 { ok: true, message: '连接正常' }。 字段标题和控件偏好使用 Valibot 表单 metadata。

只在状态会自行变化时加入 live refresh

dataChanged() 是 latest-state 提示,不携带事件 payload,也不保证逐事件交付。仅当领域状态在 action 之外变化时,才用 open signal 注册领域 listener:

overview: ({ signal, dataChanged }) => {
	this.serviceEvents.addEventListener('change', dataChanged, { signal })
	return {
		load: () => ({ status: this.inspectService() }),
		actions: { refresh: async () => {
			await this.refreshService()
			return { ok: true, message: 'Refreshed' }
		} },
	}
},

signal 会在 entry close、socket close、Plugin replacement 或 owner withdrawal 时 abort。listener 的清理属于领域对象; dataChanged()、load lane 与 Shell observer 由 Framework 拥有。

Markdown 与字段规则

Markdown 在 build time 编译,浏览器不会运行 Markdown parser 或插入 raw HTML。支持普通段落、标题、强调/删除线、 列表、引用、分隔线、代码、bounded GFM table,以及安全的 https:、mailto: 和本文 fragment link。HTML、图片、 相对链接、JSX 风格标签、task list 与 frontmatter 会使构建失败;{name} 一类 MDX expression 只按普通文本处理, 不会执行。

:slot[key] 只能在普通 paragraph 中放 inline scalar data;::slot[key] 放 block data 或 action。每个声明必须恰好出现一次, unknown、missing、duplicate、nested 或 inline action 都会使构建失败。Data schema 用于只读展示,不允许 default、transform、lazy、 undefined-producing wrapper 或 password;action input 必须是 host form 支持的 Valibot object,默认用 dialog,form: 'embedded' 固定展开。Shell 只提交 raw portable data,Runtime 会在 handler 前再次执行 authoritative Valibot validation。

需要防止误触时给 action 添加确认文案,例如 workbench.action({ label: '删除', confirm: '确定删除这条记录吗?' })。Shell 会显示危险样式并在提交前调用 host confirm。 这只是 UX guard,不是授权边界:Framework 会在执行时重新确认 owner generation;handler 仍须根据 open 时认证的 principal 重新授权,并在写入前重新检查当前领域状态,不能信任确认框、旧 data 或客户端提交的前置条件。

load() 一次返回所有 data keys,可以复用领域已有的递归只读 detached snapshot;Framework 不修改它。actions 必须与声明的 action keys 精确对应。

声明内容打开与执行行为
data + action打开时 initial load;action 在同一 run() 中 settle 后再 load
只有 action不建立 data subscription,直接返回 action outcome
action 之外也有状态变化按上节注册 dataChanged(),提供最新状态提示
纯 Markdown省略 slots;publish(ServiceWorkbench) 即可,不创建 root 或占 opened-entry quota,也不生成 MF producer/Bridge

subscribe()、load()、run() 是本地 opened handle 的领域名称;底层 Content RPC root 使用 subscribeDto()、loadDto()、runDto(),纯数据结果与 root 持有的订阅寿命分离。

需要逐事件交付、独立并发状态、进度/取消、服务端分页或自定义 React UI 时,改用 View。

检查结果

运行应用,从“应用”目录打开插件的 Overview 入口。应看到状态和 Refresh 按钮;点击后状态会重新读取。 如果入口缺失,检查宿主已开启 Workbench、Plugin 已运行,并且 publish() 的 key 与声明一致。 Markdown 的错误 slot、重复 slot 或不支持的内容应在构建时修正。

最后更新于

本页目录