Commit Graph
333 Commits
Author SHA1 Message Date
Atlas Zeng 4631a02db0 Merge pull request #360 from omdsh-dev/feat/markdown-html-toc
feat(editor): Markdown 预览内嵌 HTML 安全渲染 + 目录大纲(TOC)
2026-08-24 14:31:58 +08:00
Menghuan1918 2b757cfe24 fix(test): 测试间状态串台与 macOS 临时目录路径导致的用例失败
两处既有测试基建问题(在慢 runner 的 CI 上表现为 free-window 用例
间歇性失败,阻塞 PR 合并):

- free-window/sidebar-crash/bottom-auto-terminal 三个 spec 共用 session
  id 's1':store 会按 200ms 防抖把每会话状态持久化到
  localStorage['dsh-sidebar:v1:s1'],前一个用例的迟写(drop 点 400,300
  → 浮窗 left=205)被下一用例 setSession 恢复,新浮窗断言(期望 317)
  被 querySelector 拿到的旧窗口炸掉——用例间隔是否超过 200ms 决定成
  败,本地快时常过、CI 慢必挂。修复:挂载 helper 缺省生成唯一
  session id(持久化往返用例显式传固定 id),afterEach 在 unstub 之后
  清 localStorage 兜底
- git/git-worktree 两个用例在 macOS 上必挂:tmpdir() 返回 /var 符号
  链接而 git 报告解析后的 /private/var 前缀,路径比较与
  resolveWorktree 的恒等匹配失配(CI 的 Linux 路径规范所以一直绿)。
  修复:期望值与传入 git API 的路径统一 realpathSync 规范化

