Commit Graph

6 Commits

Author SHA1 Message Date
bincxz 33f8221d5c refactor: remove immersive mode toggle remnants — always enabled
Immersive mode was already hardcoded to true with a no-op setter.
Clean up all dead code:
- Remove isImmersive param from useImmersiveMode hook
- Remove immersiveMode/setImmersiveMode from useSettingsState
- Remove toggle from SettingsPage and SettingsAppearanceTab
- Remove sync read/write of immersiveMode setting
- Remove i18n keys for the removed toggle
- Simplify App.tsx conditionals

Kept: useImmersiveMode hook (core logic), CSS classes (fade overlay),
sync type field (backward compat), storage key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:49:03 +08:00
陈大猫 2f314c3588 feat: group configuration inheritance (#220) (#593)
* feat(i18n): add translations for group config panel

* feat(models): add GroupConfig data model, resolution logic, and encryption

Add the GroupConfig interface for group-level default settings that hosts
inherit. Includes ancestor-chain resolution (A/B/C merges from A, A/B,
A/B/C), host-level application logic, storage key, and secure field
encryption/decryption for sensitive GroupConfig fields.

Part of #220.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(state): add groupConfigs state management with encryption

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ui): create GroupDetailsPanel with full config editing

Side panel for editing group-level default configuration using AsidePanel.
Includes General, SSH, Telnet, Advanced, Mosh, and Appearance sections
with sub-panel navigation for Proxy, Chain, EnvVars, and Theme selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vault): wire GroupDetailsPanel, replace rename dialog with full config panel

Replace all group rename dialog triggers with the new GroupDetailsPanel sidebar.
The hover edit button, context menu, and tree view edit callbacks now open the
full group configuration panel instead of a simple rename dialog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(connect): apply group config defaults at connection time

When connecting to a host, merge group-level default configuration so
hosts inherit their group's settings for auth, protocol, appearance,
and other inheritable fields. Connection logs still reference the
original host's label/hostname.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(sync): include groupConfigs in sync and export payloads

Add groupConfigs to SyncPayload, SyncableVaultData, buildSyncPayload,
and applySyncPayload so group connection defaults are preserved during
cloud sync and data import/export. Also wire groupConfigs into the
vault object in SettingsPage so it flows through to the sync payload
builder.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(vault): update group configs on move and delete

* feat(host-panel): show inherited group defaults as placeholders

When editing a host that belongs to a group with configuration, group
default values now appear as placeholder text in username, startup
command, and charset fields where the host doesn't have its own value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: clean up unused imports in GroupDetailsPanel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(group-panel): add/remove protocol sections, editable parent group

- SSH and Telnet sections are now add/remove — click "Add Protocol"
  to enable, "..." menu to remove. Only enabled protocols override hosts.
- Parent Group is now editable via Combobox dropdown for quick
  group moving.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move SSH-specific fields into SSH protocol section

Startup Command, Legacy Algorithms, Proxy, Host Chaining,
Environment Variables, and Mosh are all SSH-specific and now only
visible when SSH protocol is added. Only Charset remains as a
shared field in the Advanced section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: hide charset and appearance when no protocol is added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: close Add Protocol dropdown after selection

Use controlled open state to explicitly close the dropdown when a
protocol is selected, preventing residual content from overlapping
the newly rendered section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: apply group defaults in TerminalLayer sessionHostsMap

Terminal component was re-reading the original host from the hosts
array by hostId, bypassing the group defaults applied in
handleConnectToHost. Now sessionHostsMap applies resolveGroupDefaults
+ applyGroupDefaults when building the host object for each session,
so Terminal sees the merged credentials/settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move Add Protocol to bottom, fix i18n for protocol/font labels

- Add Protocol button moved below Appearance section
- Added i18n keys: addProtocol, removeProtocol, fontFamily, fontSize
- All hardcoded English strings replaced with t() calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace font family text input with TerminalFontSelect dropdown

Use the same font selector component as settings, showing available
terminal fonts with preview. Includes "Use Global" reset button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(group-panel): match HostDetailsPanel key/certificate selection pattern

Replace the simple Combobox key selector with the same credential selection
flow used in HostDetailsPanel: a popover with Key/Certificate options,
inline combobox per type, and proper badge display with certificate icon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(group-panel): add Local Key File option to credential selection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(group-panel): add identityFilePaths to GroupConfig and Local Key File option

- Added identityFilePaths to GroupConfig interface and INHERITABLE_KEYS
- GroupDetailsPanel now supports Key, Certificate, and Local Key File
  credential selection, matching HostDetailsPanel's full credential flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent local key file input from overflowing panel width

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: constrain local key file input width with w-0 flex-1

