Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'none'; img-src 'self' data:; base-uri 'none'; form-action 'none'">
<link rel="icon" href="./skillsync.svg"><link rel="stylesheet" href="./style.css"><title>Skill 文档检查 · SkillSync</title></head>
<body><header><img src="./skillsync.svg" width="34" height="34" alt=""><strong>SkillSync</strong><a href="https://github.com/Chumaniac/skillsync">本机目录核验 ↗</a></header>
<main><h1>检查 Skill 文档</h1><p>粘贴 SKILL.md,读取 YAML 文档头和正文。内容只在当前标签页处理。</p>
<div class="columns"><section><label for="source">SKILL.md</label><textarea id="source" maxlength="24576" spellcheck="false" aria-describedby="limit"></textarea><small id="limit">最多 24 KiB。网页检查文档格式,本机 CLI 可继续检查来源、权限和交付物。</small>
<a class="skip-link" href="#workspace">跳到文档编辑</a><main id="workspace"><div class="workspace-heading"><div><p class="eyebrow">DOCUMENT INSPECTOR</p><h1>检查 Skill 文档</h1><p>粘贴 SKILL.md,读取 YAML 文档头和正文。内容只在当前标签页处理。</p></div><span class="privacy-label">本机处理 · 无网络请求</span></div>
<div class="columns"><section class="editor-panel"><div class="panel-heading"><label for="source">SKILL.md</label><span id="document-meta"></span></div><textarea id="source" maxlength="24576" spellcheck="false" aria-describedby="limit"></textarea><small id="limit">最多 24 KiB。网页检查文档格式,本机 CLI 可继续检查来源、权限和交付物。</small>
<div class="actions"><button id="check">检查文档</button><button id="example" class="secondary">载入示例</button></div></section>
<section aria-live="polite"><h2>解析结果</h2><strong id="status">等待检查</strong><p id="message">运行检查后,这里会列出文档头字段和正文长度。</p><button id="locate" type="button" class="secondary" hidden>定位错误</button><dl id="fields"></dl></section></div></main>
<section class="result-panel" aria-live="polite" data-state="idle"><div class="panel-heading"><h2 id="result-heading" tabindex="-1">解析结果</h2><span class="result-indicator" aria-hidden="true"></span></div><strong id="status">等待检查</strong><p id="message">运行检查后,这里会列出文档头字段和正文长度。</p><button id="locate" type="button" class="secondary" hidden>定位错误</button><dl id="fields"></dl><div class="scope-note"><strong>检查范围</strong><p>读取 YAML 文档头与正文。来源身份、执行行为及目录权限,请使用本机核验台检查。</p></div></section></div></main>
<script type="module" src="./main.ts"></script></body></html>
11 changes: 10 additions & 1 deletion browser/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const status = document.querySelector<HTMLElement>("#status")!;
const message = document.querySelector<HTMLElement>("#message")!;
const locate = document.querySelector<HTMLButtonElement>("#locate")!;
let errorLocation: {start: number; end: number; line: number; column: number} | undefined;
function setState(value: string) { document.querySelector<HTMLElement>(".result-panel")!.dataset.state = value; }
function updateMeta() { document.querySelector("#document-meta")!.textContent = input.value.split("\n").length + " 行 · " + new TextEncoder().encode(input.value).length + " B"; }
function resetLocation() {
errorLocation = undefined;
locate.hidden = true;
Expand Down Expand Up @@ -32,6 +34,7 @@ function tooLarge(value: string) {
return value.length > byteLimit || encoder.encode(value).length > byteLimit;
}
function oversized() {
setState("error");
resetLocation();
fields.replaceChildren();
status.textContent = "文档过大";
Expand All @@ -50,6 +53,7 @@ input.addEventListener("beforeinput", event => {
if (event.cancelable && event.inputType.startsWith("insert")) checkInsertion(event, event.data);
});
function clear() {
setState("idle"); updateMeta();
resetLocation();
status.textContent = "等待检查";
message.textContent = "文档已更新,请重新检查。";
Expand All @@ -63,6 +67,7 @@ function check() {
}
const parsed = parseSkillDocument(input.value);
if (!parsed.ok) {
setState("error");
status.textContent = "无法解析文档";
errorLocation = parsed.location;
if (errorLocation) {
Expand All @@ -73,6 +78,7 @@ function check() {
}
return;
}
setState("valid");
status.textContent = "文档可解析";
const {frontmatter, body} = parsed.value;
const missing = ["name","description"].filter(key => typeof frontmatter[key] !== "string" || !frontmatter[key].trim());
Expand All @@ -85,8 +91,11 @@ function check() {
}
}
input.value = example;
updateMeta();
input.addEventListener("input",clear);
document.querySelector("#check")!.addEventListener("click",check);
function inspect() { check(); document.getElementById("result-heading")!.focus(); }
document.querySelector("#check")!.addEventListener("click", inspect);
input.addEventListener("keydown", event => { if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { event.preventDefault(); inspect(); } });
document.querySelector("#example")!.addEventListener("click", () => {
if (input.value.trim() && input.value !== example && !window.confirm("用示例替换当前文档?")) return;
input.value = example;
Expand Down
13 changes: 2 additions & 11 deletions browser/style.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions docs/PRODUCT_EXPERIENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ node dist/cli/index.js gui --state-dir /absolute/private/report-directory
## Live browser navigation

The static entry page accepts an explicit user-activated top-level navigation, including a reload that retains cross-site Fetch Metadata. The literal loopback Host, Origin, API nonce and mutation checks remain enforced. Cross-site iframes, script/API reads and writes are rejected. This fixes a real Chrome launch refusal without changing file-selection or repair authority.

## 工作区与窄屏操作

浏览器文档检查采用编辑区与诊断区,支持 Ctrl/⌘ + Enter。当前行数与 UTF-8 字节数随输入更新。本机核验台将目录范围和结论并列,有警告或未确认项时在主标题标出;历史只读与确认写文件的条件保持原要求。
4 changes: 3 additions & 1 deletion src/workbench/journey-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export function journeySummary(kind:'skill'|'artifacts',report:VerificationRepor
const value=report as VerificationReport;actionable=value.issues.filter(issue=>['fail','warn'].includes(issue.finding.status)).length;
counts=[{label:'阻断问题',count:value.summary.fail},{label:'建议核对',count:value.summary.warn},{label:'尚未确认',count:value.summary.unknown}];
}else{const value=report as ArtifactReport;counts=[{label:'已读取文件',count:value.files.length},{label:'问题定位',count:value.findings.length},{label:'未完成对账',count:value.reconciliations?.filter(check=>check.status==='unknown').length||0}];}
const skillCounts=kind==='skill'?(report as VerificationReport).summary:undefined;
const headline=skillCounts?.fail ? '存在需要处理的项目' : skillCounts?.unknown ? '检查完成 · '+skillCounts.unknown+' 项尚未确认' : skillCounts?.warn ? '检查完成 · '+skillCounts.warn+' 项建议核对' : report.exitCode===0?'所声明的检查通过':'存在需要处理的项目';
const canPlan=!readOnly&&kind==='skill'&&!incomplete&&actionable>0;
return {title:readOnly?'历史证据 · 只读':incomplete?'本次检查尚未完整':report.exitCode===0?'所声明的检查通过':'存在需要处理的项目',
return {title:readOnly?'历史证据 · 只读':incomplete?'本次检查尚未完整':headline,
description:readOnly?'打开历史不会重新核验或恢复修复权限。可重新核验当前目录再决定下一步。':incomplete?'先查看报告中的输入、容量或未完成原因,修正后重新核验;当前结果不能作为完整通过。':kind==='artifacts'?'交付检查不提供自动修改。按报告修正原文件或清单,再明确重新检查;本报告会保留。':canPlan?'可先生成处理计划,区分安全修改与人工步骤;审阅并明确确认后才会写文件。':'可查看报告或保存摘要。继续修改文件后,请重新核验。',counts,canPlan,readOnly};
}
Loading
Loading