setNewTitle(null)}>
diff --git a/product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx b/product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx
new file mode 100644
index 000000000..b5bae52fb
--- /dev/null
+++ b/product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx
@@ -0,0 +1,223 @@
+//! 知识渲染件 · 渲染入口(常驻模块 · 插座口 knowledge-render)
+//!
+//! 职责:把仓库里的 Markdown 文件变成可读的页面——frontmatter 解析、
+//! Notion/Outline 导出兼容、标题锚点、大纲同源、跳转。
+import { useMemo } from "react";
+import DOMPurify from "dompurify";
+import { marked, Parser, type Tokens } from "marked";
+export { knowledgeRenderManifest } from "./manifest";
+
+function escapeHtml(value: string) {
+ return value
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """);
+}
+
+export function splitFrontmatter(body: string) {
+ const normalized = body.replace(/\r\n/g, "\n");
+ if (!normalized.startsWith("---\n"))
+ return {
+ content: normalized,
+ metadata: {} as Record
,
+ tags: [] as string[],
+ };
+ const closing = normalized.indexOf("\n---\n", 4);
+ if (closing < 0)
+ return {
+ content: normalized,
+ metadata: {} as Record,
+ tags: [] as string[],
+ };
+ const metadata: Record = {};
+ const tags: string[] = [];
+ let pendingListKey = "";
+ for (const line of normalized.slice(4, closing).split("\n")) {
+ const listItem = /^\s+-\s+(.+)$/.exec(line);
+ if (listItem && pendingListKey) {
+ if (pendingListKey === "tags")
+ tags.push(listItem[1].trim().replace(/^['"]|['"]$/g, ""));
+ continue;
+ }
+ const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
+ if (!match) continue;
+ pendingListKey = match[2] === "" ? match[1] : "";
+ const value = match[2].replace(/^['"]|['"]$/g, "");
+ metadata[match[1]] = value;
+ if (match[1] === "tags") {
+ const inline = /^\[(.*)\]$/.exec(match[2].trim());
+ const source = inline ? inline[1] : value;
+ source
+ .split(",")
+ .map((part) => part.trim().replace(/^['"]|['"]$/g, ""))
+ .filter(Boolean)
+ .forEach((tag) => tags.push(tag));
+ }
+ }
+ return { content: normalized.slice(closing + 5), metadata, tags };
+}
+
+function stripNotionLinks(line: string) {
+ // Notion 页面链接 → 只留链接文字(跳转关系按谕旨失效)
+ return line.replace(
+ /\[([^\]]*)\]\(([^)]*)\)/g,
+ (whole, text: string, href: string) =>
+ /notion\.(?:so|site)/.test(href) ? text : whole,
+ );
+}
+
+export function notionCompat(content: string) {
+ // 存量旧库文件尚未洗净:渲染时按"进门即洗"同规矩兜底过滤一遍。
+ // 内容一字不改,只把外来知识库的外壳换成光湖原生 Markdown(冰朔 2026-08-15 谕:不兼容,进门就转)。
+ const result: string[] = [];
+ for (const line of content.replace(/\r\n/g, "\n").split("\n")) {
+ const trimmed = line.trim();
+ if (trimmed === "") continue;
+ if (trimmed.startsWith(":::toggle")) {
+ const title = trimmed.slice(":::toggle".length).trim();
+ result.push(title ? `**▸ ${title}**` : "**▸ 详情**");
+ continue;
+ }
+ if (trimmed === ":::" || trimmed.startsWith(":::toc")) continue;
+ result.push(stripNotionLinks(line.replace(/
/g, "\n")));
+ }
+ // Notion 粗体写法(**文字:**紧接正文)不合 CommonMark:给收尾星号补一口气
+ return result
+ .join("\n")
+ .replace(/(\*\*[^*\n]+\*\*)(?=[^\s.,;:!?,。;:!?、)\]"'])/g, "$1 ");
+}
+
+const CALLOUT_TINTS: [RegExp, string][] = [
+ [/📌|🌌|🧭|💜|🗂|🏷|📚/, "callout-lavender"],
+ [/💡|⚡|🌟|☀|✨|🔑/, "callout-amber"],
+ [/⚠|🔥|❗|🚨|❌/, "callout-rose"],
+ [/✅|🌿|🍀|💚|✔/, "callout-mint"],
+ [/🌊|💧|🔵|❄|🧊/, "callout-sky"],
+];
+function normalizeTitle(value: string) {
+ return value.replace(/[\s·•・\-_|*`]/g, "").toLowerCase();
+}
+
+function calloutTint(text: string) {
+ for (const [pattern, tint] of CALLOUT_TINTS)
+ if (pattern.test(text)) return tint;
+ return "callout-slate";
+}
+
+export function markdownHtml(
+ body: string,
+ knownTitles: readonly string[] = [],
+) {
+ const compat = notionCompat(body);
+ const titleMap = new Map();
+ for (const title of knownTitles) titleMap.set(normalizeTitle(title), title);
+ const withWikiLinks = compat.replace(
+ /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,
+ (_whole, target: string, label?: string) =>
+ ``,
+ );
+ let headingIndex = 0;
+ const renderer = new marked.Renderer();
+ renderer.heading = (token) => {
+ const id = `heading-${headingIndex}`;
+ headingIndex += 1;
+ const inlineHtml = token.tokens.length
+ ? Parser.parseInline(token.tokens)
+ : escapeHtml(token.text);
+ return `${inlineHtml}\n`;
+ };
+ renderer.blockquote = (token) => {
+ const inner = Parser.parse(token.tokens) as string;
+ return `${inner}
\n`;
+ };
+ renderer.link = (token) => {
+ const inner = token.tokens.length
+ ? Parser.parseInline(token.tokens)
+ : escapeHtml(token.text);
+ const href = token.href || "";
+ if (/notion\.(?:so|site)/i.test(href)) {
+ // Notion 残留链接:认得出是自家页面就转成内部跳转,认不出标"外来页面"。
+ const hit = titleMap.get(normalizeTitle(token.text));
+ if (hit)
+ return ``;
+ return `${inner}外来页面`;
+ }
+ if (/^https?:\/\//i.test(href))
+ return `${inner}`;
+ return `${inner}`;
+ };
+ const html = marked.parse(withWikiLinks, {
+ async: false,
+ gfm: true,
+ breaks: false,
+ renderer,
+ }) as string;
+ return DOMPurify.sanitize(html, {
+ ADD_ATTR: ["data-wiki", "id", "target", "rel"],
+ ADD_TAGS: ["button"],
+ });
+}
+
+export function documentOutline(body: string) {
+ const headings: { id: string; level: number; title: string }[] = [];
+ const plain = (tokens: unknown): string =>
+ (tokens as { type: string; text?: string; tokens?: unknown[] }[])
+ .map((token) =>
+ token.tokens && token.tokens.length
+ ? plain(token.tokens)
+ : token.text || "",
+ )
+ .join("");
+ marked
+ .lexer(splitFrontmatter(body).content, { gfm: true })
+ .filter((token): token is Tokens.Heading => token.type === "heading")
+ .forEach((token, index) => {
+ if (index >= 24) return;
+ headings.push({
+ id: `heading-${index}`,
+ level: token.depth,
+ title: plain(token.tokens).trim(),
+ });
+ });
+ return headings;
+}
+
+export function jumpToHeading(id: string) {
+ const element = document.querySelector(
+ `.document-scroll [id="${id}"]`,
+ );
+ if (!element) return;
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
+ element.classList.remove("heading-flash");
+ void element.offsetWidth;
+ element.classList.add("heading-flash");
+}
+
+export function MarkdownDocument({
+ body,
+ knownTitles,
+ onWiki,
+}: {
+ body: string;
+ knownTitles?: readonly string[];
+ onWiki?: (target: string) => void;
+}) {
+ const parsed = useMemo(() => splitFrontmatter(body), [body]);
+ const html = useMemo(
+ () => markdownHtml(parsed.content, knownTitles),
+ [parsed.content, knownTitles],
+ );
+ return (
+ {
+ const element = (event.target as HTMLElement).closest(
+ "[data-wiki]",
+ );
+ if (element?.dataset.wiki && onWiki) onWiki(element.dataset.wiki);
+ }}
+ dangerouslySetInnerHTML={{ __html: html }}
+ />
+ );
+}
diff --git a/product-source/hololake-clean-desktop/src/modules/knowledge-render/manifest.ts b/product-source/hololake-clean-desktop/src/modules/knowledge-render/manifest.ts
new file mode 100644
index 000000000..dfcadb807
--- /dev/null
+++ b/product-source/hololake-clean-desktop/src/modules/knowledge-render/manifest.ts
@@ -0,0 +1,27 @@
+//! 模块形状声明 · 插座制(冰朔 2026-08-15 架构谕)
+//!
+//! 软件只认这一个插座形状:每个模块 = manifest(我是谁/插哪个口/从哪来)
+//! + index(渲染入口导出)。常驻模块住在频道里;将来 origin='repo' 的
+//! 模块躺代码仓库,人格体按需拉取部署。
+export const knowledgeRenderManifest = {
+ moduleId: "hololake.knowledge-render",
+ name: "知识渲染件",
+ version: "0.1.0",
+ slot: "knowledge-render",
+ origin: "resident",
+ exports: [
+ "splitFrontmatter",
+ "documentOutline",
+ "jumpToHeading",
+ "MarkdownDocument",
+ ],
+} as const;
+
+export type ModuleManifest = {
+ moduleId: string;
+ name: string;
+ version: string;
+ slot: string;
+ origin: "resident" | "repo";
+ exports: readonly string[];
+};
diff --git a/product-source/hololake-clean-desktop/src/styles.css b/product-source/hololake-clean-desktop/src/styles.css
index 4616e1067..b9f1aadca 100644
--- a/product-source/hololake-clean-desktop/src/styles.css
+++ b/product-source/hololake-clean-desktop/src/styles.css
@@ -459,6 +459,32 @@ main {
overflow: hidden;
background: rgba(5, 17, 31, 0.76);
}
+.knowledge-workspace.complete {
+ grid-template-columns: 260px minmax(0, 1fr) 232px;
+ height: min(720px, calc(100vh - 175px));
+ min-height: 560px;
+}
+.knowledge-workspace.complete.inspector-closed {
+ grid-template-columns: 260px minmax(0, 1fr);
+}
+.knowledge-browser {
+ min-height: 0;
+ overflow: auto;
+}
+.browser-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 2px 13px;
+}
+.browser-title strong {
+ font-size: 12px;
+ color: #cad8e6;
+}
+.browser-title span {
+ font-size: 10px;
+ color: #6f879e;
+}
.knowledge-workspace > aside {
border-right: 1px solid var(--line);
padding: 13px;
@@ -532,6 +558,197 @@ main {
font-family: "SFMono-Regular", Menlo, monospace;
font-size: 14px;
}
+.document-workspace {
+ min-height: 0;
+}
+.document-scroll {
+ min-height: 0;
+ overflow: auto;
+ padding: 42px clamp(28px, 5vw, 70px) 70px;
+}
+.reader-heading {
+ padding-bottom: 25px;
+ margin-bottom: 25px;
+ border-bottom: 1px solid var(--line);
+}
+.reader-heading h1 {
+ font-size: 34px;
+ margin: 0 0 10px;
+ letter-spacing: -0.025em;
+}
+.reader-heading p {
+ color: #8097ad;
+ font-size: 12px;
+ margin: 0;
+}
+.meta-tags {
+ display: flex;
+ gap: 7px;
+ margin-top: 14px;
+}
+.meta-tags span {
+ padding: 4px 8px;
+ border-radius: 7px;
+ color: #b8d8ef;
+ background: rgba(71, 139, 188, 0.14);
+ font-size: 10px;
+}
+.markdown-document {
+ color: #cbd8e6;
+ font-size: 15px;
+ line-height: 1.9;
+ overflow-wrap: anywhere;
+}
+.markdown-document h1,
+.markdown-document h2,
+.markdown-document h3,
+.markdown-document h4 {
+ color: #f0f5fb;
+ line-height: 1.35;
+ scroll-margin-top: 24px;
+}
+.markdown-document h1 {
+ font-size: 28px;
+ margin: 1.4em 0 0.6em;
+}
+.markdown-document h2 {
+ font-size: 22px;
+ margin: 1.7em 0 0.55em;
+}
+.markdown-document h3 {
+ font-size: 18px;
+ margin: 1.5em 0 0.5em;
+}
+.markdown-document p {
+ margin: 0 0 1.1em;
+}
+.markdown-document a,
+.markdown-document .wiki-link {
+ color: #84c9f3;
+}
+.markdown-document .wiki-link {
+ display: inline;
+ border: 0;
+ padding: 0 2px;
+ background: transparent;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+.markdown-document blockquote.callout {
+ margin: 1.3em 0;
+ padding: 14px 17px;
+ border-left: 3px solid #74bee9;
+ border-radius: 0 10px 10px 0;
+ background: rgba(72, 137, 181, 0.1);
+}
+.markdown-document blockquote.callout-amber {
+ border-left-color: var(--gold);
+ background: rgba(205, 153, 64, 0.1);
+}
+.markdown-document blockquote.callout-rose {
+ border-left-color: #df8991;
+ background: rgba(190, 83, 94, 0.1);
+}
+.markdown-document blockquote.callout-mint {
+ border-left-color: var(--green);
+ background: rgba(81, 170, 119, 0.1);
+}
+.markdown-document pre {
+ overflow: auto;
+ padding: 16px;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: rgba(1, 7, 14, 0.65);
+}
+.markdown-document code {
+ font-family: "SFMono-Regular", Menlo, monospace;
+ font-size: 0.9em;
+}
+.markdown-document table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1.4em 0;
+}
+.markdown-document th,
+.markdown-document td {
+ padding: 9px 11px;
+ border-bottom: 1px solid var(--line);
+ text-align: left;
+}
+.markdown-document img {
+ max-width: 100%;
+ border-radius: 12px;
+}
+.heading-flash {
+ animation: headingFlash 1.2s ease;
+}
+@keyframes headingFlash {
+ 0% {
+ color: var(--gold);
+ text-shadow: 0 0 18px rgba(240, 197, 107, 0.4);
+ }
+}
+.document-inspector {
+ min-height: 0;
+ overflow: auto;
+ border-left: 1px solid var(--line);
+ border-right: 0 !important;
+ padding: 0 !important;
+}
+.document-inspector > header {
+ padding: 18px 16px;
+ border-bottom: 1px solid var(--line);
+}
+.document-inspector > header strong,
+.document-inspector > header span {
+ display: block;
+}
+.document-inspector > header strong {
+ font-size: 13px;
+}
+.document-inspector > header span {
+ color: #72899f;
+ font-size: 10px;
+ margin-top: 4px;
+}
+.outline-list {
+ padding: 10px 8px 18px;
+ border-bottom: 1px solid var(--line);
+}
+.outline-list button {
+ display: block;
+ width: 100%;
+ border: 0;
+ background: transparent;
+ text-align: left;
+ color: #9fb2c5;
+ font-size: 11px;
+ line-height: 1.45;
+ padding-block: 7px;
+}
+.outline-list p {
+ color: #72899f;
+ font-size: 11px;
+ padding: 8px;
+}
+.document-evidence {
+ padding: 14px 16px;
+ margin: 0;
+}
+.document-evidence div {
+ margin-bottom: 13px;
+}
+.document-evidence dt {
+ color: #6f879e;
+ font-size: 10px;
+}
+.document-evidence dd {
+ color: #bdcddd;
+ font-size: 11px;
+ line-height: 1.5;
+ margin: 4px 0 0;
+ word-break: break-all;
+}
.split-list {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -848,6 +1065,13 @@ main {
.knowledge-workspace {
grid-template-columns: 210px 1fr;
}
+ .knowledge-workspace.complete,
+ .knowledge-workspace.complete.inspector-closed {
+ grid-template-columns: 210px minmax(0, 1fr);
+ }
+ .document-inspector {
+ display: none;
+ }
.split-list {
grid-template-columns: 1fr;
}
diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json
index 96a7a7b6b..330c5c9ee 100644
--- a/routing/hololake-current-architecture.json
+++ b/routing/hololake-current-architecture.json
@@ -10,6 +10,8 @@
"canonical_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-FOUNDATION.tcs",
"old_product_role": "READ_ONLY_DONOR_NO_WHOLE_MERGE",
"first_preinstalled_module": "HLP-MOD-KB-0001",
+ "knowledge_module_state": "LOCAL_COMPLETE_HUMAN_VISUAL_RENDERING_CREATE_READ_EDIT_IMPORT_EXPORT_TRASH_PRIVATE_GIT",
+ "knowledge_human_rendering_source": "product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx",
"typed_language_sources": ["USER_MESSAGE", "PERSONA_RESPONSE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"],
"public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json",
"public_update_release_state": "SERVER_HTTP_204_NO_PUBLIC_RELEASE",