* fix(ssh): per-host skipEcdsaHostKey toggle + advanced algorithm overrides (#1027)
#1027 reported an old Huawei S7706 (SSH banner `SSH-2.0--`, empty
software-version field) where the legacy-algorithms toggle still
couldn't get a connection through. The debug log shows the handshake
makes it through every negotiation step, picks `ecdsa-sha2-nistp521`
for the host key, then dies at:
Handshake failed: signature verification failed
i.e. ssh2's strict RFC verifier rejects the ECDSA signature the
switch produces. OpenSSH on the same machine connects because its
known_hosts is already pinned to an RSA fingerprint, so it never
advertises ECDSA in the first place.
This adds two layers of escape hatch:
A. `host.skipEcdsaHostKey` (one-click) — drops every `ecdsa-sha2-*`
from the offered host-key list. Forces the fallback to
ssh-rsa / ssh-dss / ssh-ed25519 that those old stacks implement
correctly. Wired through ssh / sftp / port-forwarding bridges,
inheritable from the group default.
B. `host.algorithms` (advanced) — per-category override lists
(`kex`, `cipher`, `hmac`, `serverHostKey`, `compress`). When a
category's array is non-empty, it fully replaces the negotiated
list for that category. Exposed in a collapsible "Advanced
algorithm overrides" panel on both host and group settings,
inspired by Tabby's per-profile algorithm UI. Empty arrays
normalize to "use default" so picking zero algorithms in a
category doesn't bench the connection with
"no matching algorithm".
Overrides apply BEFORE `skipEcdsaHostKey` so the latter stays an
unconditional kill switch even if the user explicitly puts
`ecdsa-*` back into the host-key list.
Behavior with default values (neither toggle set, no override list)
is identical to before — zero change for hosts that aren't opted in.
Tests cover the new options on both `buildAlgorithms` and
`buildSftpAlgorithms`, plus group→host inheritance for both fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): address Codex review on #1116 — proper seed + jump-host carry + missing call sites
Four review findings from the Codex pass on #1116, all real:
1. AlgorithmOverridesPanel was seeding the first customization in a
category from `SUPPORTED_ALGORITHMS_BY_CATEGORY`, which contains
legacy algorithms (CBC, arcfour, MD5, ssh-dss, group1-sha1...).
Unchecking a single modern algorithm in a host that had legacy mode
*off* would silently start offering those legacy algorithms. Now
seeds from `effectiveDefaultAlgorithms(legacyEnabled)`, mirroring
what `buildAlgorithms` actually emits at connect time.
- New `effectiveDefaultAlgorithms` pure helper in
`domain/sshAlgorithmList.ts` with its own test suite (also asserts
the modern subset contains no SHA-1 KEX / CBC / arcfour / MD5)
- `AlgorithmOverridesPanel` takes a `legacyEnabled` prop
- `isChecked` now reflects the effective-default state for an
untouched category, so unchecked rows visually represent
algorithms the connection wouldn't currently advertise
2. The chain-mode jump-host loop in `sshBridge.cjs` was applying the
target host's `skipEcdsaHostKey` / `algorithmOverrides` to every
bastion hop, which is wrong when the bastion has its own settings.
This was actually a pre-existing issue with `legacyAlgorithms` too
— `NetcattyJumpHost` simply didn't carry any of these fields.
- `NetcattyJumpHost` gains `legacyAlgorithms`, `skipEcdsaHostKey`,
`algorithmOverrides`
- `createTerminalSessionStarters` populates them from each jump
host's own configuration
- The jump-host bridge call now reads `jump.* ?? options.*` so a
hop with its own setting wins, but unset hops still fall back to
the target's settings (preserves historic chain-wide behavior
when nothing is overridden)
3. `infrastructure/services/portForwardingService.ts` only forwarded
`legacyAlgorithms` to the port-forwarding bridge, so a host that
needed the ECDSA skip or advanced overrides could connect through
the terminal but its auto-start tunnels would still hit the
original handshake failure.
- Forward `skipEcdsaHostKey` and `algorithmOverrides` at the target
call site and at the jump-host map.
4. `application/state/sftp/useSftpHostCredentials.ts` built
`NetcattySSHOptions` for `openSftp` without any of the algorithm
fields — and on inspection it didn't even forward `legacyAlgorithms`
to begin with, so SFTP panes for legacy-mode hosts were silently
negotiating with the modern default list. Same gap at the jump-host
map.
- Forward all three fields at both the target and the jump-host map.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* polish(hosts): make the advanced-algorithm collapsible trigger a real button
The first cut used a plain underlined caption for the
"Advanced algorithm overrides" collapsible trigger. That blends into
the surrounding helper text and doesn't read as an interactive
control — users couldn't tell that the per-category checkbox editor
was reachable at all.
Match the project's existing collapsible-trigger pattern (see
`SerialConnectModal`): full-width ghost Button, label on the left,
ChevronDown / ChevronUp on the right that flips with open state,
controlled via `useState`. Applied in both `HostDetailsPanel` and
`GroupDetailsPanel`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* polish(hosts): rename Legacy Algorithms card to SSH Algorithms; split Backspace into its own section
The card now carries three algorithm controls (Allow Legacy / Skip
ECDSA / Advanced overrides) plus a Backspace Behavior dropdown that
doesn't belong with the rest. Two cleanups:
1. Rename the card from "Legacy Algorithms" to "SSH Algorithms".
The original title only described the first toggle; the section
now covers the whole algorithm-negotiation surface, including the
ECDSA host-key skip and the per-category override editor.
- i18n key renamed `hostDetails.section.legacyAlgorithms`
-> `hostDetails.section.sshAlgorithms` in zh-CN / en / ru
- `HostDetailsPanel` references the new key
2. Move the Backspace Behavior control out of the algorithm card.
- `HostDetailsPanel`: new dedicated "Terminal Behavior" card
(TerminalSquare icon) placed between SSH Algorithms and
Keepalive. New i18n key `hostDetails.section.terminalBehavior`.
- `GroupDetailsPanel`: no separate Card scaffolding for group
defaults — moved Backspace to the bottom of the SSH section
(after Mosh) so it visually separates from the algorithm block
above without introducing a new card heading.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sftp): per-hop algorithm settings in SFTP chain (Codex review on #1116)
`sshBridge.cjs`'s jump loop already reads `jump.* ?? options.*` for
the three algorithm fields, but `sftpBridge.cjs#connectThroughChainForSftp`
was missed in that change and still applied the target host's
`options.legacyAlgorithms` / `skipEcdsaHostKey` / `algorithmOverrides`
to every bastion. A bastion that needed the ECDSA skip or a custom
algorithm list while the target didn't would still fail the SFTP
handshake before reaching the target.
Mirror the sshBridge fix: read each setting from the jump host first,
falling back to the target options when the hop didn't override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): keychain SSH exec + advanced editor seed honor inherited algorithm settings
Two findings from a local Codex review pass on the branch:
1. The keychain "export public key to host" flow opens its own one-off
SSH connection through `sshBridge#execCommand`, but the connectOpts
built there didn't include any `algorithms`. ssh2 then negotiated
with its built-in modern defaults regardless of what the host had
set, so a host that needs the ECDSA skip (or legacy mode) would
connect in the terminal but the keychain export would fail with
the original signature-verification error.
- `sshBridge.cjs#execCommand` now sets `algorithms` from
`payload.legacyAlgorithms` / `skipEcdsaHostKey` /
`algorithmOverrides`, mirroring `startSSHSession`.
- `useKeychainBackend`'s `execCommand` typing gets the three new
optional fields.
- `KeychainManager` forwards the host's `legacyAlgorithms`,
`skipEcdsaHostKey`, and `algorithms` when invoking the export.
2. The Advanced Algorithm Overrides editor seeded its first
customization from `form.legacyAlgorithms`, which is the host's
own field — it doesn't reflect a value the host *inherits* from
its group's default. A user in a group with `legacyAlgorithms=true`
editing a host that hadn't explicitly set the flag would see the
editor seed in modern-only mode, and saving could silently drop
the legacy algorithms the host actually needed.
- `HostDetailsPanel` passes `form.legacyAlgorithms ?? groupDefaults?.legacyAlgorithms`.
- `GroupDetailsPanel` adds an `inheritedLegacyAlgorithms` memo
resolved from the parent group chain via `resolveGroupDefaults`,
and the editor uses `form.legacyAlgorithms ?? inheritedLegacyAlgorithms`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(keychain): export-public-key honors group-inherited algorithm settings
Second-pass Codex review on PR #1116 flagged that the keychain
"export public key to host" flow now carries the three algorithm
fields end-to-end, but only reads them from `exportHost.*` directly —
which doesn't reflect values the host inherits from its group's
defaults. A host that left `legacyAlgorithms` / `skipEcdsaHostKey` /
`algorithms` unset but sat inside a group that turned them on would
work fine in the terminal (the terminal starter applies group
defaults before sending the IPC payload) but its keychain export
would silently fall back to ssh2's modern defaults and hit the
original signature-verification failure.
Resolve the effective host with `applyGroupDefaults` +
`resolveGroupDefaults` before the `execCommand` call, then read the
algorithm fields off the effective host. Requires plumbing
`groupConfigs` into `KeychainManager` (added as an optional prop,
forwarded by `VaultView`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(host-details): algorithm-overrides editor reads currently selected group's defaults
Third-pass Codex review caught that the editor seed was reading from
the `groupDefaults` prop, which is whatever group the host belonged
to when the panel opened. If a user switched the host into a different
group inside the panel and then opened the Advanced Algorithm
Overrides collapsible before saving, the editor would seed from the
old group's `legacyAlgorithms` flag and could save the wrong list.
The panel already memoizes `effectiveGroupDefaults` from
`form.group`/`defaultGroup`/`groupConfigs` for exactly this kind of
re-resolution (used by theme/font effective lookups). Read the
inherited flag from there instead of the stale prop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(algorithms): trim UI cipher list to algorithms ssh2 actually supports
Fourth-pass Codex review flagged that `SUPPORTED_CIPHER_ALGORITHMS`
included `blowfish-cbc`, `cast128-cbc`, and the `arcfour*` family,
but OpenSSL 3 disabled those primitives, so ssh2's `canUseCipher`
filter drops them from `SUPPORTED_CIPHER` at startup. Selecting any
of them in the Advanced Algorithm Overrides editor would make
`ssh2.Client.connect()` throw `Unsupported algorithm` synchronously,
turning the override into a "host now unreachable" footgun instead
of a narrowing knob.
Realigned each `SUPPORTED_*_ALGORITHMS` list to ssh2's actual
`SUPPORTED_*` constants:
- `SUPPORTED_CIPHER_ALGORITHMS`: dropped blowfish / cast128 / arcfour;
added the no-suffix `aes128-gcm` / `aes256-gcm` variants ssh2 also
accepts.
- `SUPPORTED_KEX_ALGORITHMS`: added `diffie-hellman-group15-sha512`
and `diffie-hellman-group17-sha512` (present in ssh2 but missing
from the UI list); reordered to ssh2's canonical order.
- `SUPPORTED_HMAC_ALGORITHMS`: reordered so the ETM/SHA-2 grouping
matches ssh2's `DEFAULT_MAC` and lookups are predictable.
Locked the invariant in `sshAlgorithmList.test.ts` with a new test
that asserts every UI-offered algorithm is a member of ssh2's
`SUPPORTED_*` for its category. A future ssh2 bump that drops an
algorithm we still expose will fail this test instead of silently
becoming a connect-time error for users.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(algorithms): run KEX override through the runtime fixed-DH filter
Codex round-N flagged a gap in `applyAlgorithmOverrides`: when the
user supplies a custom KEX list via the advanced editor, the previous
implementation copied it verbatim into the negotiated `algorithms.kex`
field. The default builder already passes its KEX list through
`filterSupportedFixedDhKex` to drop fixed-DH groups the runtime
doesn't support (notably `diffie-hellman-group1-sha1` on
Electron/BoringSSL, which lacks modp2), but the override path
bypassed that filter — so an Electron user enabling legacy mode and
saving any KEX checkbox state would re-advertise group1-sha1 and the
handshake would crash with "Unknown DH group" instead of failing fast.
Apply the same `filterSupportedFixedDhKex` to the override list. New
test in `sshAlgorithms.test.cjs` exercises a simulated BoringSSL
runtime that lacks modp2 and asserts the override-filtered KEX no
longer includes group1-sha1, while group14-sha1 / group-exchange-sha1
remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(algorithms): run HMAC override through the FIPS MD5 filter
Codex flagged the same runtime-bypass pattern Round-N-1 fixed for KEX,
now in HMAC: `applyLegacyHmacAlgorithms` gates `hmac-md5` behind
`md5Supported()` so FIPS Node builds don't get it, but the UI's
`effectiveDefaultAlgorithms(true)` seed adds `hmac-md5` /
`hmac-md5-96` unconditionally. A user with legacy mode on who saves
any HMAC checkbox change would push those MD5 entries through
`applyAlgorithmOverrides`, which previously copied the override
verbatim — bypassing the FIPS gate and making ssh2 throw
"Unsupported algorithm" before negotiation.
New `filterRuntimeUnsupportedHmac` helper applies the same
`md5Supported()` gate to a user override. `applyAlgorithmOverrides`
routes the HMAC override through it (mirrors how the same function
already routes KEX overrides through `filterSupportedFixedDhKex`).
New test simulates a FIPS-disabled MD5 runtime and asserts MD5
variants drop from the final HMAC list while SHA-1 / SHA-2 entries
remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): jump-host overrides no longer inherit the leaf's algorithm overrides
Codex flagged that the jump-loop fallback I added for chain
convenience applied the target's per-host \`algorithmOverrides\` to
every bastion whose own override wasn't set, which is wrong: a target
restricted to e.g. \`serverHostKey: [\"ssh-rsa\"]\` would lock the hop
to ssh-rsa too and break negotiation against an Ed25519-only bastion.
The fallback IS still correct for \`legacyAlgorithms\` and
\`skipEcdsaHostKey\` — those are append/safety toggles that widen the
offered list, so propagating them to a bastion is safe and matches
the historic chain-wide behavior of \`options.legacyAlgorithms\`
(single-toggle convenience for a chain with one old leaf).
Treat \`algorithmOverrides\` strictly per-host instead. Same change in
both \`sshBridge.cjs\` and \`sftpBridge.cjs\` jump loops, with a
comment block explaining the asymmetry so a future refactor doesn't
"clean up" the distinction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(host-details): surface inherited algorithm overrides in the advanced editor
Codex flagged a real gap in the inheritance model: when a parent
group has set algorithm overrides (e.g. \`algorithms.kex: [...]\`),
a host or child group under it can't simply Reset a category back to
NetCatty's defaults — \`applyGroupDefaults\` treats an unset host
field as "inherit", so the local Reset falls back to the group's
list rather than to ssh2's defaults. Cleanly distinguishing "reset
to NetCatty defaults" from "inherit from group" needs a new schema
field or sentinel, which is a non-trivial design change well beyond
the scope of this PR.
For now, surface the situation in the UI so the user understands
why Reset doesn't behave the way they might expect and where to go
to actually clear the restriction:
- \`AlgorithmOverridesPanel\` accepts an optional \`inheritedFromGroup\`
prop and, when populated, renders a blue inline notice listing the
inherited categories and directing the user to the group's
algorithm settings if they need to opt out.
- \`HostDetailsPanel\` passes \`effectiveGroupDefaults?.algorithms\`.
- \`GroupDetailsPanel\` adds a new \`inheritedAlgorithmOverrides\`
memo that resolves the same way the existing
\`inheritedLegacyAlgorithms\` does, and forwards it.
- i18n strings added in zh-CN / en / ru.
Follow-up (out of scope for this PR): if real users do hit this,
introduce a \`host.algorithms = null\` (or explicit
\`algorithmsOverride: boolean\`) sentinel and a Reset-to-defaults
button that uses it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): jump-host skipEcdsaHostKey is per-host; panel seeds from inherited overrides
Three findings from the latest Codex pass:
1 & 2. `skipEcdsaHostKey` on the leaf was still falling back onto
jump hosts in both `sshBridge.cjs` and `sftpBridge.cjs`. I had
classified it with `legacyAlgorithms` as a "safety widening" knob,
but Codex is right that it actually *narrows* the offered host-key
list by dropping every `ecdsa-sha2-*`. An ECDSA-only bastion (or
one where the operator pinned ECDSA via known_hosts) would still
negotiate when ECDSA is offered, but fails when the leaf's skip is
propagated to it. Same fix as `algorithmOverrides` last round:
strictly per-host. Only `legacyAlgorithms` keeps the chain-wide
fallback (append-only — it widens the offer, can never break a
hop that wasn't already failing).
3. `AlgorithmOverridesPanel` was seeding the first customization of a
category from the NetCatty/legacy effective default, ignoring any
list the host inherits from its group for OTHER categories.
Because `applyGroupDefaults` treats `host.algorithms` as an
all-or-nothing inherit boundary, the moment the user saved any
host-local override `{ cipher: [...] }`, the group's
`{ serverHostKey: ["ssh-rsa"] }` restriction silently dropped from
the effective host. Now:
- `toggleAlgorithm`'s first-click seed reads
`inheritedFromGroup?.[category] ?? effectiveDefault[category]`,
so customizing one category preserves the group's narrowing on
the others.
- `updateCategory` initializes `next` from
`{ ...inheritedFromGroup, ...value }` so saved overrides carry
the inherited categories alongside the host's own edits.
- `isChecked` reflects the inherited list when there's no local
value, so the visible checkbox state matches what would actually
be advertised.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(host-details): Reset preserves the inherited list instead of silently widening
Codex caught a follow-on bug from the previous "carry inheritance"
fix: pressing Reset on a category that's inherited from the group
deleted that category from \`host.algorithms\` while other host-local
or carried-inherited categories remained. Because
\`applyGroupDefaults\` treats \`host.algorithms\` as all-or-nothing,
the moment any host-local entry exists the group's \`algorithms\`
object stops being inherited as a whole — so the freshly-deleted
category fell back to NetCatty defaults rather than the group's
narrower list. Effective result: Reset on an inherited category
*widened* the offer instead of restoring it.
Reset now persists the inherited list verbatim onto the host when
the group has an override for that category, so "Reset" means "use
what this host would otherwise inherit" in all cases.
Also tightened \`isCustomized\` to suppress the "customized" badge
(and the per-category Reset button) when the host's stored list is
identical to the inherited list — those rows haven't really been
customized by the user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(host-details): gate the inherited-notice / checkbox baseline on no host override
Codex caught the asymmetry between the read and write sides of the
panel. The write side (`updateCategory`, `toggleAlgorithm`, Reset)
intentionally carries `inheritedFromGroup` onto the host on the first
edit so the runtime's all-or-nothing inherit boundary in
`applyGroupDefaults` doesn't silently widen the offer. But the read
side (the inherited-notice banner and the `isChecked` baseline for
unfilled categories) was applying inherited values *unconditionally*
— including when the host already had any local `algorithms` object,
which makes `applyGroupDefaults` stop inheriting from the group as a
whole. Net effect on a host that only locally overrode `cipher`: the
UI claimed the group's `serverHostKey` restriction was still in
effect, while the runtime would actually use NetCatty's modern
defaults for that category.
Introduce `inheritedForDisplay`, defined as `value === undefined ?
inheritedFromGroup : undefined`, and route the notice + the
`isChecked` baseline through it. The write side keeps consulting the
unconditional `inheritedFromGroup` so the carry-over still happens on
first edit — that part is what makes the runtime behavior match what
the UI used to advertise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(host-details): legacy/skipEcdsa toggles reflect group-inherited value
Codex caught that the Skip ECDSA toggle in both the host and group
panels read `form.skipEcdsaHostKey` directly, so a host whose group
turned the flag on (and `applyGroupDefaults` therefore applied it to
the runtime SSH negotiation) still saw the toggle rendered as off.
Worse, clicking it computed `!form.skipEcdsaHostKey` — which is `!undefined`
= `true` — so the first click could not actually disable the
inherited setting on a per-host basis; it would just store `true`
explicitly, the same effective state.
The Allow Legacy Algorithms toggle had the same pre-existing issue.
Fix both at once: each toggle now derives its enabled state from
`form.<field> ?? <inherited>` and the onToggle handler flips that same
effective value, so the toggle accurately represents what the runtime
would do and a single click off correctly stores `false`
(which `applyGroupDefaults` then leaves alone, breaking inheritance
for this host).
- `HostDetailsPanel` reads from `effectiveGroupDefaults`.
- `GroupDetailsPanel` adds an `inheritedSkipEcdsaHostKey` memo
alongside the existing `inheritedLegacyAlgorithms` and uses both.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix#954: unify Tooltip styling + replace native selects
Replace native HTML title= tooltips and native <select> dropdowns
with the existing Radix-based Tooltip / Select components so they
share the app's rounded styling, theme tokens and i18n pipeline.
Adds a global TooltipProvider in AppWithProviders so every
descendant Tooltip works without a per-file Provider wrapper.
Scope (driven by the issue #954 examples and "全部都处理" follow-up):
- TerminalLayer toolbar: Add Terminal / Split View / SFTP / Scripts
/ Theme / AI Chat / Move panel / Close panel.
- TopTabs middle bar: quick switcher, more tabs, AI assistant, theme
toggle, settings; window-control buttons (min/max/close), tray
close and hotkey reset/disable have their native title dropped per
the user's explicit opt-out ("可以不用Tooltip,直接全局禁用
原生title 属性").
- AI panels: AIChatSidePanel session history / new chat / delete,
ConversationExport, AgentSelector, ChatInput attach / expand /
permission, ModelSelector, ProviderCard, ai-elements/tool-call.
- SFTP: SftpSidePanel header, SftpBreadcrumb, SftpFileRow,
SftpPaneToolbar, SftpTabBar, SftpTransferQueue.
- Settings: SettingsPage close, SettingsAppearanceTab theme/accent
swatches, SettingsFileAssociationsTab edit/remove, SettingsSystemTab
crash-log paths and global hotkey reset.
- Host vault: HostDetailsPanel (clear / suggestions / show-password /
key path / browse key), GroupDetailsPanel, KnownHostsManager,
ConnectionLogsManager, KeychainManager, SyncStatusButton,
CloudSyncSettings, LogView, QuickSwitcher, ScriptsSidePanel,
Terminal status bar copy-host + broadcast/focus, ZmodemProgressIndicator.
- Terminal subcomponents: HostKeywordHighlightPopover, TerminalComposeBar,
TerminalConnectionDialog, TerminalSearchBar.
- Editor: TextEditorPane (subtitle, search, wrap, promote-to-tab).
- TrayPanel session rows and port-forwarding rows.
Native <select> migrated to custom Select component:
- SerialConnectModal (data bits, stop bits, parity, flow control)
- SerialHostDetailsPanel (same four fields)
- HostDetailsPanel backspace behavior
- GroupDetailsPanel backspace behavior
- SettingsTerminalTab local shell picker
- terminal/ThemeSidePanel font weight
Hardcoded English strings extracted to i18n. New keys for both
en and zh-CN: terminal.layer.*, topTabs.*, ai.chat.* (sessionHistory,
attach, collapse, expand, enableAgent), zmodem.*, settings.shortcuts.
resetToDefault. Inline help text on SnippetsManager package-name input
removed because the same hint is already shown in a visible <p> below
the input.
Existing per-file <TooltipProvider> wrappers (SnippetsManager,
ScriptsSidePanel, SelectHostPanel, RuleCard, HostDetailsPanel proxy
section) are left in place — they nest harmlessly under the global
provider and stay self-sufficient for component tests.
Tests:
- tsc clean for changed files (pre-existing repo-wide errors
unrelated to this PR).
- All 802 tests pass (3 skipped pre-existing).
- HostDetailsPanel.proxyProfile.test and TextEditorPane.test
updated to wrap with TooltipProvider, matching the runtime
context now needed by the migrated components.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix#954: wrap Settings + Tray windows with TooltipProvider
Settings and the tray panel mount as separate Electron windows with
their own React root in index.tsx, so they do not inherit the global
TooltipProvider added under AppWithProviders. After the unified
Tooltip migration, any settings tab that used a Tooltip (Appearance,
Application, FileAssociations, System, Shortcuts, Terminal, AI
ProviderCard, AI ModelSelector) — and TrayPanel — threw
"Tooltip must be used within TooltipProvider" and rendered nothing.
Wrap both branches with TooltipProvider at the same level as
ToastProvider in index.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fixed 8% brightness causes compositers to have severe rendering issues. (Only effected on the Midnight color scheme) 10% seems to be okay.
- Reduced backdrop-blur as it's expensive CSS.
- Removed radial-gradient backgrounds (they don't show up)
* Replace app logo across window icon, tray, splash, and in-app brand
- public/logo.svg: new netcatty mark
- public/icon.png: regenerated 1024x1024 from new SVG (source for
electron-builder — .icns/.ico rebuilt automatically at pack time)
- public/dmg-fix-icon.png: regenerated 1024x1024
- public/tray-icon{,@2x}.png: regenerated color 16/32px for Linux/Windows
- public/tray-iconTemplate{,@2x}.png: regenerated monochrome silhouette
for macOS menu bar (background stripped, foreground flattened to
black on transparent so template-image rendering produces a clean
mask)
- components/AppLogo.tsx: render the new logo as a static <img>. The
old hand-coded inline SVG bound fills to the accent CSS variable;
the new mark has a fixed palette, so callers keep their sizing /
rounding classes via className while the asset itself is a single
file served from /public.
- index.html: splash screen now uses the same /logo.svg via <img>,
with border-radius for the rounded-square frame.
* Polish logo: theme the in-app mark, gloss the OS icon, shrink cat
- components/AppLogo.tsx: back to an inline SVG. Background rect fills
with hsl(var(--primary)) so the in-app brand follows the theme
accent (was fixed navy when imported as <img>). Cat scaled to 68%
of the frame and centred so it doesn't crowd the edges at small
sidebar sizes.
- public/logo.svg + regenerated PNGs: polished OS icon variant with a
large rounded-square clip (rx 224 on 1024), top-left spotlight
radial gradient, subtle top sheen + bottom darkening, and an inner
edge vignette for a slight chamfer. The cat is shrunk to the same
68% as the in-app logo for visual consistency.
- Monochrome tray template (macOS menu bar) is rebuilt from the
shrunk-cat path set with all fills flattened to black; keeps a
clean silhouette instead of a filled rounded square.
* Smooth paws, richer gloss on app icon
- Drop the dark toe/claw detail paths from the source illustration
(indices 22-25, 30, 35, 37, 39 — the ones tracing vertical claw
dividers inside the paws). At small sizes those read as teeth/
claws; paws now render as clean rounded blobs.
- public/logo.svg (OS icon source): richer depth pass —
* two-tone navy vertical gradient (lighter top, deeper bottom)
* brighter upper-left spotlight for glassy highlight
* top sheen + bottom darkening for sheen-across-curve effect
* soft elliptical ground shadow beneath the cat to anchor it
* 2% inner edge stroke to crisp the rounded-square chamfer
- components/AppLogo.tsx: regenerated with the same cleaned cat set,
still themed via hsl(var(--primary)). The in-app mark stays flat
(no gloss) because the effect adds nothing at 20-40px sidebar
sizes and would fight theme accents.
- All raster variants (icon.png, dmg-fix-icon.png, tray color + tray
macOS template) rebuilt from the cleaned sources.
* Respect Apple icon safe area; drop gloss, add thin border
macOS icon was rendering to the full 1024x1024 canvas, so it looked
noticeably larger than neighbour apps (VS Code, Ghostty, Zed) in the
Dock. Apple's Big Sur+ convention puts the artwork body inside an
~824x824 safe area centred in a 1024 canvas, which is how those apps
are sized.
- public/logo.svg: artwork body is now 824x824 centred with ~100px
transparent padding. Corner radius 185 (close enough to the macOS
squircle at Dock scale). Cat rescaled so it keeps the same 68%
proportion within the smaller body.
- Gloss layers (spotlight / sheen / ground shadow / vignette) removed
per request — went for a Ghostty-style clean look instead.
- Thin white inner border (stroke 3px, 22% opacity) outlines the
rounded square for definition.
- Tray PNGs for Linux/Windows keep the full-bleed variant (tray slots
expect the icon to fill the space, unlike the Dock safe area).
- components/AppLogo.tsx unchanged conceptually — it still fills its
own bounding box via hsl(var(--primary)); the Apple safe-area rule
is Dock-specific, not relevant to in-app rendering.
* AppLogo: tighten corner radius to match previous (rx 18.75%)
Previous AppLogo used rx=12 on a 64 viewBox (18.75%). The inline
replacement had rx=224 on a 1024 viewBox (21.9%), which combined
with the caller's rounded-xl class read noticeably rounder in the
sidebar. Drop to rx=192 on 1024 viewBox so the in-app mark matches
the old proportions.
* Beef up icon border so it survives Dock downscaling
3 px at 22% opacity disappeared when rasterised down to ~128 px Dock /
Launchpad size. Bumped stroke-width to 8 px and opacity to 40% so the
inner highlight reads as ~1 px at Dock scale. Stroke is inset by
stroke-width/2 so it sits fully inside the rounded-square body (no
anti-alias bleed outside the safe area). Same treatment applied to the
full-bleed tray variant.
* Enlarge cat inside icon tile (68% -> 85% of body)
Dock render had too much navy margin around the mark. Bump the cat's
scale so it fills 85% of the Apple safe-area body while keeping a
visible bezel to the rounded corners and the inner border. Tray color
variant and macOS template (scale 0.9, no border) follow the same
scale-up.
* Add ripple effect on sidebar nav and tidy logo in vault header
- Add RippleButton wrapper + ripple keyframe; use it for the six vault
sidebar nav entries (Hosts, Keychain, Port Forwarding, Snippets,
Known Hosts, Logs) so clicks get a subtle material-style ripple.
- Shrink vault sidebar AppLogo to h-8 w-8 and drop the outer rounded-xl
so the visible corner comes from the SVG's own rx instead of the
container clip.
- Relax AppLogo tile rx/ry to 144 for a more moderate corner radius.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* AppLogo: bump tile corner radius back up to rx 18.75%
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Unify manager toolbars, tighten tabs and vault sidebar title
- Manager toolbars (Keychain, KnownHosts, PortForwarding, Snippets)
normalised to h-14 / h-10 controls with bg-secondary/80 backdrop-blur
and the shared bg-foreground/5 secondary button treatment, so Hosts /
Keychain / Known Hosts / Port Forwarding / Snippets headers size and
tint identically.
- Keychain filter tabs: drop primary tint and cert-count pill; reuse
the same foreground/5 vs foreground/10 active states as other
managers. Search input grown to h-10 to match.
- Known Hosts: removed the leftover text-xs on Scan System / Import
File so they inherit Button's text-sm like every other action.
- TopTabs: drop the 2px active-accent top line and add rounded-t-md +
overflow-hidden so active tabs read as a clean soft tab shape rather
than a banner.
- VaultView sidebar: wordmark grown to text-xl font-black italic with
tightened tracking; logo gap trimmed from 3 to 2.5; outer bg dropped
from secondary/80 to flat secondary to sit flush against the
toolbars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: pin toolbar above content on KeychainManager page
* fix: apply panel offset to outer wrapper so toolbar is not covered
The aside panel is rendered as an absolute overlay (right-0, w-[380px]),
so any container covered by the overlay needs mr-[380px] to avoid
having its right-side controls obscured. Previously only the inner
scroll area had the offset, which left the toolbar at full width —
its right-side controls (view-mode dropdown, etc.) would be covered
by the panel and become unclickable when it opened.
Move both the margin and the transition to the outer flex wrapper so
the toolbar and the scroll area shift together when the panel opens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: yuzifu <yuzifu@TB16PGen5.Info>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add execCommand options to opt into keyboard-interactive auth and wire MFA only
for export-key flows, preserving non-interactive exec usage elsewhere.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Pass managedSources prop through SelectHostPanel and all components that
use it (SnippetsManager, PortForwarding, KeychainManager) so hosts created
in these contexts can properly receive managedSourceId when placed in a
managed group.
This ensures hosts added from any panel will sync back to managed SSH
config files.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
Introduces persistent view mode storage for hosts, keys, snippets,
known hosts, and port forwarding by using dedicated storage keys
and a reusable view mode hook. Improves user experience by
remembering individual section preferences instead of defaulting
to "grid" on each visit.
Enhances the user experience for selecting identities by replacing inline suggestion lists with a popover-based dropdown, including empty-state messaging. Adds right-click context menus to identity cards for quick edit and delete actions. Updates translations for "no results found" in English and Chinese. These changes streamline interaction and improve accessibility when managing identities.
Introduces reusable SSH identities for authentication, allowing hosts to reference centralized identity records containing usernames, passwords, and key/certificate associations. Updates connection, SFTP, terminal, and sync logic to resolve credentials via identities, improving credential management and reuse.
Enhances UI for identity selection, editing, and deletion. Updates localization for related identity and credential strings. Ensures identities are included in sync and vault persistence.
Streamlines SSH connection attempts by preferring key-based auth with fallback to password, both for host and identity-based authentication.
Facilitates safer, more flexible credential management across the application.
Integrates all user-facing strings into the translation system and
expands locale files for comprehensive coverage, enabling dynamic
i18n across the interface. Replaces hardcoded labels, button texts,
and prompts with translation keys, benefiting maintainability and
future language support. Improves translation ergonomics in forms,
dialogs, wizards, and settings, ensuring consistent user experience
in both English and Chinese.
Drops all biometric (Termius-style), WebAuthn, and FIDO2 SSH key logic, UI, and dependencies.
Simplifies key storage and authentication to only support standard and certificate keys.
Eliminates related backend, OS Secure Storage, and Windows Hello/Touch ID code.
Archives any legacy key records for migration safety.
Reduces complexity and potential maintenance/security burden.
Drops all FIDO2 hardware security key and WebAuthn passkey functionality,
including backend bridges, UI components, state hooks, and type definitions.
Simplifies authentication to use only standard, certificate, and biometric
(Windows Hello/Touch ID) keys, improving maintainability and reducing code
complexity. Updates UI, application state, and backend logic to fully remove
FIDO2 options and code paths.
Fixes formatting in biometric key flow and messaging
Resolves minor whitespace inconsistencies and improves
user-facing text for clarity regarding key protection and
biometric unlock requirements.
Enhances readability and maintains consistent formatting
across files.
Introduces Termius-style biometric SSH key generation and usage, leveraging ED25519 keys encrypted with a random passphrase stored securely via OS Keychain or Windows Credential Manager. Enhances key management UI, integrates passphrase retrieval prompts for biometrics, and ensures proper cleanup of secure storage on key deletion. Enables cross-platform biometric authentication for SSH keys, improving security and usability.
Improves code readability by correcting inconsistent indentation
and formatting in biometric credential creation and device
filtering logic. No functional changes are introduced.
Implements a comprehensive UI and backend bridge for FIDO2 hardware and platform authenticator SSH key creation, including device selection, PIN entry, and touch prompts. Modernizes the key generation panel with enhanced options, feedback, and error handling, and establishes Electron IPC support for seamless device communication.
Improves security, user experience, and support for modern authentication hardware.
Ensures migrated keys have generated IDs and labels when missing,
enhancing backward compatibility for legacy data. Updates the
upload function's type usage for better maintainability. Refines
callback dependencies to include all relevant variables, promoting
more reliable reactivity.
Updates user messaging to dynamically reference Touch ID or Windows Hello, enhancing clarity based on platform.
Refines logic to prefer the browser fallback for WebAuthn in all Electron development environments (not just macOS), reducing potential authentication issues and confusion.
Makes minor adjustments to package metadata for consistency.
Replaces legacy GitHub Gist sync UI with a modular cloud sync component and introduces OAuth callback support in the bridge layer. Updates the top bar to use a sync status button linked to settings, improving user experience and future extensibility for cloud sync providers.
Enables users to customize terminal theme and font size for each log replay,
improving readability and personalization of the log view experience. Updates
persist in the log model, and theme/font changes apply instantly without
recreating the terminal. Also improves UI for these controls and tidies minor
imports.
Implements a browser-based WebAuthn fallback for biometric key creation and authentication, improving reliability of Touch ID prompts on macOS, especially when Electron's embedded WebAuthn flow fails. Integrates helper flows, IPC, and UI feedback to guide users through the browser prompt and ensure cross-platform consistency.
Replaces the local HTTP server approach with a custom privileged protocol for loading the app in production, enabling secure WebAuthn (Touch ID/Windows Hello) without the overhead of an internal HTTP server. Improves platform authenticator error handling and ensures RP ID compatibility across environments. Updates development server configs for better cross-platform WebAuthn support and adds packaging scripts and dependencies for electron-builder.
Integrates WebAuthn-based authentication for SSH and SFTP, enabling use of FIDO2 security keys and platform authenticators (Touch ID, Windows Hello) as SSH credentials. Updates UI for better credential selection and feedback. Improves error handling by routing errors through toast notifications instead of inline displays. Refactors backend to route credential details and agent logic for WebAuthn and certificate-based keys, ensuring agent-based authentication is prioritized and fallback to password is possible.
Replaces all direct usage of browser globals and infrastructure service imports in UI components with dedicated application/state backend hooks. Introduces lint rules to prevent direct access to backend bridges and localStorage from components, promoting a cleaner separation of concerns and improved maintainability.
Moves user preferences (e.g., port forwarding form mode) to persistent state hooks, updates port forwarding and SFTP logic to rely on backend hooks, and centralizes logging through a logger utility. Cleans up debug code and removes obsolete scripts from HTML.
Improves testability, prepares for alternative backend implementations, and enforces architectural boundaries.
Updates all code references, IPC channels, CSS classes, and storage keys
from "nebula" to "netcatty" to align with new branding and application identity.
Ensures consistency across frontend, Electron backend, global types,
and package metadata.
Reorder and normalize import statements, group icon imports, and add
missing utility imports (cn). Fix export/newline formatting and minor
type import tweaks. Add ESLint scripts to package.json and update the
lockfile.
Improves UI consistency and interaction by switching filter and
view mode menus from popover to dropdown components.
Enhances code clarity and future extensibility for tab controls.
Extracts major UI panels, file transfer, terminal controls, and SFTP logic into dedicated subcomponents for improved maintainability and clarity.
Modularizes Electron main process, delegating SSH, SFTP, terminal, port forwarding, and window management to bridge modules, reducing complexity and centralizing IPC registration.
Enables easier future development, clearer separation of concerns, and better scalability by making both frontend and backend architectures more modular and easier to reason about.
Replaces dropdown menus with popovers for filter and view actions,
streamlining interactions and visual consistency. Enhances popover
positioning logic to ensure reliable rendering in Electron, addressing
layout and visibility issues with dynamic updates.
Switches UI menu interactions from popover to dropdown for improved stacking and positioning reliability.
Updates z-index and disables problematic animations to prevent display issues, especially on first open.
Enhances consistency and visual layering of menu components across the application.
Refactors card components to use consistent "soft-card" and "elevate" styles,
harmonizes sizing and padding, and standardizes grid gaps for a uniform look
across managers and views. Updates hover effects and background transitions for
better visual feedback. Reduces box-shadow intensity and simplifies elevation
transitions for a cleaner UI.
Unifies avatar, icon, and badge dimensions to 44x44 px with consistent rounded-xl styling for improved visual consistency across list, grid, and card layouts. Simplifies conditional sizing logic and ensures uniform icon appearance regardless of view mode.
Improves usability by introducing toggleable list and grid views, search, and sorting capabilities to key, host, snippet, and identity managers. Replaces some destructive actions in list mode with quick edit buttons for consistency. Enhances filtering via tags and enables a more compact, accessible layout, especially in list view, supporting faster navigation and management of items.
Integrates real SSH keypair generation via Electron backend, replacing previous client-side mock generation for stronger security and reliability.
Refines authentication logic to always provide password credentials for SSH and SFTP connections, ensuring fallback support when key-based authentication fails.
Improves public key export flow with better host update handling and clearer error feedback.
Updates host save logic to correctly update or insert records on key association.
Fixes edge cases in SSH command execution status reporting for more robust error detection.