本地全量 vitest 963 通过 0 失败(此前 free-window 间歇 0~2 失败 +
git 2 个环境性失败);free-window 单文件连跑 8 次稳定。
2026-08-24 14:18:18 +08:00
Menghuan1918 8e0ef7a175 merge: 合入 main(PR #353 agent-open-tools),AGENTS.md 参考实现清单两条例目并存 2026-08-24 13:58:58 +08:00
Atlas Zeng 4c0da8119b Merge pull request #353 from omdsh-dev/feat/agent-open-tools
feat: 侧边栏全局设置「模型主动打开」——sidebar_open 工具(默认关闭)
2026-08-24 12:27:02 +08:00
Menghuan1918 7f634866bf feat(editor): Markdown 预览内嵌 HTML 安全渲染 + 目录大纲(TOC)
宿主 MarkdownText 出于聊天安全把 raw HTML 按字面文本渲染,预览 GitHub
风格 README(徽章墙 div、details 折叠块内嵌 markdown、表格单元格内联
标签、video/picture)时全是源码文本。本次在不动渲染器的前提下:

- markdown-html.ts(纯函数):fence 感知切分器把文档切成 markdown/html
  段(CommonMark type-6 块级标签名单 + 注释段 + 空行终止);段内标签
  tokenizer 归约 open/close/html parts;全文档引用定义收集(每段追加
  保持 [text][id] 跨段语义)
- MarkdownHtml.tsx:DOMPurify 白名单消毒(显式禁 script/iframe/form
  等;a 强制 _blank+noopener;本地媒体 src 经 /sidebar/file 重写);
  MarkdownDocument 帧栈状态机让 <details> 跨段嵌套内部 markdown;
  MarkdownSegment 内联替换(MutationObserver 幂等 pass,把表格单元格
  等处标签文本消毒还原为元素,纯散文不误伤)
- md-toc.tsx:零高度 sticky 浮动目录按钮(≥3 标题)+ 弹出大纲,跳转
  平滑滚动 + 自动展开折叠 details + 高亮闪现。关键修复:子组件读父
  ref 在 React layout 阶段恒为 null(子先于父),改为经自身 bar ref
  的 parentElement 定位容器(jsdom 单测传预填 ref 掩盖了该 bug,仅
  真实挂载 e2e 抓到)
- 纯 markdown 文档走原单次解析路径零变化(现有 mermaid e2e 守卫一字
  不动);新增 readme-style.md 种子 + 探针(消毒渲染/嵌套/内联/TOC
  全链路);19 语言词典新增 toc 键;dompurify 提升为直接依赖(与
  mermaid 传递依赖去重单版本)

设计文档 docs/plans/2026-08-24-markdown-html-toc-design.md
2026-08-24 12:05:14 +08:00
Menghuan1918 68640101e1 feat: sidebar_open tool gates model-driven sidebar opens behind a global setting
New global Side card setting `agentOpenTools` (default off, 常规 group):
when enabled, one `sidebar_open` tool is injected into the model so it can
actively open a local file, a local folder (tree rooted there), or an
HTTP(S) page in the calling session's sidebar.

- host: AgentOpenRegistry + registerOpenTool (src/agent-opens.ts); the tool
  classifies http(s) URLs / stat-driven file/folder paths, refuses other
  schemes, resolves relative paths against the session cwd, and reports
  target-tab-disabled instead of silently no-oping. Gated like
  agentTerminalTools (register/drain on settings watch).
- delivery: /sidebar/ws/agent-opens push (consume-on-send queue, replay on
  attach); the tool result reports `delivered` so queued opens for
  non-active sessions are honest.
- client: Sidebar subscribes and routes pushes to editor (file), folder
  window (meta.dir, tree rooted at the folder — EditorHost), or browser
  (URL) tabs; settings row in 常规 + 20 locale dictionaries.
- tests: agent-opens registry/tool spec, smoke gating + WS route list,
  prefs/plugin-shape defaults, editor-host folder window, side-card rows.
- docs: design doc, AGENTS.md key lists + reference, README feature bullets.
2026-08-24 01:19:43 +08:00
Atlas Zeng 36a4119253 Merge pull request #354 from omdsh-dev/feat/free-window
feat(ui): 自由窗口——tab 拖出侧边栏成为可移动/缩放/置顶的浮动窗口 (v0.16.0)
2026-08-24 01:12:37 +08:00
Menghuan1918 d15645bf23 feat(ui): 文件二级页面间距规整(8px 网格)+ 浮窗默认尺寸改手机竖屏比例
- 文件页 chrome 行统一:路径行与树搜索行同为 41px 高(路径输入框改
  border-box 28px 总高)、gap 统一 6px、内边距统一 6px 8px
- 树体侧距 6→8px 对齐 chrome 行;markdown 预览 10/14→12/16(与错误
  文案块一致);CodeMirror scroller 新增同款 12/16 内边距(预览/编辑
  切换文字位置不跳);截断横幅 12→8px
- 浮窗默认尺寸 480×360 → 390×780(1:2 手机竖屏比例),创建时先按
  视口减 24px 钳制再居中,矮视口得到更矮的窗而非溢出
- 测试/文档同步:state/free-window 预期重算(含 clampMove 回推的
  y=24 语义)、e2e 断言宽度上界、AGENTS §7.5 与设计文档
2026-08-24 00:59:27 +08:00
Atlas Zeng b7229aec67 Merge pull request #351 from HuanLinOTO/merge/bug-pr-train
merge: bug PR train (12 PRs, --no-ff to preserve authors)
2026-08-24 00:43:39 +08:00
Menghuan1918 f648b0c006 fix(ui): 浮窗内容区改为列方向 flex 契约,修复插件 tab 内容 0 宽白窗
.floatContent 原为行方向 flex——弹性子项主尺寸(宽)按内容收缩,内在宽度为
0 的插件宿主(如深迹 DeepTrace)塌成 0px 宽:DOM 在、内容画不出(可见白窗
但 DevTools 有内容)。对齐 pane 的内容契约:.floatContent 复制 .paneContent
的列方向 + overflow hidden,浮窗内再包一层 .paneTab(pane 的 per-tab 包装,
flex:1),子项宽度由 align-stretch 撑满——浮窗里的 tab 获得与 pane 内完全
一致的尺寸契约。e2e 增加内容绘制断言(浮动后内容子项实际宽度 > 200px),
真实浏览器回归。
2026-08-24 00:35:58 +08:00
Menghuan1918 e20b95024b fix(ui): 浮窗内 tab 的 visible 恒为 true,不再跟随面板开合
renderTab 的可见性原为 panelOpen && active——浮窗内容随侧边栏收起被判定为
不可见,遵守 visible 契约(不可见时暂停/空渲染)的消费插件 tab 会在浮窗里
白屏。改为 placement 三态(top/bottom/float),float 恒 true(兑现 AGENTS
§7.5 契约);回归测试断言面板收起后浮窗内的注册插件 tab 仍收到
visible=true。
2026-08-24 00:01:41 +08:00
Menghuan1918 f17a1b30ac feat(ui): 自由窗口——tab 拖出侧边栏成为可移动/缩放/置顶的浮动窗口 (v0.16.0)
- 拖出:标签栏 tab 拖到主会话列(HTML5 DnD 检测 + 虚线提示浮层)或右键菜单
  「移动到自由窗口」(右/底面板共用 TabBar),tab 从 pane 移动进 SidebarState.floats
- 窗口(FreeWindow,渲染于 [data-dsh-panel-host] z-42):头部 pointer 拖动
  (rAF 直写 + 松手提交)、拖到 pane 高亮停靠(center 合并)、SE 角缩放、
  点击置顶、头部右键(回到侧边栏/关闭)、X = closeTab 走正常生命周期
- 服务语义:openTab dedupe/id 聚焦浮动 tab = 置顶窗口(不展开面板);
  closeTab/activateTab 兼顾 floats;agent 终端 reconcile 覆盖浮窗;
  features 单调新增 'floatWindows',版本 0.16.0
- 持久化:floats 随会话 localStorage,sanitizeState 宽容校验 + 几何钳制,
  uid 播种覆盖 float 前缀;i18n 三键 19 语言同步
- 关键修复(e2e 发现):portal 覆盖层(头部 Menu)的合成事件沿 React 树
  冒泡回拖拽表面,劫持菜单行点击——拖拽起点加 DOM 同源守卫(jsdom 不复现)
- e2e-mount.sh:缺省 tarball 改取 mtime 最新(旧字典序选择会把冒烟挂到
  过期产物);新增 tests/e2e/float-window.e2e.ts 双用例
2026-08-23 23:44:18 +08:00
HuanLinOTO 751fb954a2 fix: address Copilot review (9 comments)
#1 GitView: hide/disable 'open in editor' for external linked worktrees
   (path outside session workspace is rejected by the fence); add
   isWithinWorkspace client helper for the gate.
#2 DiffTab: untracked diff read resolves against diff.repoRoot (child-repo
   paths are repo-root-relative, not cwd-relative); fs.read route threads
   selectedRepoOf into resolveGitPath so the host resolves the right base.