Native input elements have a large default min-width. Using w-0 with
flex-1 forces the input to shrink within the flex container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add overflow-hidden to SSH Card to contain local key file input

Matches HostDetailsPanel's Card which uses overflow-hidden on the
credentials section to prevent long file paths from overflowing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add min-w-0 to key file path row for proper text truncation

Flex children need min-w-0 for truncate to work correctly,
otherwise the text pushes the container wider.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: force key file path text truncation with inline max-width calc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use fixed 320px max-width on key file path text to force truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add overflow-hidden to AsidePanelContent to prevent content overflow

The root cause was the inner div of AsidePanelContent only had
overflow-x-hidden which was being overridden by ScrollArea's viewport.
Changed to full overflow-hidden with w-full box-border.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: override Radix ScrollArea viewport's display:table in AsidePanel

Radix ScrollArea Viewport wraps content in a div with
display:table and min-width:100%, causing content to expand beyond
the panel width. Override this on AsidePanelContent's ScrollArea
to use display:block and min-width:0 instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: critical issues — seed new hosts from group defaults, validate group names, fix empty import

- HostDetailsPanel: When groupDefaults has values for port/username/charset,
  new hosts start with undefined/empty so group defaults take effect via
  applyGroupDefaults() instead of being blocked by hardcoded values
- GroupDetailsPanel: Validate group name in handleSubmit to reject '/' and
  '\' characters, matching the old rename dialog behavior, with visual error
- useVaultState: Check groupConfigs !== undefined instead of truthy so that
  importing an empty array [] properly clears all group configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: safe prefix replacement, remove dead code, extract shared resolveEffectiveHost

- Replace all .replace(oldPath, newPath) / .replace(sourcePath, newPath) with
  explicit prefix slicing (newPath + str.slice(oldPath.length)) in handleSaveGroupConfig
  and moveGroup for more robust path renaming
- Remove dead c.path === oldPath branch in finalConfigs mapping since updatedConfigs
  already contains the config with newPath
- Extract resolveEffectiveHost helper in App.tsx to deduplicate group defaults
  resolution in _handleTrayPanelConnect and handleConnectToHost

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: preserve undefined port on save when group has port default

form.port || 22 was forcing port to 22 even when intentionally left
undefined for group inheritance. Now uses nullish coalescing and only
defaults to 22 when no group port default exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: SSH-adjacent field detection, chain host defaults, telnet inheritance, theme clear

- hasSshFields() now checks proxyConfig, hostChain, startupCommand,
  legacyAlgorithms, environmentVariables, moshEnabled, moshServerPath,
  and identityFilePaths so the SSH section auto-opens when editing
- Chain hosts in sessionChainHostsMap now get group defaults applied
  via resolveGroupDefaults + applyGroupDefaults
- Added telnetEnabled to GroupConfig interface and INHERITABLE_KEYS;
  save handler sets telnetEnabled: true when Telnet section is on
- Theme/font "Use global" clear now sets override to false instead of
  undefined, preventing parent group theme from leaking through

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: review round 4 — sync, SFTP, port forwarding, type safety, UX

- Scan groupConfigs in encrypted credential guard (P1 security)
- Add groupConfigs to auto-sync payload and three-way merge (P1 sync)
- Apply group defaults in SFTP connections (P1 SFTP)
- Apply group defaults in all port forwarding paths (P1 port forwarding)
- Make Host.port optional to fix unsafe type cast (P1 type safety)
- Fix port input empty → 0 instead of undefined (P2)
- Add port placeholder showing inherited value (P2)
- Mutual exclusion of group/host detail panels (P2)
- Fix sub-panel width jump 420px → 380px (P2)
- Validate duplicate group path on rename/reparent (P2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: review round 5 — null guard, empty array inheritance, memo comparator, form reset

- Guard groupConfigs import against null payload (P1 crash)
- Validate duplicate path on moveGroup drag-drop (P2 data corruption)
- Clear empty environmentVariables to undefined for group inheritance (P1)
- Clear empty hostChain to undefined for group inheritance (P2)
- Add groupConfigs to SftpView memo comparator (P1 stale defaults)
- Add key={editingGroupPath} to GroupDetailsPanel for form reset (P1)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: review round 6 — copy credentials, protocol dialog use effective host

- Apply group defaults in handleCopyCredentials (P2)
- Apply group defaults in hasMultipleProtocols check (P2)
- Pass effective host to ProtocolSelectDialog (P2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: serialize protocol:'ssh' marker to persist SSH section in group config

- Add protocol:'ssh' as marker field in handleSubmit SSH block
- Detect protocol:'ssh' in hasSshFields() to preserve section on reopen
- Clean up protocol field in removeSsh()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:40:40 +08:00
陈大猫 c7ae51b952 feat: host/group management improvements (#506) (#589)
* feat(models): add pinned and lastConnectedAt fields to Host

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(i18n): add translations for pinned and recently connected sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(vault): add pin toggle, lastConnectedAt tracking, and computed sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vault): render Pinned and Recently Connected sections at root level

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vault): add pin/unpin context menus and hover edit buttons in all views

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vault): make breadcrumb a drop target for moving groups back to root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(settings): add toggle for showing recently connected hosts section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve lint warnings for unused vars and unnecessary dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: improve pin performance and add pop-in animation

