Repository navigation
feat(plugin-launcher): grouped grid, trigger hand-off, and local app launching (#2085) - #2091
Conversation
The panel showed one flat list of seven plugins, forgot nothing, and could not be resized. It now has the shape the request describes: a search row, recently used (two rows of eight) and pinned (one row) when the input is empty, and a hit grid capped at three rows when it is not. Matching moved into a pure module so the ordering rules are testable: a declared trigger beats every name match, then exact name and alias, prefixes, pinyin and its initials, and substrings. Every tile names why it matched, and plugins and applications are distinguishable by tile shape as well as by tag. Recents, pins and aliases live under one storage key and are migrated from the previous panel's plugin-only history, so an upgrade keeps the order the user was used to. The window is now resizable and remembers its size and position in the data directory, following the same main-process shell-state rule as the main window.
The panel only ever launched plugins. It now also lists the applications this machine actually has: Start Menu shortcuts on Windows, bundles on macOS, and desktop entries on Linux, cached for five minutes and re-scanned after that so an uninstalled application stops appearing. The renderer never receives an executable path. It gets a display name, a source tag, a small icon and an opaque id, and the host resolves that id against its own scan and re-checks the target before opening it. The two new channels additionally refuse any sender that is not the launcher window, so the capability cannot be borrowed by another surface. Scanning lives behind injected I/O, so the platform parsers, the id scheme, the cache and the removed-target path are all unit-tested without touching a real desktop.
A plugin can now declare contributes.launcherTriggers, so typing an expression offers the plugin that understands it before any name match, and pressing Enter hands that plugin the text it matched. The match is against the whole trimmed input, never a substring, and the host refuses patterns that nest a quantifier inside a repeated group, so a careless declaration cannot freeze the panel. The payload travels with the panel open request and is delivered once, after the page has loaded, through a preload method that clears it on read. Only the plugin the user chose receives the text, and the payload is bounded so a paste cannot be forwarded verbatim. Validation lives in its own submodule because the contributions validator was already at the size where a new block belongs beside the existing ones rather than inside them.
The rewritten panel labels each tile with why it matched, offers pin and alias actions, and reports how many matches the grid had to drop, so the catalog needs those strings in all nine shipped locales. Key parity and placeholder parity are enforced by the catalog test and by the per-locale EnglishCatalog type.
…tion The plugin specs still described the launcher as merged into global search and had nothing about claiming input by pattern, so the new spec 18 records the layout, ranking, payload delivery, the local-application boundary and the ten checks a user can run. Spec 09 keeps its command-palette content and gains one paragraph separating the two surfaces. The manifest schema gains contributes.launcherTriggers with the rules the host actually enforces, ADR 0339 records why application launching is confined to the launcher window and why the panel keeps no path, and the E2E plan gains a scenario for the grid, the trigger hand-off and the resized window.
The launcher's MRU list moved from plugin-launcher-history.ts into launcher-store.ts, so the neighbouring comment now names the file that exists instead of the one that was removed.
|
补充一条环境说明(供 reviewer 判断哪些绿灯可信): 这个工作树按仓库策略复用父 checkout 的
本 PR 自己新增/改动的套件在同样的环境里是干净的:launcher-search、launcher-store、plugin-launcher-state、local-apps、plugin-launcher-window 共 62 项全绿;Rust 侧 cargo test -p host-core launcher 5 项、plugin 过滤 147 项全绿。 真正的全量门禁以 CI(干净安装)为准;本机无法执行 pnpm(shim 损坏)与 Electron E2E,已在 PR 描述里标注。 |
|
还有一处没有在本 PR 里做完,先明确记下来,避免被当作已完成: issue 第 3 节说“规则是插件自己声明的,我能在插件详情里看到它什么时候会跳出来,不想要就关掉”。当前状态:
原因是主窗口插件详情目前拿不到 contributes.launcherTriggers(plugins.list 只返回 summary,不带 triggers),要在那里展示需要再扩一条只读的面板级摘要字段或专门的 IPC。这块更适合作为本 issue 的第二个 PR(“插件详情可见可控”),我在这里先如实标注,不假装已完成。 |
The style-token gate rejects raw typography and radius literals in the renderer, and the new launcher grid introduced four of them: a letter-spacing, a 50% radius for round application icons, and two line-height: 1 declarations. They now come from the tracking, radius and leading scales, which is also what keeps the icon shapes consistent with the rest of the shell.
未完成事项清单(逐条对应 issue 需求)这份清单取代我之前两条笼统的说明。每条都写明:issue 的哪一句 → 当前真实状态 → 差在哪里 → 为什么不在本 PR 里补。 A. issue 第 3 节:正则带参唤起A1. 「我能在插件详情里看到它什么时候会跳出来,不想要就关掉」→ 只完成了数据层,UI 完全没做。
A2. 验收第 2 条里的
A3. 插件作者侧文档缺失。
B. issue 第 4 节:启动本地应用B1. Linux 上
B2. 别名入口比较隐蔽,没有独立管理界面。
B3. 图标策略没有给用户选择。
C. issue 第 2 节:布局细节差异C1.
C2. 命中溢出的提示位置与设计稿不同。
C3. 窄屏降列没有做。
C4. 空位没有渲染占位方块。
D. 验收 10 条的逐条状态
E. 工程侧未完成/未验证E1. 没有跑 Electron E2E。 本工作树按仓库策略复用父 checkout 的 E2. 本地 E3. 没有做真实应用启动的冒烟。 F. 我认为需要你拍板的三个取舍
|
The open-panel handler now decides whether an open carries launcher input, so plugin-ui-ipc imports the launcher module. The provider-catalog test loads that handler with a stubbed module map and refuses unknown dependencies; it now maps the launcher module to the real pure function, so the handler is still exercised as shipped instead of against a stand-in.
…to content Every tile now reads icon + type + name. The second line used to explain why something matched or what the plugin would receive, which made a trigger hit look like a different kind of result and left the panel talking about `1+1 = 2` instead of the plugin. A declared regex now simply puts its plugin in the first cells of the same grid, and the payload still reaches only the plugin the user picks. The per-tile menu is gone: one pin button replaces it, the pinned row holds eight and refuses with a footer notice plus a shake once it is full. "All plugins" scrolls inside the panel, and the window height follows what was rendered rather than a dragged height, so a short result no longer sits in an empty box. Alias editing goes with it: plugin and application names are not user-editable. Local application icons now come from the platform at `large` size with a 32 KiB budget and an id-keyed cache, so a scan stops paying for the same icon twice and tiles stop falling back to letters.
The panel no longer explains how an item matched, and it no longer offers aliasing, so every `reason*` and alias string lost its call site. All nine catalogs drop them and gain the two type labels plus the pin-limit notice and the plugin-market button the new footer and empty state render.
…height The old assertions described a detail line, alias matching and a 24-pin row, all of which no longer exist. The suites now pin down the behaviour that replaced them: a declared-trigger hit takes the first cells and is not listed twice, pinning stops at eight, the content-height arithmetic rounds up and floors at zero, and local icons are requested at `large` with a 32 KiB budget and reused across scans.
…ized panel The spec still promised a reason line under every tile, an alias editor, a 24-pin row and a window height the user drags. It now documents the icon + type + name tile, a trigger hit leading the same grid, the eight-pin row that refuses instead of evicting, the in-panel all-plugins view, the window that follows measured content through its own IPC, and letter tiles for plugins. The ADR records the icon budget change (large, 32 KiB, cached) and why a plugin still gets a letter tile, and E2E-268 gains the scenarios that make the new behaviour checkable by hand.
Brings the launcher work onto the current base. Three conflicts, all additive: the IPC registrar imports keep both the launcher and the computer-use module, the ADR index keeps 0339 next to 0340 and 0342, and E2E-268 keeps its scenario alongside the new computer-use section.
`main` promoted its own 0339 for chat file references while this branch was open, so the launcher ADR collided with it and the docs gate rejected two duplicate decision ids. The launcher record moves to the next free id and every reference to it follows.
这个 PR 做了什么
按 issue #2085 重写插件快捷面板(
Alt+Space/⌥+Space)。第一版把分组网格、正则唤起和本地应用做完;这一版按反馈把格子的呈现、固定方式、面板高度和改名能力重新定稿:feat(plugin-launcher): 统一格子为「图标 + 类型 + 名称」、正则命中占同一网格最前、图钉固定(上限 8)、面板内全部插件视图、内容自适应高度、真实大图标feat(i18n): 用类型标签替换命中原因文案,删除别名文案(9 个语言包)test(plugin-launcher): 覆盖新契约(触发置顶去重、固定上限、内容高度换算、图标预算与缓存)docs(plugins): 18-plugin-launcher(中英)、ADR 0339、E2E-268 同步Merge origin/main+docs(plugins): 与最新主干对齐(3 处 additive 冲突),并把 ADR 重新编号为 0343(0339 已被主干新增的 chat-file-refs ADR 占用)需求对照
LauncherTileButton.tsx:图标统一 48px 圆角方形,名称独占整行,过长才省略;不再有命中原因 / 类型徽标 /收到 1+1小字1+1时声明了正则的插件排第一个格子,且不与搜索命中做视觉区分launcher-search.ts:正则整条命中仍是最高优先级,但进的是同一个匹配结果网格的第一批格子,外观与其它格一致;同一插件只出现一次1+1/1+1 = 2trigger.payload仍随点击传给插件(launcherOpenPlugin)launcher-store.ts去掉aliases与setLauncherAliases,LauncherItemMenu.tsx删除,i18n 删除 5 个别名 key,LauncherMatchReason去掉"alias"LAUNCHER_PIN_LIMIT24→8;已满时不再新增,底部提示「固定位已满(8/8)」并让固定区抖一下全部插件 →;面板内单网格列出全部插件(最近使用优先,其余按目录序),复用结果区滚动,激活项scrollIntoView;继续输入即回到搜索态,Esc先收回该视图minmax(0,1fr)、无占位格;新增launcher-height.ts+pluginLauncher/contentHeightIPC,主进程按测量高度setContentSize,下限降到 120(宽度默认 900、可拖 800–1280 并记忆)pluginLauncher/openMarket走应用菜单命令打开插件市场页app.getFileIcon(..., { size: "large" }),预算 4 KiB→32 KiB,新增按 id 的跨扫描图标缓存;<img onError>回退色块。插件仍用首字母色块(manifest.icon宿主从不读取,见 ADR 0339)webContents| 插件与本地应用是否要视觉区分 | 不做区分:统一 48px 圆角方形,不画角标、不写「插件/应用」;来源只保留在悬停
title里。这条推翻了 issue 原文的「一眼分得开」,issue 与文档已同步改写 || 面板宽度 |
PLUGIN_LAUNCHER_DEFAULT_WIDTH760→900、MIN_WIDTH560→800(八列里每格仍放得下 48px 图标与可读名称) |验证
node --testlauncher 相关 7 个套件(launcher-search/launcher-store/plugin-launcher-state/plugin-launcher-height/plugin-launcher-window/local-apps/plugin-provider-catalog-ipc)→ 79 passed, 0 failedpackages/i18nvitest → 31 passed(9 语言包键集与插值一致性)tsc -p apps/desktop --noEmit→ 通过biome lint→ 136 files, 无问题node docs/scripts/check-locales.mjs→ 89 对 OK;node docs/scripts/check-docs.mjs→ 612 页(仅 2 条既有 ADR 提示,与本次无关)pnpm -r build(含 renderer/electron 构建)→ 通过6700105d2;Docs、JS、Rust fmt/lint/test、Rust Windows E2E、Electron E2E、base 校验)未执行 / 已知限制(请 reviewer 留意)
pnpm.CMD指向缺失二进制),Electron 端无可用产物,pnpm test:e2e*无法执行;E2E-268 已按模板更新并标为 Draft。请以 CI 结果为准。openPluginMarket(主窗口聚焦 + 切到插件页 market 标签),没有新增 shell 导航契约;主窗口尚未 boot 时该命令会排队。aliases字段静默忽略、不再写回。.desktop里Exec写裸命令(如Exec=code %F)时,launchTarget仍是裸命令,shell.openPath会失败并提示「已卸载」。需要按PATH解析,属于本 PR 之外的后续工作,代码里已留下注释说明。Refs #2085
CI
(期间修了三处真实失败:4 个裸样式值改为设计 token;
plugin-ui-ipc新增依赖补齐了 provider-catalog 测试的桩映射;ADR 0339 与主干新增的同号决策冲突,已重新编号为 0343。另有 2 项与本次无关的 Electron E2E 抖动,重跑后通过。)