六个活动的汇报界面与技能补齐;skill-engine 接上装配面
汇报界面(AM 07):
· 视图外壳与卡片件提到 `@sdk`(viewShell / kvCard / tableCard / stagesCard / noteCard)——
六个活动共用一套面貌,不各自发明;样式内联自带(沙箱页面不继承工作台 CSS)。
· flow 四活动按**步序位置**挂模版:
A1 事态全景 / 打标判据 / 成果预览;A4 取价基准 / 定价与门禁 / 草案待放行;
A6 正式报价单 / 放行即发出;A7 结果登记 / 闭环待放行。
· loop 两活动按**阶段与里程碑**组织:案例全景一屏,画阶段生命周期态、当下可为、目标缺口 ——
素材全部来自语义引擎,零 LLM 即可成屏;模型只决定下一步做什么,不负责画面。
为此给 loop-engine 补了视图端口,与 flow 共用同一个网关。
· 每张模版都把「这一屏在说什么 / 放行意味着什么」写在数据旁边 ——
人要在数据上作判断,判据就得摆在手边。
技能(AM 08):
· 六个活动各写了 1–2 条真技能:适用描述 + 方法正文 + 结晶片段脚本。
正文写的是**方法经验**而非流程复述(如「原文没写的不要补」「该等还是该问」
「无基准不是异常,是一种基准」)。
· 资源声明放**技能 ID**(权力面),资产放**定义本体**(方法面),两处分清。
· **skill-engine 从来没有被构造过** —— 模块实现完整却无人调用,这是它一直没被用上的原因。
容器里补上:目录与白名单都取自活动清单,装配前查扩权。
· loop 的叙事面接入**技能目录**(渐进披露:常驻上下文只给「有哪些方法、何时适用」,
正文等 AI 说要才加载),装配随即**盖戳入任务记忆** —— 回放与归因依赖之。
gates: tsc / eslint / depcruise(334 模块)/ 423 测试 全绿
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 视图模版工具箱 —— 活动开发者写「呈现」时看到的世界(AM 07)。
|
||||
*
|
||||
* 模版是**内容不是代码**:这里产出的一律是无脚本、无事件处理器、无外链的静态 HTML;
|
||||
* 数据由基座在推送时填(`data-slot` 取值、`data-rows` 逐条展开)。
|
||||
* 容器是沙箱 iframe(`sandbox=""`),因此**样式必须自带** —— 页面不继承工作台的 CSS。
|
||||
*
|
||||
* 六个活动共用这一套外壳:汇报面貌一致,不是每个活动各自发明一套。
|
||||
*/
|
||||
|
||||
export type Tone = 'brand' | 'ok' | 'warn' | 'danger' | 'case' | 'archive';
|
||||
|
||||
const ACCENT: Readonly<Record<Tone, string>> = {
|
||||
brand: '#3370ff',
|
||||
ok: '#0aa294',
|
||||
warn: '#ff8800',
|
||||
danger: '#e5484d',
|
||||
case: '#7f3bf5',
|
||||
archive: '#46586a',
|
||||
};
|
||||
|
||||
/** 一套克制的排版;所有尺寸用相对单位,窄栏也不横向溢出 */
|
||||
const BASE_CSS = (accent: string): string => `
|
||||
:root { color-scheme: light; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0; padding: 18px;
|
||||
font: 13px/1.6 -apple-system, "PingFang SC", "Noto Sans SC", "Segoe UI", sans-serif;
|
||||
color: #1f2933; background: #f7f8fa;
|
||||
}
|
||||
.wrap { max-width: 760px; margin: 0 auto; }
|
||||
.hd { display: flex; align-items: stretch; gap: 12px; margin-bottom: 14px; }
|
||||
.hd .bar { width: 4px; border-radius: 2px; background: ${accent}; flex: none; }
|
||||
.hd .kick { font-size: 11px; letter-spacing: .08em; color: #6a7686; }
|
||||
.hd h1 { margin: 2px 0 0; font-size: 17px; font-weight: 650; letter-spacing: .01em; }
|
||||
.card { background: #fff; border: 1px solid #e6e9ee; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
|
||||
.card h2 { margin: 0 0 10px; font-size: 12px; font-weight: 600; color: #6a7686; letter-spacing: .04em; }
|
||||
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 18px; }
|
||||
.kv > div { min-width: 0; }
|
||||
.kv em { display: block; font-style: normal; font-size: 11px; color: #8b95a3; margin-bottom: 2px; }
|
||||
.kv b { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
|
||||
.tbl-wrap { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
th { text-align: left; font-weight: 600; color: #6a7686; padding: 6px 8px; border-bottom: 1px solid #e6e9ee; white-space: nowrap; }
|
||||
td { padding: 7px 8px; border-bottom: 1px solid #f0f2f5; overflow-wrap: anywhere; }
|
||||
tr:last-child td { border-bottom: 0; }
|
||||
.mono { font-family: "SF Mono", Menlo, Consolas, monospace; }
|
||||
.tag { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;
|
||||
background: ${accent}1a; color: ${accent}; }
|
||||
.note { font-size: 11px; color: #8b95a3; margin-top: 10px; line-height: 1.75; }
|
||||
.steps { list-style: none; margin: 0; padding: 0; }
|
||||
.steps li { position: relative; padding: 0 0 12px 20px; border-left: 2px solid #e6e9ee; }
|
||||
.steps li:last-child { padding-bottom: 0; border-left-color: transparent; }
|
||||
.steps li::before { content: ""; position: absolute; left: -5px; top: 5px; width: 8px; height: 8px;
|
||||
border-radius: 50%; background: ${accent}; }
|
||||
.steps .t { font-weight: 600; }
|
||||
.steps .d { color: #8b95a3; font-size: 11.5px; }
|
||||
`;
|
||||
|
||||
/** 页头 + 卡片区 —— 每张模版都长这样,只换标题与内容 */
|
||||
export function viewShell(input: {
|
||||
title: string;
|
||||
kicker: string;
|
||||
tone: Tone;
|
||||
body: string;
|
||||
}): string {
|
||||
const accent = ACCENT[input.tone];
|
||||
return `<style>${BASE_CSS(accent)}</style>
|
||||
<div class="wrap">
|
||||
<div class="hd"><span class="bar"></span><div><div class="kick">${input.kicker}</div><h1>${input.title}</h1></div></div>
|
||||
${input.body}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/** 键值卡:`[标签, 取值路径]`;`mono` 用等宽显示编号类字段 */
|
||||
export function kvCard(title: string, items: ReadonlyArray<readonly [string, string, ('mono' | 'tag')?]>): string {
|
||||
const cells = items
|
||||
.map(([label, path, style]) => {
|
||||
const cls = style === undefined ? '' : ` class="${style === 'mono' ? 'mono' : 'tag'}"`;
|
||||
return `<div><em>${label}</em><b${cls} data-slot="${path}"></b></div>`;
|
||||
})
|
||||
.join('');
|
||||
return `<div class="card"><h2>${title}</h2><div class="kv">${cells}</div></div>`;
|
||||
}
|
||||
|
||||
/** 逐条表:行数不写死,由 `data-rows` 展开 */
|
||||
export function tableCard(
|
||||
title: string,
|
||||
rowsPath: string,
|
||||
columns: ReadonlyArray<readonly [string, string, ('mono')?]>,
|
||||
note?: string,
|
||||
): string {
|
||||
const head = columns.map(([label]) => `<th>${label}</th>`).join('');
|
||||
const cells = columns
|
||||
.map(([, path, style]) => `<td${style === 'mono' ? ' class="mono"' : ''} data-slot="${path}"></td>`)
|
||||
.join('');
|
||||
return `<div class="card"><h2>${title}</h2><div class="tbl-wrap"><table>
|
||||
<thead><tr>${head}</tr></thead>
|
||||
<tbody data-rows="${rowsPath}"><tr>${cells}</tr></tbody>
|
||||
</table></div>${note === undefined ? '' : `<p class="note">${note}</p>`}</div>`;
|
||||
}
|
||||
|
||||
/** 说明卡:把「为什么这么做」摆在数据旁边 —— 人要在数据上作判断 */
|
||||
export function noteCard(title: string, html: string): string {
|
||||
return `<div class="card"><h2>${title}</h2><p class="note" style="margin-top:0">${html}</p></div>`;
|
||||
}
|
||||
|
||||
/** 阶段时间线:loop 按案例模型的阶段与里程碑组织汇报(AM 07) */
|
||||
export function stagesCard(title: string, rowsPath: string, note?: string): string {
|
||||
return `<div class="card"><h2>${title}</h2><ul class="steps" data-rows="${rowsPath}">
|
||||
<li><span class="t" data-slot="name"></span><span class="d" data-slot="state"></span></li>
|
||||
</ul>${note === undefined ? '' : `<p class="note">${note}</p>`}</div>`;
|
||||
}
|
||||
Reference in New Issue
Block a user