#3 GitView: chooseRepo handler mirrors chooseWorktree — clears status/
   history/log and bumps generation before async refresh, preventing stale
   rows from acting on the newly selected repository.
#4 git.ts: repoRoots/repoRoot compare roots via pathIdentity() (platform-
   aware) instead of raw includes(), fixing win32 forward/backslash
   mismatch that silently fell back to roots[0].
#5 markdown-images: mask fenced code blocks and inline code spans before
   rewriting; only rewrite reference definitions whose label is actually
   referenced by image syntax (not plain links). +3 tests.
#6 package.json: downgrade unrun ^0.3.1 → ^0.2.39 (0.3.x requires Node
   ^22.13, incompatible with declared engines >=20).
#7 market-manifest.spec: shell:true on win32 so execFileSync resolves the
   pnpm .cmd shim.
#8 smoke.spec: build html-route URL via encodeHtmlUrl() (production
   encoder) so the Windows drive separator is correct.
#9 index.ts git.worktrees route: thread selectedRepoOf so a workspace
   container's selected child repo's linked worktrees appear.
2026-08-23 22:02:47 +08:00
HuanLinOTO 9410e5bb2a fix(i18n): add worktree translation to all 19 third-language dicts (#285)
PR #285 added the zh/en 'worktree' key but the 19 third-language dicts
(de/fr/pt/ko/ar/hi/id/tr/vi/th/ru/it/nl/sv/pl/zhHK/zhTW/zhMO) were missing
it — only ja was patched in the prior commit. The dicts are typed
Record<keyof typeof zh, string> via an unchecked 'as' cast, so tsc did
not catch the gap.

This commit:
- adds the worktree translation to all 19 dicts
- strengthens tests/locales.spec.ts to assert key-set equality for EVERY
  shipped dictionary (not just zh/en/ja), preventing future regressions
2026-08-23 20:58:33 +08:00
HuanLinOTO 0a21629fa3 test(market-manifest): raise pack timeout to 180s (#338)
pnpm pack triggers prepublishOnly: pnpm build (~60s tsdown rebuild);
the default 5s vitest timeout expired on CI.
2026-08-23 20:52:14 +08:00
HuanLinOTO e07d4ba5dd fix(i18n): add ja translation for worktree key (#285) 2026-08-23 20:39:46 +08:00
HuanLinOTO 72796ee46a chore: sync pnpm-lock.yaml for unrun devDep (#336) 2026-08-23 20:29:35 +08:00
HuanLinOTO 5951bae1ad integrate train/misc: mermaid + layout-push + fs-search + markdown-images 2026-08-23 20:28:49 +08:00
HuanLinOTO b4118a4c77 integrate train/git: worktree RC8 forward-port + multi-repo selector
# Conflicts:
#	src/index.ts
2026-08-23 20:28:17 +08:00
HuanLinOTO af8ec58f4c integrate train/css: panel z-index + overflow clip + mobile sidebar 2026-08-23 20:27:37 +08:00
HuanLinOTO a860462254 integrate train/base: cordis peer migration + unrun devDep + path security 2026-08-23 20:27:32 +08:00
HuanLinOTO d63de6f815 merge #326: support selecting child git repositories
# Conflicts:
#	src/client/GitView.tsx
#	src/client/state.ts
#	src/index.ts
2026-08-23 20:25:52 +08:00
HuanLinOTO 37a84815d1 merge #254: mobile explain unavailable sidebar and prevent overflow
# Conflicts:
#	README.md
#	README_EN.md
#	dsh.plugin.json
#	package.json
#	src/client/service.ts
2026-08-23 20:22:49 +08:00
HuanLinOTO 8f4d58dcc7 merge #292: markdown preview local/relative images
# Conflicts:
#	README.md
#	README_EN.md
2026-08-23 20:21:50 +08:00
HuanLinOTO 90584535d4 merge #345: 限制文件 API 访问会话工作区 2026-08-23 20:19:41 +08:00
HuanLinOTO e9a488024c merge #336: add unrun devDependency for tsdown config loading 2026-08-23 20:19:20 +08:00
HuanLinOTO ed3083d133 merge #338: 移除公开版 cordis peer,迁移类型基底到 @deepseek-ai/cordis
# Conflicts:
#	README.md
#	README_EN.md
#	package.json
2026-08-23 20:18:55 +08:00
HuanLinOTO 3445331494 merge #278: panel host clip viewport, hidden panel no longer causes bidirectional overflow 2026-08-23 20:17:58 +08:00
HuanLinOTO 60b9c867d8 merge #342: fs-search skip node_modules and other noise dirs 2026-08-23 20:17:22 +08:00
HuanLinOTO c88eccfadb merge #330: drop panel host below DSH cordis dynamic-plugin layer 2026-08-23 20:17:16 +08:00
HuanLinOTOandxxxg0001 5c88279d7a merge #285: forward-port linked worktrees to RC8
Co-authored-by: xxxg0001 <xxxg0001@users.noreply.github.com>
2026-08-23 20:17:14 +08:00
HuanLinOTO 23a72a5f64 merge #310: keep conversation composer in viewport under layout push 2026-08-23 20:16:59 +08:00
HuanLinOTO ba83dd33f7 merge #341: fix(mermaid): suppress global error rendering 2026-08-23 20:16:33 +08:00
多玩幻灵qwq d6d8db0416 Merge pull request #339 from HuanLinOTO/feat/better-locale-ja
feat(i18n): 接入 @huanlin/dsh-plugin-better-locale 支持 19 语言覆盖(ja/de/fr/...)
2026-08-23 19:52:15 +08:00
HuanLinOTO 4af8d3121a fix: devDependency 改用 npm registry 版本,去掉本机 link 路径 2026-08-23 19:42:24 +08:00
HuanLinOTO f58e361cbe fix(i18n): guard ctx.get call so absent better-locale is truly no-op
Sidebar.tsx read ctx.get('betterLocale') unconditionally. In real
cordis hosts ctx.get exists and returns undefined when better-locale
is absent — but the unit-test mocks build a minimal ctx without a
get method, throwing 'TypeError: ctx.get is not a function' inside
<Sidebar> render and taking down 7 tests
(bottom-auto-terminal + sidebar-crash suites).

Add a typeof guard so the optional contract holds even when ctx.get
is missing. The activation-order-safe ctx.effect path in index.tsx
is unaffected (real cordis Context, runs once).
2026-08-23 16:56:07 +08:00
HuanLinOTO 7f8f95a59b feat(i18n): 覆盖支持扩展到 19 语言(de/fr/pt/ko/ar/hi/id/tr/vi/th/ru/it/nl/sv/pl/zh-HK/zh-TW/zh-MO) 2026-08-23 14:57:48 +08:00
Zzy dab6e795a8 Merge upstream main into fix/composer-clipped-by-layout-push 2026-08-23 11:19:36 +08:00
kelvinwww 4882a5c90f docs: 补充文件 API 路径安全说明 2026-08-23 10:42:36 +08:00
kelvinwww e9ce58afea fix: 限制文件 API 访问会话工作区 2026-08-23 10:28:05 +08:00
lx b87f263c09 fix(build): make cleanup command cross-platform 2026-08-23 03:26:25 +08:00
leokuang d0f194b67a fix(explorer): 文件名搜索跳过 node_modules 等噪声目录
右侧文件窗口的全局文件名搜索会深度优先遍历整个工作区,原先只跳过
.git。含大型 node_modules 的仓库会先把 10 万条访问预算耗尽,导致
docs/ 等后序目录里的真实文件搜不到。

搜索改为跳过依赖、包管理存储和常见构建缓存目录(不匹配也不下降),
并补回归测试钉住 node_modules / dist 内的命中不会泄漏。
2026-08-23 02:50:37 +08:00
lx 57a4ad82e1 merge main into fix/multi-repository-git-selector 2026-08-23 02:50:34 +08:00
HuanLinOTO 16a809594c fix(i18n): better-locale 接入改为 activation-order-safe
ctx.get('betterLocale') 是非响应式读取(cordis 只对声明的 inject
依赖重新求值)。如果 better-locale 在 better-sidebar 之后激活,
apply 顶层的 ctx.get 返回 undefined,attachBetterLocale(undefined)
使整个覆盖接入变成 no-op,即使用户后来在设置页选了日语,
better-sidebar 的 t() 也不查询 override store。

改为在 ctx.effect 中订阅 ctx.locale.subscribe:better-locale 在
激活时(若有持久化的 override)和用户切换 override 时都会 bump
locale revision,subscribe 回调触发 sync(),重新读取 ctx.get
('betterLocale')。store 可用后 attachBetterLocale(store) + register
ja dict;store 不可用时 detach。

这解决了插件激活顺序不确定的问题:无论 better-locale 先于还是后于
better-sidebar 激活,override 接入都能正确建立。
2026-08-23 02:31:30 +08:00
zhangqianfeng a75928daf1 fix(mermaid): suppress global error rendering 2026-08-23 01:50:07 +08:00
HuanLinOTO 38dc807d15 refactor(i18n): 覆盖只在 DSH active=en 时生效(借用英文槽位)
better-locale 的覆盖本质上是借用 DSH 的英文槽位来渲染第三语言:
- store.getOverride(dshActive, ns, key) 改为只在 dshActive === 'en'
  时返回覆盖文本;DSH 在 zh 下覆盖完全惰性(保持原生 zh)
- 新增 store.isOverrideActive(dshActive):返回 active !== undefined
  && dshActive === 'en',供 better-sidebar 的 isZh() 判断
- isZh() 改用 isOverrideActive(dshActive) 判断(仅在 DSH=en 且有
  override 时返回 false),zh 下返回 true
- LanguageSwitcher 接收 dshActive prop,选了非 native 且 DSH 非 en
  时显示「请将 DSH 切换到英文以查看 [语言名]」提示
- 新增 switchToEnHint copy key(zh + en)
- 测试:better-locale +3 store tests(en-only / inert on zh /
  isOverrideActive),+1 patch test(inert on zh);better-sidebar
  +1 locales test(override inert while DSH on zh),FakeBetterLocale
  加 isOverrideActive 方法
2026-08-23 01:42:01 +08:00
HuanLinOTO 7ceee777b4 feat(i18n): 接入 @huanlin/dsh-plugin-better-locale 支持 ja 覆盖
新增 ja 词典(src/client/locales-ja.ts,326 keys,与 zh key-set-equal),
通过可选 peer @huanlin/dsh-plugin-better-locale 接入第三语言覆盖:

- locales.ts: 新增 attachBetterLocale(store) + ja dict export;t() 先查
  store.getOverride(active, 'betterSidebar', key),命中则返回覆盖文本,
  否则走原 zh/en 链;isZh() 在 override 激活时返回 false
- client/index.tsx: apply 中 ctx.get('betterLocale') 取得 override store
  (optional,未安装时为 undefined),attachBetterLocale 注入 t();同时
  register('betterSidebar', { ja }) 把 ja 词典注册进 better-locale 的
  store,让外部 ctx.locale.lookup 调用者也能拿到 ja 文本
- Sidebar.tsx: 新增 betterLocaleActive uSES 订阅 store.subscribe,覆盖
  切换时全树重渲染(DSH locale 的 active 字段不变,既有 localeRevision
  uSES 不会触发)
- package.json: @huanlin/dsh-plugin-better-locale 加为 optional peer
  + devDep link
- tests/locales.spec.ts: 新增 7 个 better-locale override 测试(命中/
  fallback/detach/interpolate/ja dict 完整性 smoke check)

未安装 better-locale 时 ctx.get 返回 undefined,整段为 no-op,zh/en
行为完全不变。
2026-08-23 00:52:20 +08:00
Menghuan1918 ddb66c9c08 fix(market): 移除公开版 cordis peer 并迁移类型基底到 @deepseek-ai/cordis
DSH 市场(dsh-community-market)预览硬拒依赖字段出现 'cordis'
(dependencies/peerDependencies/optionalDependencies 任一字段、
optional 均无效),导致 dshfind/1024Store 目录拿不到
repository_backlink 验证目标,点击安装报
"This catalog item has no verified install target"。

- package.json: 删除 cordis peer + peerDependenciesMeta;版本 0.15.2
- context-types.ts: Context 基底改 @deepseek-ai/cordis,结构服务面改
  交集(避免 DS host/client 对同一成员类型不同导致的 TS2717 merge
  冲突),仅保留 betterSidebar 的 module augmentation;删除
  declare module 'cordis'
- client/index.tsx: 4 处 effect 体失败分支改返回 no-op disposer
  (vendored cordis effect 契约拒绝 undefined 形状)
- 新增 tests/market-manifest.spec.ts:无 cordis 依赖字段、打包产物
  无生命周期脚本、bundle patch 安全路径、仓库身份、精确稳定版本
- 消费者检查扩展:@deepseek-ai/cordis 路径的 Context augmentation
  断言;文档示例同步迁移
2026-08-23 00:26:39 +08:00
Menghuan1918 d9b8f15d9e chore(release): 0.15.2
- bump version: package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION
- README / README_EN: feature tour converted to a table (two screenshots
  per row, saves space); add v0.15.2 changelog (file-tree open-in-app
  menu #334, tab context menu #331, diff collapse by default #270, prune
  empty restored panes #268, hide git windows on Windows #301, untracked
  files inside new folders #242, toggle/drag per-frame re-render perf
  #315); community QR codes landed in #325
v0.15.2
2026-08-22 23:30:19 +08:00
Atlas Zeng 22a5d372be feat(editor): 文件树「在应用中打开」子菜单(外部编辑器/图钉固定/SSH 远端/自定义编辑器) (#334)
* feat(editor): file tree 'open with' submenu (external editors, pin, SSH-remote, custom editors)

The file tree right-click menu gains an 'Open with >' submenu with the
resource manager (reveal in file manager), VS Code, Cursor and Zed, plus
user-defined custom editors (name + URL template + VSCode-family flag).
Each submenu row carries a pushpin that promotes the target to a direct
top-level menu item.

A configurable SSH host (settings: Files card gear) switches VSCode-family
openers to <scheme>://vscode-remote/ssh-remote+<host>/<path> and hides
host-local targets (file manager / Zed / non-VSCode custom editors).

External opens run through the new fenced /sidebar/api/open.external route
which spawns the platform opener with an argv array (no shell); the client
never navigates raw scheme URLs itself. The settings seam's render panel now
appends after the declarative rows instead of replacing them.

* feat(editor): polish open-with menu UI — brand icons, pushpin, chevron, row alignment

- Submenu rows: the custom label is now a block-level flex row filling the
  primitives itemLabel, so the pushpin docks at the row's far right (matching
  the trailing check of every other menu); menu row icons normalized to 16px
  (DSH native menus use 16, ours were 14).
- The submenu parent row gains a right-aligned chevron (the primitives Menu
  renders no arrow for submenu parents).
- Icons via react-icons: codicons VscPin/VscPinned pushpins (replacing the
  hand-drawn pin), VscFile/VscFolder/VscFolderOpened tree glyphs (replacing
  the </> file mark), VscLinkExternal for the open-with parent and the
  browser tab's open-external button (globally), SiCursor/SiZedindustries
  brand silhouettes; the VS Code mark is inlined from simple-icons@11.0.0
  (CC0) as IconVscode16 since later simple-icons releases dropped it. All
  monochrome currentColor to follow the active skin.
- tsdown: pin react-icons si/vsc to their ESM entries via resolve.alias —
  the package's exports map lists require before import, so the shared
  conditionNames picked the unshakeable CJS entry and bloated the core
  bundle by ~6.4 MB; tree-shaken it now adds only ~12 KB.
2026-08-22 23:19:17 +08:00