diff --git a/docs/issues/README.md b/docs/issues/README.md
index 150b66e3..3a8efaf3 100644
--- a/docs/issues/README.md
+++ b/docs/issues/README.md
@@ -34,7 +34,7 @@
| 02 | A | 待办栏接数据 | ✅ 上游已实现 | 01 | 中 |
| 03 | A | 待办→证据跳转 | ✅ 已合并 PR#9 | 02 | 中 |
| 04 | A | Markdown 渲染 | ✅ 已合并 PR#10 | 01 | 低 |
-| 05 | A | 专家模式开关 | ⬜ | 01 | 低 |
+| 05 | A | 专家模式开关 | ✅ 单测+浏览器实证绿,待开 PR | 01 | 低 |
| 06 | A | 待回应分区(占位) | ⬜ | 02 | 中 |
| 07 | B | 本地去 Docker(路线A:07a–07c) | ✅ 已合并 PR#8 | 00 | 中 |
diff --git a/docs/issues/STEP-05-expert-mode.md b/docs/issues/STEP-05-expert-mode.md
index 37d08ca5..f63c43f7 100644
--- a/docs/issues/STEP-05-expert-mode.md
+++ b/docs/issues/STEP-05-expert-mode.md
@@ -1,7 +1,7 @@
# STEP-05:专家模式开关(收纳被砍视图)
- 线:A(前端三栏)
-- 状态:⬜ 未开始
+- 状态:✅ 单测+浏览器实证绿,待开 PR
- 依赖:STEP-01
- 对应 PR:`Claude/expert-mode`
@@ -38,8 +38,19 @@ revert 单 PR;专家入口消失,主三栏不受影响。
## 实际反馈(执行后由你回填)
-- 构建:
-- 测试:
-- 行为:
+- 构建:✅ `npm run build` 通过。
+- 测试:✅ `npm test` **127 文件 / 1352 用例全绿,0 回归**。新增 `test/viewer-expert-mode.test.ts` 5 用例(默认关→折回主三栏且无进阶组、`?expert=1` 放行隐藏 tab、localStorage 开渲染 9 个进阶按钮、`?expert=0` 覆盖 localStorage、renderExpertTabs 幂等不重复)。
+- 浏览器实证:`preview_eval` + `preview_inspect` 实跑——开启后 `#tab-expert-group` 渲染 9 个标签(记忆/经验/图谱/时间线/实时/档案/审计/回放/结晶),逐个 `switchTab` 到 9 个隐藏视图**均不抛错**、console 无 error;关闭后进阶组被移除(getElementById 返回 null)。
+- 实际改动(仅 `src/viewer/index.html`):
+ 1. 新增 `EXPERT_TABS`(9 项 id+label)、`expertModeEnabled()`(读 `?expert=1/0` 优先,否则 localStorage `viewer_expert_mode`)、`setExpertMode()`。
+ 2. `normalizeTab`:专家模式开 + tab 属 EXPERT_TABS → 放行;否则维持 TAB_REDIRECTS 折回。**TAB_IDS/TAB_REDIRECTS 常量未改**(默认行为零变化)。
+ 3. `renderExpertTabs()`:幂等渲染进阶按钮组到 `.tab-main`;`toggleExpertMode()`:翻转+重渲染,关闭时若停在隐藏视图则折回 dashboard。
+ 4. 导航 `.tab-side-meta` 加「专家」开关按钮(`#expert-toggle`,`aria-pressed`);tab-bar 点击委托新增 `#expert-toggle` 分支。
+ 5. boot 处调 `renderExpertTabs()`。
+- **清理(STEP-01 遗留死代码)**:删除 `switchTab` 里对已不存在的 `tab-extra`/`tab-extra-toggle` DOM 的死引用块、未用的 `advancedTabs` 局部变量;tab-bar 点击委托里移除旧 `#tab-extra-toggle` 分支(整段替换为 `#expert-toggle`)。
- 与预测的差异:
-- 下一步影响:
+ - 预测「放行隐藏视图可能首次暴露既有运行时错误」——**实测 9 个视图 switchTab 均不抛错、无 console error**,数据格式未漂移。比预期顺利。
+ - 一处源码健壮性修复:`renderExpertTabs` 移除旧组时加 `existing.parentNode` 判空(原 `existing.parentNode.removeChild` 在 parentNode 缺失时会炸),顺带让 STEP-03/04 等共用沙箱测试的 boot 期 `renderExpertTabs()` 不受影响。
+- 下一步影响(下游待更新清单):
+ - 无对 STEP-06 的代码影响。
+ - 被收纳视图里若展示 Agent 文本,可复用 STEP-04 的 `renderMarkdownSafe`(目前未接,各视图维持原渲染)。
diff --git a/src/viewer/index.html b/src/viewer/index.html
index e1b55257..4496ab9a 100644
--- a/src/viewer/index.html
+++ b/src/viewer/index.html
@@ -220,6 +220,42 @@
background: var(--bg-alt);
}
.tab-bar button.active .tab-icon { opacity: 1; }
+ .tab-expert-group {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ margin-top: 6px;
+ padding-top: 6px;
+ border-top: 1px dashed var(--border, #d8dce1);
+ }
+ .tab-expert-group::before {
+ content: "进阶";
+ font-size: 9px;
+ letter-spacing: 0.1em;
+ color: var(--ink-faint, #9aa0a6);
+ padding: 2px 10px 4px;
+ text-transform: uppercase;
+ }
+ .tab-expert-btn .tab-icon { display: none; }
+ .expert-toggle {
+ appearance: none;
+ border: 1px solid var(--border, #d8dce1);
+ background: none;
+ color: var(--ink-muted, #6b7280);
+ font-family: var(--font-ui);
+ font-size: 10px;
+ letter-spacing: 0.06em;
+ padding: 4px 8px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
+ }
+ .expert-toggle:hover { color: var(--ink); border-color: var(--border-heavy, #b8bdc4); }
+ .expert-toggle[aria-pressed="true"] {
+ color: var(--ink);
+ background: var(--bg-alt);
+ border-color: var(--border-heavy, #b8bdc4);
+ }
.tab-extra-toggle {
margin: 4px 8px;
padding: 6px 8px;
@@ -3614,6 +3650,7 @@
+
@@ -3743,6 +3780,63 @@
灵感记忆台
var CB_STATE_COLORS = { closed: 'badge-green', open: 'badge-red', 'half-open': 'badge-yellow' };
var TAB_IDS = ['dashboard', 'actions', 'sessions'];
var TAB_REDIRECTS = { memories: 'dashboard', lessons: 'dashboard', activity: 'sessions', graph: 'dashboard', profile: 'dashboard', audit: 'dashboard', replay: 'dashboard', timeline: 'sessions', crystals: 'dashboard' };
+ // 专家模式:默认三栏干净;开启后在导航末尾恢复被砍/被隐藏的视图(后端资产不丢)。
+ // 这些视图的 view 容器与渲染函数都还在文件里,本步只是放行入口。
+ var EXPERT_TABS = [
+ { id: 'memories', label: '记忆' },
+ { id: 'lessons', label: '经验' },
+ { id: 'graph', label: '图谱' },
+ { id: 'timeline', label: '时间线' },
+ { id: 'activity', label: '实时' },
+ { id: 'profile', label: '档案' },
+ { id: 'audit', label: '审计' },
+ { id: 'replay', label: '回放' },
+ { id: 'crystals', label: '结晶' }
+ ];
+ function expertModeEnabled() {
+ try {
+ var q = new URLSearchParams(window.location.search);
+ if (q.get('expert') === '1') return true;
+ if (q.get('expert') === '0') return false;
+ } catch (_) {}
+ try { return localStorage.getItem('viewer_expert_mode') === '1'; } catch (_) { return false; }
+ }
+ function setExpertMode(on) {
+ try { localStorage.setItem('viewer_expert_mode', on ? '1' : '0'); } catch (_) {}
+ }
+ // 把被隐藏视图的按钮渲染进导航(仅专家模式)。幂等:每次先清掉旧的再按需重建。
+ function renderExpertTabs() {
+ var bar = document.getElementById('tab-bar');
+ if (!bar) return;
+ var on = expertModeEnabled();
+ var existing = document.getElementById('tab-expert-group');
+ if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
+ var toggle = document.getElementById('expert-toggle');
+ if (toggle) toggle.setAttribute('aria-pressed', on ? 'true' : 'false');
+ if (!on) return;
+ var main = bar.querySelector('.tab-main');
+ if (!main) return;
+ var group = document.createElement('span');
+ group.id = 'tab-expert-group';
+ group.className = 'tab-expert-group';
+ EXPERT_TABS.forEach(function(t) {
+ var b = document.createElement('button');
+ b.setAttribute('data-tab', t.id);
+ b.className = 'tab-expert-btn';
+ b.textContent = t.label;
+ group.appendChild(b);
+ });
+ main.appendChild(group);
+ }
+ function toggleExpertMode() {
+ var next = !expertModeEnabled();
+ setExpertMode(next);
+ renderExpertTabs();
+ // 关闭时若当前停在被隐藏视图,折回总览
+ if (!next && EXPERT_TABS.some(function(t) { return t.id === state.activeTab; })) {
+ switchTab('dashboard');
+ }
+ }
var LOCAL_SKILLS = [{"name":"academic-cv-builder","root":"Agents","path":"~/.agents/skills/academic-cv-builder/SKILL.md"},{"name":"career-changer-translator","root":"Agents","path":"~/.agents/skills/career-changer-translator/SKILL.md"},{"name":"cover-letter-generator","root":"Agents","path":"~/.agents/skills/cover-letter-generator/SKILL.md"},{"name":"creative-portfolio-resume","root":"Agents","path":"~/.agents/skills/creative-portfolio-resume/SKILL.md"},{"name":"executive-resume-writer","root":"Agents","path":"~/.agents/skills/executive-resume-writer/SKILL.md"},{"name":"find-skills","root":"Agents","path":"~/.agents/skills/find-skills/SKILL.md"},{"name":"guizang-social-card-skill","root":"Agents","path":"~/.agents/skills/guizang-social-card-skill/SKILL.md"},{"name":"huashu-design","root":"Agents","path":"~/.agents/skills/huashu-design/SKILL.md"},{"name":"interview-prep-generator","root":"Agents","path":"~/.agents/skills/interview-prep-generator/SKILL.md"},{"name":"job-description-analyzer","root":"Agents","path":"~/.agents/skills/job-description-analyzer/SKILL.md"},{"name":"linkedin-profile-optimizer","root":"Agents","path":"~/.agents/skills/linkedin-profile-optimizer/SKILL.md"},{"name":"offer-comparison-analyzer","root":"Agents","path":"~/.agents/skills/offer-comparison-analyzer/SKILL.md"},{"name":"pdf","root":"Agents","path":"~/.agents/skills/pdf/SKILL.md"},{"name":"portfolio-case-study-writer","root":"Agents","path":"~/.agents/skills/portfolio-case-study-writer/SKILL.md"},{"name":"post-to-xhs","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/post-to-xhs/SKILL.md"},{"name":"reference-list-builder","root":"Agents","path":"~/.agents/skills/reference-list-builder/SKILL.md"},{"name":"resume-ats-optimizer","root":"Agents","path":"~/.agents/skills/resume-ats-optimizer/SKILL.md"},{"name":"resume-bullet-writer","root":"Agents","path":"~/.agents/skills/resume-bullet-writer/SKILL.md"},{"name":"resume-formatter","root":"Agents","path":"~/.agents/skills/resume-formatter/SKILL.md"},{"name":"resume-quantifier","root":"Agents","path":"~/.agents/skills/resume-quantifier/SKILL.md"},{"name":"resume-section-builder","root":"Agents","path":"~/.agents/skills/resume-section-builder/SKILL.md"},{"name":"resume-tailor","root":"Agents","path":"~/.agents/skills/resume-tailor/SKILL.md"},{"name":"resume-version-manager","root":"Agents","path":"~/.agents/skills/resume-version-manager/SKILL.md"},{"name":"salary-negotiation-prep","root":"Agents","path":"~/.agents/skills/salary-negotiation-prep/SKILL.md"},{"name":"setup-xhs-mcp","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/setup-xhs-mcp/SKILL.md"},{"name":"social-media-marketing","root":"Agents","path":"~/.agents/skills/social-media-marketing/SKILL.md"},{"name":"tech-resume-optimizer","root":"Agents","path":"~/.agents/skills/tech-resume-optimizer/SKILL.md"},{"name":"weread-skills","root":"Agents","path":"~/.agents/skills/weread-skills/SKILL.md"},{"name":"x-twitter-growth","root":"Agents","path":"~/.agents/skills/x-twitter-growth/SKILL.md"},{"name":"xhs-content-plan","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-content-plan/SKILL.md"},{"name":"xhs-explore","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-explore/SKILL.md"},{"name":"xhs-interact","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-interact/SKILL.md"},{"name":"xhs-login","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-login/SKILL.md"},{"name":"xhs-profile","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-profile/SKILL.md"},{"name":"xhs-search","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-search/SKILL.md"},{"name":"xiaohongshu","root":"Agents","path":"~/.agents/skills/xiaohongshu/SKILL.md"},{"name":"academic-cv-builder","root":"Codex","path":"~/.codex/skills/academic-cv-builder/SKILL.md"},{"name":"aihot","root":"Codex","path":"~/.codex/skills/aihot/SKILL.md"},{"name":"andrej-karpathy-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/andrej-karpathy-perspective/SKILL.md"},{"name":"buddyup-next","root":"Codex","path":"~/.codex/skills/buddyup-next/SKILL.md"},{"name":"buddyup-study-abroad-assistant","root":"Codex","path":"~/.codex/skills/buddyup-study-abroad-assistant/SKILL.md"},{"name":"buddyup-xiaohongshu-growth","root":"Codex","path":"~/.codex/skills/buddyup-xiaohongshu-growth/SKILL.md"},{"name":"cai-life-skill","root":"Codex","path":"~/.codex/skills/cai-life-skill/SKILL.md"},{"name":"career-changer-translator","root":"Codex","path":"~/.codex/skills/career-changer-translator/SKILL.md"},{"name":"chatgpt-apps","root":"Codex","path":"~/.codex/skills/chatgpt-apps/SKILL.md"},{"name":"cli-creator","root":"Codex","path":"~/.codex/skills/cli-creator/SKILL.md"},{"name":"cocoloop-main","root":"Codex","path":"~/.codex/skills/cocoloop-main/SKILL.md"},{"name":"cover-letter-generator","root":"Codex","path":"~/.codex/skills/cover-letter-generator/SKILL.md"},{"name":"creative-portfolio-resume","root":"Codex","path":"~/.codex/skills/creative-portfolio-resume/SKILL.md"},{"name":"douban-sync-skill","root":"Codex","path":"~/.codex/skills/douban-sync-skill/SKILL.md"},{"name":"elon-musk-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/elon-musk-perspective/SKILL.md"},{"name":"executive-resume-writer","root":"Codex","path":"~/.codex/skills/executive-resume-writer/SKILL.md"},{"name":"feishu-research-docs","root":"Codex","path":"~/.codex/skills/feishu-research-docs/SKILL.md"},{"name":"feynman-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/feynman-perspective/SKILL.md"},{"name":"figma-code-connect-components","root":"Codex","path":"~/.codex/skills/figma-code-connect-components/SKILL.md"},{"name":"figma-create-design-system-rules","root":"Codex","path":"~/.codex/skills/figma-create-design-system-rules/SKILL.md"},{"name":"figma","root":"Codex","path":"~/.codex/skills/figma/SKILL.md"},{"name":"gh-address-comments","root":"Codex","path":"~/.codex/skills/gh-address-comments/SKILL.md"},{"name":"hv-analysis","root":"Codex","path":"~/.codex/skills/hv-analysis/SKILL.md"},{"name":"ilya-sutskever-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/ilya-sutskever-perspective/SKILL.md"},{"name":"imagegen","root":"Codex","path":"~/.codex/skills/.system/imagegen/SKILL.md"},{"name":"interview-prep-generator","root":"Codex","path":"~/.codex/skills/interview-prep-generator/SKILL.md"},{"name":"job-description-analyzer","root":"Codex","path":"~/.codex/skills/job-description-analyzer/SKILL.md"},{"name":"kevin-kelly-perspective","root":"Codex","path":"~/.codex/skills/kevin-kelly-perspective/SKILL.md"},{"name":"khazix-writer","root":"Codex","path":"~/.codex/skills/khazix-writer/SKILL.md"},{"name":"laws-of-ux-2","root":"Codex","path":"~/.codex/skills/laws-of-ux-2/SKILL.md"},{"name":"linkedin-profile-optimizer","root":"Codex","path":"~/.codex/skills/linkedin-profile-optimizer/SKILL.md"},{"name":"mrbeast-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/mrbeast-perspective/SKILL.md"},{"name":"munger-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/munger-perspective/SKILL.md"},{"name":"naval-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/naval-perspective/SKILL.md"},{"name":"neat-freak","root":"Codex","path":"~/.codex/skills/neat-freak/SKILL.md"},{"name":"nuwa-skill","root":"Codex","path":"~/.codex/skills/nuwa-skill/SKILL.md"},{"name":"offer-comparison-analyzer","root":"Codex","path":"~/.codex/skills/offer-comparison-analyzer/SKILL.md"},{"name":"openai-docs","root":"Codex","path":"~/.codex/skills/.system/openai-docs/SKILL.md"},{"name":"paul-graham-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/paul-graham-perspective/SKILL.md"},{"name":"playwright","root":"Codex","path":"~/.codex/skills/playwright/SKILL.md"},{"name":"plugin-creator","root":"Codex","path":"~/.codex/skills/.system/plugin-creator/SKILL.md"},{"name":"portfolio-case-study-writer","root":"Codex","path":"~/.codex/skills/portfolio-case-study-writer/SKILL.md"},{"name":"proactive-everme","root":"Codex","path":"~/.codex/skills/proactive-everme/SKILL.md"},{"name":"reference-list-builder","root":"Codex","path":"~/.codex/skills/reference-list-builder/SKILL.md"},{"name":"resume-ats-optimizer","root":"Codex","path":"~/.codex/skills/resume-ats-optimizer/SKILL.md"},{"name":"resume-bullet-writer","root":"Codex","path":"~/.codex/skills/resume-bullet-writer/SKILL.md"},{"name":"resume-formatter","root":"Codex","path":"~/.codex/skills/resume-formatter/SKILL.md"},{"name":"resume-quantifier","root":"Codex","path":"~/.codex/skills/resume-quantifier/SKILL.md"},{"name":"resume-section-builder","root":"Codex","path":"~/.codex/skills/resume-section-builder/SKILL.md"},{"name":"resume-tailor","root":"Codex","path":"~/.codex/skills/resume-tailor/SKILL.md"},{"name":"resume-version-manager","root":"Codex","path":"~/.codex/skills/resume-version-manager/SKILL.md"},{"name":"salary-negotiation-prep","root":"Codex","path":"~/.codex/skills/salary-negotiation-prep/SKILL.md"},{"name":"self-improvement","root":"Codex","path":"~/.codex/skills/self-improvement/SKILL.md"},{"name":"skill-creator","root":"Codex","path":"~/.codex/skills/.system/skill-creator/SKILL.md"},{"name":"skill-installer","root":"Codex","path":"~/.codex/skills/.system/skill-installer/SKILL.md"},{"name":"speech","root":"Codex","path":"~/.codex/skills/speech/SKILL.md"},{"name":"steve-jobs-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/steve-jobs-perspective/SKILL.md"},{"name":"storage-analyzer","root":"Codex","path":"~/.codex/skills/storage-analyzer/SKILL.md"},{"name":"sun-yuchen-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/sun-yuchen-perspective/SKILL.md"},{"name":"taleb-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/taleb-perspective/SKILL.md"},{"name":"taught-master-applications","root":"Codex","path":"~/.codex/skills/taught-master-applications/SKILL.md"},{"name":"taught-master-applications","root":"Codex","path":"~/.codex/skills/taught-master-applications/taught-master-applications/SKILL.md"},{"name":"tech-resume-optimizer","root":"Codex","path":"~/.codex/skills/tech-resume-optimizer/SKILL.md"},{"name":"trump-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/trump-perspective/SKILL.md"},{"name":"x-mastery-mentor","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/x-mastery-mentor/SKILL.md"},{"name":"zhang-yiming-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/zhang-yiming-perspective/SKILL.md"},{"name":"zhangxuefeng-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/zhangxuefeng-perspective/SKILL.md"},{"name":"agentmemory-import","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory-import/SKILL.md"},{"name":"agentmemory-understand","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory-understand/SKILL.md"},{"name":"agentmemory","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory/SKILL.md"},{"name":"audit","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/audit/SKILL.md"},{"name":"control-in-app-browser","root":"Plugin","path":"~/.codex/plugins/cache/openai-bundled/browser/26.601.21317/skills/control-in-app-browser/SKILL.md"},{"name":"design-qa","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/design-qa/SKILL.md"},{"name":"documents","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/documents/26.601.10930/skills/documents/SKILL.md"},{"name":"everme-memory","root":"Plugin","path":"~/.codex/plugins/cache/everme/everme/0.4.0/skills/everme-memory/SKILL.md"},{"name":"figma-code-connect","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-code-connect/SKILL.md"},{"name":"figma-create-new-file","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-create-new-file/SKILL.md"},{"name":"figma-generate-design","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-design/SKILL.md"},{"name":"figma-generate-diagram","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-diagram/SKILL.md"},{"name":"figma-generate-library","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-library/SKILL.md"},{"name":"figma-use-figjam","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use-figjam/SKILL.md"},{"name":"figma-use-slides","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use-slides/SKILL.md"},{"name":"figma-use","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use/SKILL.md"},{"name":"get-context","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/get-context/SKILL.md"},{"name":"ideate","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/ideate/SKILL.md"},{"name":"image-to-code","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/image-to-code/SKILL.md"},{"name":"index","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/index/SKILL.md"},{"name":"presentations","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/presentations/26.601.10930/skills/presentations/SKILL.md"},{"name":"prototype","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/prototype/SKILL.md"},{"name":"research","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/research/SKILL.md"},{"name":"share","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/share/SKILL.md"},{"name":"spreadsheets","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/spreadsheets/26.601.10930/skills/spreadsheets/SKILL.md"},{"name":"understand-chat","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-chat/SKILL.md"},{"name":"understand-chat","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-chat/SKILL.md"},{"name":"understand-dashboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-dashboard/SKILL.md"},{"name":"understand-dashboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-dashboard/SKILL.md"},{"name":"understand-diff","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-diff/SKILL.md"},{"name":"understand-diff","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-diff/SKILL.md"},{"name":"understand-domain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-domain/SKILL.md"},{"name":"understand-domain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-domain/SKILL.md"},{"name":"understand-explain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-explain/SKILL.md"},{"name":"understand-explain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-explain/SKILL.md"},{"name":"understand-knowledge","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-knowledge/SKILL.md"},{"name":"understand-knowledge","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-knowledge/SKILL.md"},{"name":"understand-onboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-onboard/SKILL.md"},{"name":"understand-onboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-onboard/SKILL.md"},{"name":"understand","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand/SKILL.md"},{"name":"understand","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand/SKILL.md"},{"name":"url-to-code","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/url-to-code/SKILL.md"},{"name":"user-context","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/user-context/SKILL.md"}];
@@ -4931,6 +5025,10 @@
灵感记忆台
function normalizeTab(tab) {
var normalized = String(tab || '').replace(/^#/, '').toLowerCase();
+ // 专家模式下,被隐藏视图放行;否则按 TAB_REDIRECTS 折回主三栏。
+ if (expertModeEnabled() && EXPERT_TABS.some(function(t) { return t.id === normalized; })) {
+ return normalized;
+ }
if (TAB_REDIRECTS[normalized]) return TAB_REDIRECTS[normalized];
return TAB_IDS.indexOf(normalized) >= 0 ? normalized : 'dashboard';
}
@@ -4956,7 +5054,6 @@
灵感记忆台
function switchTab(tab, opts) {
opts = opts || {};
tab = normalizeTab(tab);
- var advancedTabs = { timeline: true, lessons: true, actions: true, audit: true, replay: true };
if (state.activeTab === 'replay' && tab !== 'replay' && typeof stopReplayTimer === 'function') {
stopReplayTimer();
}
@@ -4976,13 +5073,6 @@
灵感记忆台
document.querySelectorAll('.view').forEach(function(v) {
v.classList.toggle('active', v.id === 'view-' + tab);
});
- var extraPanel = document.getElementById('tab-extra');
- var extraToggle = document.getElementById('tab-extra-toggle');
- if (extraPanel && extraToggle) {
- extraPanel.classList.add('open');
- extraToggle.setAttribute('aria-expanded', 'true');
- extraToggle.setAttribute('aria-label', '进阶功能已展开');
- }
if (state.flagsConfig) renderFlagBanners(state.flagsConfig);
loadTab(tab);
}
@@ -8672,21 +8762,16 @@
灵感记忆台
}
document.getElementById('tab-bar').addEventListener('click', function(e) {
+ var toggle = e.target instanceof Element ? e.target.closest('#expert-toggle') : null;
+ if (toggle) {
+ toggleExpertMode();
+ return;
+ }
var btn = e.target instanceof Element ? e.target.closest('button[data-tab]') : null;
if (btn) {
switchTab(btn.dataset.tab);
return;
}
- var toggle = e.target instanceof Element ? e.target.closest('#tab-extra-toggle') : null;
- if (toggle) {
- var panel = document.getElementById('tab-extra');
- if (!panel) return;
- var willOpen = !panel.classList.contains('open');
- panel.classList.toggle('open', willOpen);
- toggle.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
- toggle.setAttribute('aria-label', willOpen ? '收起进阶功能' : '展开进阶功能');
- try { localStorage.setItem('viewer_tab_extra_open', willOpen ? '1' : '0'); } catch {}
- }
});
document.addEventListener('click', function(e) {
@@ -9379,6 +9464,7 @@
灵感记忆台
}
});
+ renderExpertTabs();
switchTab(tabFromRoute(), { replaceRoute: true });
connectWs();
startDashboardAutoRefresh();
diff --git a/test/viewer-expert-mode.test.ts b/test/viewer-expert-mode.test.ts
new file mode 100644
index 00000000..fe92825b
--- /dev/null
+++ b/test/viewer-expert-mode.test.ts
@@ -0,0 +1,220 @@
+import * as vm from "node:vm";
+import { describe, expect, it } from "vitest";
+import { renderViewerDocument } from "../src/viewer/document.js";
+
+// STEP-05: 专家模式开关。默认三栏干净;开启后放行被隐藏视图并在导航末尾
+// 渲染进阶按钮。这些用例锁定:① 默认关 → normalizeTab 折回主三栏、无 expert 组
+// ② ?expert=1 / localStorage 开 → 被隐藏 tab 放行、渲染 9 个进阶按钮。
+
+function loadViewerSandbox(opts) {
+ opts = opts || {};
+ const rendered = renderViewerDocument();
+ if (!rendered.found) throw new Error("viewer document not found");
+ const scriptMatch = rendered.html.match(
+ /