- Use ref for hosts in callbacks to avoid stale closures and
  unnecessary re-renders when hosts array changes
- Add pop-in spring animation on pinned host cards with staggered
  delay for a satisfying visual effect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fix pop-in animation visibility and improve pin responsiveness

- Move @keyframes pop-in out of @layer base to global scope so inline
  styles can reference it
- Add translateY to animation for a bouncier, more satisfying feel
- Use pinnedAnimKey to force card remount on pin changes so animation
  replays each time
- Wrap onUpdateHosts in startTransition for non-blocking pin updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: only animate newly pinned card, increase section spacing

- Track lastPinnedId instead of global animKey so only the newly pinned
  card gets the pop-in animation, not all existing pinned cards
- Clear animation state via onAnimationEnd for clean re-trigger
- Add mb-4 to Pinned and Recent sections for better visual separation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vault): show pin indicator icon on pinned host cards

Small semi-transparent pin icon in top-right corner of pinned host
cards in the Hosts section (grid view only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: use solid amber/yellow pin indicator icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: tilt pin indicator icon 45 degrees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: replace pin indicator with filled amber star on all pinned cards

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move lastConnectedAt tracking to App-level handleConnectToHost

Previously updating lastConnectedAt in VaultView's handleHostConnect
which could be lost during tab switches. Now tracked at the App level
where all connections are handled, ensuring the timestamp persists
regardless of UI navigation state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Codex review findings (P2 issues)

1. useStoredBoolean now syncs across same-window components via
   CustomEvent dispatch, so Settings toggle immediately updates VaultView
2. lastConnectedAt updated after connectToHost succeeds, not before
3. Pinned and Recently Connected sections now respect active search
   and tag filters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address second round Codex review findings

1. Track lastConnectedAt on actual 'connected' status instead of
   session creation - handles via handleSessionStatusChange wrapper
2. Covers tray panel connections since all paths go through
   updateSessionStatus
3. Pinned/Recent cards now honor multi-select mode with checkbox
   UI instead of triggering connections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address third round Codex review findings

1. [P1] Use hostsRef in handleSessionStatusChange to avoid
   overwriting concurrent host changes with stale snapshot
2. [P2] Exclude pinned/recent hosts from main host list at root
   level to prevent duplicate cards on screen
3. [P2] Remove Pin action from tree view context menu since tree
   view has no pinned ordering/indicator support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address fourth round Codex review findings

1. [P1] Remove leftover onToggleHostPinned references in HostTreeView
   root-level component that were missed in previous cleanup
2. [P2] Add draggable + onDragStart to pinned/recent host cards so
   drag-and-drop between groups still works
3. [P3] Fix grouped view header count to exclude hosts already shown
   in pinned/recent sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use functional state update for lastConnectedAt, dedupe pinned from recent

1. [P2] Add updateHostLastConnected using setHosts(prev => ...) functional
   update pattern (same as updateHostDistro) to avoid overwriting concurrent
   host changes when multiple sessions connect simultaneously
2. [P3] Exclude pinned hosts from Recently Connected section to prevent
   duplicate cards between the two top sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: wire showRecentHosts into settings sync, clear pin on duplicate

1. [P2] Add showRecentHosts to SyncPayload settings so the preference
   survives cloud sync and settings export/import
2. [P2] Clear pinned and lastConnectedAt on duplicated hosts so copies
   don't inherit pin/recent status from the original

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 23:55:45 +08:00
陈大猫 aeacd913f5 feat: sync global SFTP bookmarks via cloud sync (#553)
* feat: sync global SFTP bookmarks via cloud sync

Global SFTP path bookmarks were stored only in localStorage and not
included in the cloud sync payload, so they could not be synced across
devices. Add them to the sync settings, with auto-sync detection via
a custom event and in-memory snapshot rehydration on import.

Local bookmarks remain device-specific by design.

Closes #548

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: deduplicate global SFTP bookmarks by path during merge

When the same path is bookmarked independently on two devices, each
generates a different random ID. The entity-array merge preserves both,
creating duplicates. Add path-based deduplication after settings merge,
following the same pattern used for known hosts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: sync global bookmarks across renderer windows via storage event

When cloud sync imports bookmarks in the Settings window, the main
window's in-memory snapshot stays stale. Listen for cross-window
storage events on the bookmark key to auto-rehydrate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:59:58 +08:00
陈大猫 9a7d4decff feat: 终端命令自动补全系统 (#527)
* feat: 终端命令自动补全系统

实现类似 WindTerm/Fish 的终端命令自动补全功能,不依赖机器学习:

- 历史命令持久化存储:按主机分组,频率+时间衰减排序,跨会话共享
- 前缀匹配引擎:支持精确前缀匹配和模糊匹配(首字符+连续字符+词边界加权)
- Prompt 检测器:识别 bash/$、zsh/%、fish/> 等常见 prompt 模式,排除 vim/less 等程序
- Ghost Text 插件:xterm.js 自定义 addon,光标后灰色行内建议,→ 接受全部,Ctrl+→ 接受一词
- 弹出补全菜单:浮动列表 UI,↑↓ 导航,Tab/Enter 选中,Esc 关闭,来源标记(h/c/s/o/a)
- @withfig/autocomplete 集成:600+ 命令规范的子命令、选项、参数补全
- 上下文感知:解析命令行 token,根据当前位置提供对应类型的补全
- 用户配置:启用/禁用、Ghost Text、弹出菜单、防抖延迟、最小字符数等
- 快速打字防误触:检测打字速度,快速输入时抑制建议
- 输入防抖 100ms,异步匹配不阻塞 UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 补全菜单混合展示历史命令和 spec 子命令

- 输入已知命令名(如 docker)时即使没有空格也预览子命令
- 历史命令条数从 8 降为 5,留空间给 spec 建议
- 修复 wordIndex === 0 时 spec 补全被跳过的问题

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 补全菜单在终端底部时向上展开

当光标在终端下方、空间不足时,弹出菜单向上展开(底边对齐光标行),
避免溢出终端区域。列表顺序和选中逻辑不变——最可能的选项始终在顶部,
用户初始向下选择。参考 Termius 的做法。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 补全菜单跟随终端主题 + Enter 直接执行命令

1. 补全菜单颜色从终端主题动态派生(color-mix),不再硬编码色值,
   确保与任何主题视觉一致
2. 在弹出菜单中按 Enter 选择命令时,直接插入并发送 \r 执行,
   无需用户再按一次回车
3. Tab/鼠标点击仍然只插入不执行(保留选择后编辑的能力)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 PR review 发现的全部 20 个问题

功能修复:
- #1 修复 selectAndExecute 导致命令双重录入历史:用 suppressNextEnterRecordRef
  标志位让 handleInput 的 Enter 分支跳过已经录入过的命令
- #2 修复 Prompt 末尾 $ 误判:重写 findPromptBoundary 为从左到右逐字符扫描,
  排除 $HOME/$PATH 等变量引用(检查 $ 前是否有空格、是否在 token 内部)
- #6 快速打字检测实际生效:快速打字时 debounce 延迟翻倍(200ms),等用户停顿
- #8 resolveSpecContext 处理带参数的 option(如 --name value):
  识别 option 的 args 字段,自动跳过下一个 token
- #9 Ghost text 位置随终端滚动/渲染更新:注册 term.onRender 回调
- #13 Escape 键不再拦截 vi-mode:仅在 popup 可见时消费 Escape,
  ghost text 显示时不拦截(ghost text 是被动的,不应阻止 shell 交互)
- #14 所有 setState 统一使用 EMPTY_STATE 常量,不再遗漏 expandUpward 字段

架构修复:
- #3 消除 CustomEvent 通信:改为 onAcceptText 回调注入,
  Terminal.tsx 直接传 writeToSession 回调给 hook,
  删除 createXTermRuntime 中约 20 行 listener 代码和 cleanupAutocompleteListener 字段
- #7 xterm 私有 API 访问集中到 xtermUtils.ts:getCellDimensions 统一入口,
  带缓存机制,仅在首次访问或 terminal 切换时触发 DOM 测量
- #16 删除 getCommandNameSuggestions 中多余的动态自导入 await import("./figSpecLoader")

性能修复:
- #5 合并 ghost text 和 popup 的查询路径:删除独立的 getInlineSuggestion,
  fetchSuggestions 只调一次 getCompletions,ghost text 取 completions[0]
- #10 preloadCommonSpecs 分批加载(每批 8 个,requestIdleCallback 间隔),
  延迟 200ms 启动,且检查 enabled 才执行
- #11 scoreEntry 改为 scoreEntryAt(entry, now),now 在查询开始时缓存一次
- #15 scrollIntoView 从 smooth 改为 instant,消除快速导航动画排队
- #19 loadSpec 添加 in-flight 去重(inFlightLoads Map),同一 spec 并发加载只触发一次 import
- #20 存储满时淘汰改为按 score 排序后保留前半,而非按插入顺序

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复二审发现的全部 10 个问题

功能修复:
- #1(高) insertSuggestion 改用实时 detectPrompt 而非过时的 lastPromptRef,
  修复用户继续打字后 Tab 选择建议导致字符重复插入的 bug
- #2(中) handleInput Enter 录入历史优先用实时 detectPrompt,
  修复快速打字场景下 recordCommand 记录不完整命令
- #9 suppressNextEnterRecordRef 添加 100ms 安全超时清除,防止 flag 残留
- #10 getNextWord 从 index 1 开始搜索分隔符,修复 ghost text 以 / 开头时
  一次接受全部而非逐段的问题

性能修复:
- #3(中) GhostTextAddon 注册 term.onResize 调用 invalidateCellDimensionCache,
  确保 resize/字体变化后 cell 尺寸缓存正确失效
- #4 updatePosition 缓存 lastLeft/lastTop,位置无变化时跳过 DOM 写入;
  字体属性移到 show() 中只设置一次,不再每帧写 6 个 style
- #5 统一 clearState() 函数替代所有 setState({...EMPTY_STATE}),
  带 popupVisible 守卫避免无效 re-render
- #6 hasSpec 中 specs.includes() 改为 Set.has(),O(1) 查找

架构修复:
- #7 Terminal.tsx 中 autocompleteAcceptTextRef 去掉多余的 useCallback 包装
- #8 删除 AutocompletePopup 的 onClose 死代码 prop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: popup 默认不选中任何项,用户按 ↑/↓ 后才选中

修复输入 ls 等简单命令时回车误执行联想结果的问题:
- selectedIndex 初始为 -1(无选中),Enter 直接执行用户输入的命令
- 用户按 ↑/↓ 导航后 selectedIndex >= 0,此时 Enter 才执行选中的建议
- Tab 仍然可以直接接受第一条建议(主动接受行为)
- Enter 无选中时关闭 popup 并让按键透传到终端

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fig spec 改为从静态资源 fetch 加载,修复生产构建中补全不工作

根因:@vite-ignore 动态 import 在 Electron 生产构建中无法解析
node_modules 路径(app:// 协议只能访问 dist/ 目录)。

修复方案(与 Monaco 编辑器相同的模式):
- 新增 scripts/copy-fig-specs.cjs,prebuild 时将全部 739 个 fig spec
  从 node_modules/@withfig/autocomplete/build/ 复制到 public/fig-specs/
- Vite 自动将 public/ 内容复制到 dist/,app:// 协议可以正常访问
- figSpecLoader.ts 改用 fetch + Blob URL + dynamic import 加载 spec,
  同时保留 @vite-ignore import 作为 fallback(兼容 dev 模式)
- public/fig-specs 加入 .gitignore(构建时生成,不进版本控制)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: ESLint 忽略 public/fig-specs 目录(第三方生成代码)

与 public/monaco 相同的处理方式——这些是从 node_modules 复制的
第三方构建产物,不应被项目 ESLint 规则检查。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 输入完整子命令名时展示其选项(如 git commit 显示 --message 等)

当 currentToken 完全匹配一个子命令时(如 "git commit" 中的 "commit"),
导航进入该子命令并展示其 options 和 sub-subcommands 作为预览。

之前的逻辑因为 name !== currentToken 过滤掉了完全匹配的项,
且 resolveSpecContext 的 consumedTokens 不包含当前 token,
导致停留在父级而看不到子级的选项。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 fig spec index.js 解析失败导致补全不工作

根因:index.js 格式为 var e=[...],diffVersionedCompletions=[...];
正则 /var\s+\w+\s*=\s*(\[[\s\S]*?\]);/ 要求 ] 后紧跟 ;,
但第一个数组后面是 , 不是 ;,导致非贪婪匹配跳到第二个 ];,
捕获了两个数组拼在一起,JSON.parse 失败,spec 列表为空。

修复:改用 indexOf 找第一个 [ 和对应的 ],直接截取子串解析。
fig spec 的 index 是简单的字符串平坦数组,无嵌套括号。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fig spec 改用 URL 直接 dynamic import,移除 fetch+Blob 方案

fetch + Blob URL + import() 方案可能被 Electron CSP 策略阻止。
改为直接用完整 URL 做 dynamic import:
- dev: import("http://localhost:5173/fig-specs/git.js")
- prod: import("app://./fig-specs/git.js")

两种环境下动态 import 都能正常解析模块,无需 fetch 中间步骤。
同时简化 getAvailableSpecs 也用同样方式,移除 fetch+正则解析。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fig spec 改为通过 Electron IPC 加载,彻底解决 dev/prod 加载问题

之前的方案(静态文件 + dynamic import / fetch + Blob URL)都因为
Vite dev server 对 .js 文件的模块转换和 Electron CSP 限制而失败。

新方案:通过 main process 的 Node.js require() 加载 fig spec,
通过 IPC 传给 renderer:
- main.cjs: 添加 netcatty:figspec:list 和 netcatty:figspec:load handler
- preload.cjs: 暴露 listFigSpecs() 和 loadFigSpec() API
- figSpecLoader.ts: 通过 window.netcatty bridge 调用 IPC

优势:
- main process 直接访问 node_modules,dev 和 production 都可靠
- 无需复制文件到 public/、无需 @vite-ignore hack
- spec 数据通过 IPC 序列化传输,无 CSP 限制
- 删除了 scripts/copy-fig-specs.cjs 和 public/fig-specs/ 相关代码

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: main process fig spec 加载改用 import() 替代 require()

@withfig/autocomplete 是 ESM 包("type": "module"),
CommonJS 的 require() 无法加载 ESM 模块会抛 ERR_REQUIRE_ESM。
改用 dynamic import() 在 async handler 中加载。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fig spec 加载用 pathToFileURL 绕过 package.json exports 限制

@withfig/autocomplete 的 exports 字段只允许 import "." 和 "./dynamic",
Node.js 严格遵守 exports map 拒绝解析 build/git.js 等子路径。

改为手动拼接文件绝对路径 + pathToFileURL 转换为 file:// URL 后 import,
完全绕过 Node.js 的 package exports 限制。

同时修复 promptDetector 不再 trim 尾部空格(用 cursorX 确定实际输入长度),
确保 "git commit " 的尾部空格被保留,触发空 token 显示选项列表。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: 补全菜单添加详情面板 + 清理调试日志

- 选中或悬停补全项时,右侧显示详情面板(类似 VS Code IntelliSense)
  - 显示完整命令名、来源类型标签(Option/Subcommand/History 等)
  - 显示完整的描述文本(不再截断)
- source 标记移到左侧,与描述分离,更易读
- 悬停和键盘选中都能触发详情面板
- 向上展开时详情面板也正确对齐
- 清理所有临时调试 console.log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: 清理全部调试日志

移除 autocomplete 模块中所有临时 console.log 调试语句,
仅保留 figSpecLoader 中的 console.warn 用于真实错误报告。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 三审问题修复 — 移除多余 prop、过滤子路径 spec、防路径遍历

1. 移除 Terminal.tsx 传给 AutocompletePopup 的多余 onClose prop
2. getCommandNameSuggestions 过滤含 / 的 spec 名(aws/s3 等不是直接命令)
3. figspec:load IPC handler 添加 .. 路径遍历检查

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 5 个问题全部修复

1. [P1] fuzzy 匹配建议不以 userInput 开头时,用 Ctrl+U 清行再写入完整命令,
   避免 substring 截断产生损坏的命令行
2. [P2] Ghost addon 初始化改用 polling 等待 termRef,解决首次挂载时
   termRef.current 为 null 导致 ghost text 永远不激活的问题
3. [P2] popup overlay 改为 pointer-events-none 透传,仅 popup 自身设
   pointer-events: auto,不再阻止终端区域的鼠标交互
4. [P2] getCompletions 异步返回后重新 detectPrompt 校验输入是否已变,
   丢弃过时的补全结果避免覆盖新状态
5. [P2] prompt 检测支持折行:当 line.isWrapped 时向上回溯查找 prompt 行,
   拼接多行内容作为完整 userInput

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第二轮 3 个问题修复

1. [P2] broadcast 模式下 autocomplete 插入也触发广播 —
   onAcceptText 回调中调用 onBroadcastInputRef 通知其他 session
2. [P2] 支持无尾随空格的 prompt(如 cmd.exe C:\path>)—
   prompt 字符后允许直接是行尾,boundary 为 i+1
3. [P2] 光标移动 escape 序列(Left/Home/End)清除过时建议 —
   不再静默忽略,改为 clearState() 清除 popup 和 ghost text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第三轮 3 个问题修复

1. [P2] commandBufferRef 处理 Ctrl+U 清行 — fuzzy 匹配发送 \x15 时
   重置 buffer,避免 onCommandExecuted 记录错误的拼接命令
2. [P2] fetchVersionRef 递增计数器废弃过时异步结果 — clearState/Escape
   关闭 popup 时 bump version,getCompletions 返回后检查 version 匹配,
   防止已关闭的 popup 被旧请求重新打开
3. [P2] prompt scanLimit 从 80 提高到 200 — 支持包含 git branch、
   kube context、长路径的 prompt,超过 80 列不再失效

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第四轮 3 个问题修复

1. [P1] 拒绝绝对路径 — figspec:load IPC handler 检查 commandName
   不以 / 或 \ 开头,防止 path.join 丢弃前缀导致任意 JS 执行
2. [P1] cmd.exe prompt > 后不要求空格 — 对 > ❯ ➜ › 等 prompt 字符
   不强制要求后跟空格,支持 C:\src>dir 格式
3. [P2] serial line mode 下 autocomplete 走 serialLineBufferRef —
   在串口 lineMode 时不直接 writeToSession,而是缓冲到 line buffer
   并处理 local echo,与正常按键输入行为一致

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第五轮 — translateToString(false) 保留尾部空格

translateToString(true) 会 trim 行尾空格,导致 cursorX 截取的
userInput 与实际行内容不一致。改为 translateToString(false) 保留
原始空格,确保 "git commit " 的尾部空格被正确保留用于触发选项补全。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: 设置页添加自动补全开关(启用/Ghost Text/弹出菜单)

在终端设置页末尾新增「自动补全」区域,包含三个开关:
- 启用自动补全:总开关
- 行内建议(Ghost Text):光标后灰色建议文本
- 弹出菜单:浮动补全列表

子开关在总开关关闭时 disabled。中英文 i18n 翻译齐全。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第六轮 3 个问题修复

1. [P1] 光标不在行尾时禁止补全 — 检测 cursorX 后方是否有字符,
   有则 clearState 不显示建议,避免 mid-line 插入导致文本重复
2. [P2] Enter 录入历史改为先尝试实时 detectPrompt,失败则 fallback
   到 lastPromptRef 缓存,应对高延迟 SSH 下 buffer 未回显的情况
3. [P2] fuzzy 替换在 Windows host 上用退格清行而非 Ctrl+U —
   cmd.exe/PowerShell 不支持 Ctrl+U,改为发送 \b 退格序列

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第七轮 — commandBuffer 退格处理 + 接受后历史记录

1. [P2] commandBufferRef 处理 \b 退格 — Windows fuzzy 替换用退格
   清行时正确移除 buffer 末尾字符,避免记录拼接错误的命令
2. [P3] lastAcceptedCommandRef 追踪接受的补全文本 — Tab/→ 接受后
   立即 Enter 时用追踪值录入历史,不依赖可能未回显的 buffer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第八轮 — 历史记录准确性 + 设置同步

1. [P2] 用户继续编辑后清除 lastAcceptedCommandRef — Tab 接受
   "git status" 后追加 " --short" 再 Enter 时记录完整编辑后的命令
2. [P2] Ghost text →/Tab 接受路径也设置 lastAcceptedCommandRef —
   确保所有接受路径在快速 Enter 时都能准确记录命令
3. [P2] autocomplete 设置加入 SYNCABLE_TERMINAL_KEYS —
   跨设备同步时保留自动补全偏好

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第九轮 — REPL 误识别 + 本地终端 OS 检测

1. [P1] local terminal 的 hostOs 改用 navigator.platform 检测实际 OS,
   避免 Windows 上 fallback 到 "linux" 导致 Ctrl+U 清行失败
2. [P2] 回退 > 无条件接受改动,恢复要求 > 后跟空格或行尾 —
   避免 python >>>、mysql>、sqlite> 等 REPL 被误识别为 shell prompt
3. 新增 REPL NON_PROMPT_PATTERNS:>>>(python)和 word>(mysql/redis)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第十轮 4 个问题修复

1. [P1] cmd.exe prompt C:\path> — 对 > 特判:前面是 \ 或 / 时允许无空格,
   避免误匹配 REPL(python>>>、mysql>)的同时支持 Windows cmd prompt
2. [P2] serial lineMode autocomplete 不再 early return — fall through 到
   共享的 commandBuffer/broadcast 更新逻辑
3. [P2] serial 字符模式 + localEcho 时 autocomplete 插入文本也本地回显
4. [P3] 运行时关闭 autocomplete 时调用 clearState() 清除已显示的 popup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第十一轮 — option args、PS2 误识别、bridge 缓存

1. [P2] resolveSpecContext 返回 option 的 args — 当光标在 option 参数
   位置时(如 git archive --format |),返回该 option 的 args 而非
   subcommand 的 args,使 tar/zip 等枚举值能正确补全
2. [P2] 排除 bare > 作为 shell prompt — bash PS2 续行提示 > 加入
   NON_PROMPT_PATTERNS,避免在多行命令续行和 REPL 中误触发补全
3. [P3] bridge 不存在时不缓存 null — preload 时 bridge 可能未就绪,
   缓存 null 会永久禁用该命令的 spec 补全

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第十二轮 — prompt 检测取最后一个分隔符

Starship/Powerlevel10k 等 prompt 包含多个 prompt 字符
(如 ➜  repo git:(main) $),之前在第一个 ➜ 就停了,
把后续 prompt 文本当成用户输入。

改为收集所有候选 prompt 边界,返回最后一个。确保
"➜  repo git:(main) $ ls" 中 userInput 正确为 "ls"。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Codex review 第十三轮 — prompt 搜索范围限制 + cmd.exe 路径

1. [P2] prompt 扫描限制在行前 60% — 避免 "echo foo > bar" 中的
   重定向符 > 被当作 prompt 结束(prompt 不会出现在行尾部分)
2. [P3] cmd.exe 路径检测扩展 — 除了 \ / 前缀,也检测行首是否有
   驱动器号 (X:) 模式,支持 C:\Users\me> 等标准 Windows prompt

P1 (高延迟 SSH buffer 滞后) 和 P2 (Enter 时 stale prompt) 属于
prompt 检测方案的固有局限,根本解决需要 OSC 133 Shell Integration,
不在本 PR 范围内。已有 lastAcceptedCommandRef fallback 缓解。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:45:34 +08:00
陈大猫 34f9d2a663 chore: 死代码清理与架构分层修复 (#524)
* chore: 移除死代码并修复架构分层违规

- 删除未使用的 ACP 模块 (infrastructure/ai/acp/)
- 删除未使用的 AI 组件 (ExecutionPlan, PermissionDialog)
- 将 syncPayload.ts 从 domain 移至 application 层,修复分层违规
- 移除未使用的导出 (useSecurityState, useProviderStatus, GitHubAuthState,
  getAgentCommandLabel, ImageAttachment, HotkeyActions)
- 收窄 Electron bridge module.exports,移除未使用的导出函数
- 将仅内部使用的函数/类型取消导出 (isSupportedLocale, SyncDashboard)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: 二次审查清理 — 移除更多死代码和架构违规

- 移除未使用的 ConversationEmptyState 组件和类型
- 移除未使用的 PromptInputSelect 系列组件 (5 个导出)
- 移除 global.d.ts 中残留的 SMBConfig 类型和 cloudSyncSmb* 方法声明
- 移除 useAutoSync.ts 中未使用的 toast 导入 (同时修复 application→components 反向依赖)
- 清理因删除而产生的多余 import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: 消除直接 localStorage 访问,提取 safeSend 共享工具

localStorage 集中化:
- 新增 storageKeys 常量: SIDE_PANEL_WIDTH, PF_RECONNECT_CANCEL, DEBUG_HOTKEYS, DEBUG_UPDATE_DEMO
- TerminalLayer/SettingsApplicationTab/App.tsx/useUpdateCheck 改用 localStorageAdapter
- CloudSyncManager 内部方法改用 localStorageAdapter
- portForwardingService 改用 localStorageAdapter + 集中 key

safeSend 去重:
- 新增 electron/bridges/ipcUtils.cjs 共享模块
- sshBridge/sftpBridge/portForwardingBridge/sshAuthHelper/aiBridge 统一引用

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: 终审清理 — 移除未使用的 require 和废弃类型别名

- 移除 sftpBridge.cjs 中未使用的 require("node:net")
- 移除 aiBridge.cjs 中未使用的 require("node:path")
- 移除 types.ts 中已废弃的 ChatMessageImage 类型别名

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 ESLint 错误 — 组件不再直接导入 infrastructure

- 新增 useStoredNumber hook,TerminalLayer 通过 hook 访问侧边栏宽度
- SettingsApplicationTab 的 isUpdateDemoMode 改为从 useUpdateCheck hook 传入
- 移除 useCloudSync.ts 中未使用的 CloudSyncManager 导入和 GitHubAuthState 接口

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: 提取 notification port,消除 application 层对 components 的依赖

将 toast 通知抽象为 application/notification.ts 端口,
UI 层通过 setNotify 注入实现,useAutoSync 改用 notify 接口。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:14:37 +08:00