Commit Graph

51 Commits

Author SHA1 Message Date
陈大猫
f5c3302329 feat: terminal rename, closeSession shortcut, and pane zoom (#1459)
* feat: auto-poll Docker capabilities while Docker tab is active

When the Docker tab is visible and hasDocker is not yet true,
poll refreshCapabilities() at the process refresh interval.
Stop polling once hasDocker becomes true, or when switching
to a different tab.

* fix: use resolvedTab instead of activeTab for Docker auto-poll condition

The auto-poll useEffect condition used activeTab, which stays stale
when Docker becomes unavailable. Changed to resolvedTab which reflects
the actual displayed tab. Also updated the dep array.

* fix: replace setInterval with setTimeout recursion in Docker tab probe

Replace setInterval-based polling with setTimeout recursion in the Docker
tab capability probe effect. This ensures the next probe only starts after
the previous one finishes, avoiding overlapping probes when SSH timeout
exceeds the polling interval.

- Add dockerPollTimerRef to track the timeout handle
- Use async pollOnce() that awaits refreshCapabilities() before scheduling next
- Use cancelled flag in cleanup to prevent scheduling after unmount
- Keep same dependency array for correctness

* fix: stabilize docker poll timer by using useRef for refreshCapabilities

refreshCapabilities() can return a new reference on every render, causing
the useEffect to re-run on every render — cleanup cancels the polling timer,
then the effect immediately calls pollOnce(), effectively bypassing the
configured timeout interval.

Fix: store refreshCapabilities in a useRef (refreshRef), use
refreshRef.current() inside pollOnce(), and replace refreshCapabilities
with refreshRef in the useEffect dependency array.

Closes #PR1456 Codex P2 review item.

* fix: delay auto-poll first probe by one interval to avoid overlap with tab-switch probe

When switching to the Docker tab, two mechanisms were triggering probes:
1. tab-switch effect (line 67-76): immediate probe via refreshCapabilities()
2. auto-poll effect: pollOnce() executing immediately on mount

This caused duplicate probes that waste SSH channel resources.

Fix: pollOnce() no longer fires on mount. Instead, the effect schedules the
first probe with setTimeout(pollOnce, capabilitiesTtlMs), so the first probe
happens after one full interval. Subsequent probes continue at interval pace
via the setTimeout recursion in pollOnce itself.

The tab-switch effect still fires the immediate probe (the correct one),
so responsiveness on tab switch is preserved.

* fix: reset cancelledRef in effect body to prevent permanent stalling of Docker polling

The cancelledRef was set to true in the cleanup function when dependencies
changed, but never reset when the effect re-ran. This caused pollOnce to
always early-return on subsequent timer ticks, permanently halting
Docker capability probing after the first dependency change.

* fix(system-manager): replace cancelledRef with closure variables for per-effect cancellation

Each effect generation now has its own  and  closure
variables instead of shared  / . This
prevents stale probes from surviving cleanup when the panel hides and
re-shows (Codex P2 review).

* fix: wrap refreshCapabilities in try/catch to keep polling on exception

If refreshCapabilities throws (instead of returning {success: false}),
the await would exit pollOnce without scheduling the next setTimeout,
silently killing Docker auto-detection polling.

* fix: add in-flight probe guard to prevent tab-switch and auto-poll concurrent probes

Add probingRef to track whether a capabilities probe is already in-flight.
- Tab-switch effect for Docker branch checks probingRef before starting a new probe
- Auto-poll pollOnce checks probingRef at entry and sets/clears it around the actual probe
- Tmux branch left unchanged as it has no auto-poll overlap risk

* fix: re-schedule next poll timer when probe is in-flight

When probingRef.current is true (tab-switch probe still running),
pollOnce was returning early without scheduling the next timer,
causing auto-poll to stop permanently afterward.

Now it schedules the next poll within the interval and returns,
so the polling loop keeps running until a slot where no probe is
active.

* fix: convert comments to ASCII-only English

- Line 105: translate Chinese comment to 'probe is in-flight, reschedule for next cycle'
- Line 113: replace em dash (U+2014) with ASCII dash

* feat: session inline rename, closeSession shortcut, pane zoom

* fix: sidebar inline rename with local state

* fix: add sessionDisplayName to terminalPropsAreEqual comparator

The Terminal component is wrapped with React.memo(…, terminalPropsAreEqual),
but the comparator was missing a check for sessionDisplayName. After renaming
a session, the pane title bar would show the old name until some other prop
changed and triggered a re-render.

Add prev.sessionDisplayName === next.sessionDisplayName to the comparator
so that display name changes cause the Terminal to re-render immediately.

* fix: add onStartSessionRename to TerminalLayerWorkspaceSection ctx destructuring and TerminalPanesHost props

* fix: add toggleWorkspaceViewMode to executeHotkeyActionImpl destructuring

The togglePaneZoom handler calls toggleWorkspaceViewMode() but it
wasn't destructured from getCtx(), causing a ReferenceError at runtime.

* fix: restore truncated ctx object in TerminalView render call

The TerminalView ctx object literal on line 1265 was truncated to
'showSele...' due to an editing tool truncation bug, causing
Parsing error: ',' expected on npm run lint / tsc --noEmit.

Restored the missing fields from the base commit:
showSelectionAIAction, snippets, status, statusDotTone, sudoHintRef,
sudoHintText: t("terminal.sudoHint.pressEnter"), t, termRef,
terminalBackend, terminalContextActions, terminalCwdTracker,
terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem

Kept the PR's new additions (isVisible, onRename, sessionDisplayName)
intact.

* fix: add toggleWorkspaceViewMode to executeHotkeyAction context and add terminal.menu.rename translations

- Add toggleWorkspaceViewMode to the context getter in executeHotkeyAction (App.tsx)
- Add terminal.menu.rename translation for en (Rename), zh-CN (重命名), ru (Переименовать)

* fix: validate focusedSessionId before closing in closeSession hotkey

When closeSession hotkey fires, workspace.focusedSessionId may reference
a session that was already closed by another trigger (e.g., mouse click
on tab close button). Collect alive session IDs from the workspace root
and fall back to the first living pane if the stored focusedSessionId
is stale.

* fix(auto-poll): check useSessionCapabilities probing state in pollOnce

When auto-poll timer fires before the initial probe (from
useSessionCapabilities) completes, probingRef.current is still false
because the initial probe doesn't set it — causing a second overlapping
probe.

Add  check so that any in-flight probe from any path
(initial/auto-poll/tab-switch) prevents auto-poll overlap.

PR #1459

* fix: address remaining Codex review issues

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add detach session from workspace with toolbar button and context menu

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: use customName in pane header display name for renamed sessions

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: refine workspace terminal detach interactions

* fix: preserve workspace detach tab ordering

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:30:44 +08:00
陈大猫
5e00e998a8 Add vault drag reorder ordering (#1407) 2026-06-11 16:05:17 +08:00
bincxz
35bf38be70 Improve host tree rename and hover details 2026-06-10 11:04:36 +08:00
bincxz
7d6f30f51f fix(ui): restore Windows title bar window control hover (#1352)
Align window controls with utility icons, extend hover to the full title bar height, restore red close-button hover, flush close to the right edge, and use neutral gray hover for top-bar utility buttons.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 01:29:49 +08:00
bincxz
037b85bd66 fix(ui): smooth work-tab chrome transitions and split-pane autocomplete
Replace immersive instant-switch with animated active chrome theme sync so
top tabs match terminal sessions immediately on tab click, and clamp
autocomplete popups to the active pane so they stay anchored to the cursor
in split workspaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 01:10:33 +08:00
陈大猫
4b5993cad6 fix host sidebar behavior for editor tabs (#1348) 2026-06-09 21:24:16 +08:00
Pyro
d85f4edbbb fix: host tree tab jump on first terminal open (#1331) 2026-06-09 17:27:10 +08:00
陈大猫
36e5779d94 perf(terminal): reduce terminal tab-switch and layout jank (#1321)
* perf(terminal): smooth layout drags and faster tab switching

Defer xterm refit during split, sidebar, and host-tree drags while keeping pane containers in sync with live layout measurements. Refactor TerminalLayer into focused sections with TabBridge/memo optimizations and add the terminal host tree sidebar.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(terminal): keep side panels alive and guard session attach races

Prevent terminal boot unmount from leaking backend sessions, keep SFTP/scripts/theme/AI state when switching side tabs, and defer heavy SFTP UI mount so first entry stays responsive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(terminal): reduce tab switch jank

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 03:35:03 +08:00
陈大猫
b20163d762 fix: add custom CSS hooks for terminal side panel and split view (#1302)
Expose data-section selectors for SFTP/side panel, split panes, and resizers
so custom CSS can target the correct regions. Clarify docs that
terminal-workspace-sidebar is focus-mode only.

Fixes #1301

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:11:51 +08:00
陈大猫
b89f06b7f0 fix(terminal): prevent horizontal content drift (#1240) 2026-06-04 19:28:58 +08:00
Pyro
42b23a9faa fix: blurry inline aside panel rendering (#1185) 2026-06-02 00:40:17 +08:00
陈大猫
92dd898eb4 Fix #931: let users pick a CJK font + per-font smart pairing (#940)
* feat(fonts): add CJK font pairing composition module

Introduces composeFontFamilyStack() which builds the xterm fontFamily
CSS string at runtime from:
  - the user's primary Latin font
  - an explicit CJK font (TerminalSettings.fallbackFont) if set
  - otherwise a per-Latin-font recommended CJK pairing
  - a hardcoded system CJK fallback stack
  - a Nerd Font icon fallback stack
  - the universal monospace generic

14 unit tests cover composition order, deduplication, OS defaults,
quoting, and recommendation override behavior.

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

* refactor(fonts): expose raw Latin families and add CJK-coverage entries

- TERMINAL_FONTS[].family no longer bakes in the CJK fallback stack;
  composition is deferred to runtime via composeFontFamilyStack().
- Drops withCjkFallback helper from this module and its caller in
  lib/localFonts.ts.
- Adds 6 CJK-coverage primary fonts to the dropdown: Sarasa Mono SC/TC,
  Maple Mono CN, LXGW WenKai Mono, Microsoft YaHei UI, PingFang SC.

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

* feat(terminal): compose font-family stack with user-configurable CJK fallback

resolvedFontFamily now passes through composeFontFamilyStack(), which
prepends the user's TerminalSettings.fallbackFont (if set) ahead of the
per-Latin-font recommended CJK pairing and the system fallback stack.

The platform argument is derived from navigator.platform inside the
useMemo, so the same Latin font may pair with PingFang SC on macOS and
Microsoft YaHei UI on Windows out of the box.

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

* feat(settings): add CJK font picker to terminal settings

Adds a new "CJK font" select row right under the main font selector in
the Terminal settings tab. Bound to TerminalSettings.fallbackFont (an
already-existing-but-unused field), so this needs no schema or sync
payload change.

Default value "Auto" leaves fallbackFont empty, which lets the new
per-Latin-font pairing in cjkFonts.ts pick a CJK font automatically.
Selecting any explicit option (Sarasa Mono SC, PingFang SC, Microsoft
YaHei UI, etc.) takes precedence over the per-font pairing.

Includes en + zh-CN i18n strings.

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

* test(sync): cover fallbackFont round-trip + legacy payload tolerance

Four new test cases verify cloud-sync compatibility for the new CJK
font setting:

  - buildSyncPayload includes fallbackFont when set
  - buildSyncPayload omits fallbackFont when unset
  - applySyncPayload writes incoming fallbackFont to TERM_SETTINGS
  - applySyncPayload from a legacy client (no fallbackFont) does NOT
    wipe the local value — critical for old-to-new upgrades

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

* feat(fonts): add font availability detection (canvas + document.fonts API)

Three-layer detection used by isFontInstalled(family):
  1. Known @fontsource-bundled families (e.g. JetBrains Mono) always
     count as installed.
  2. document.fonts.check() — picks up @font-face and system-loaded fonts.
  3. Canvas width measurement against serif / sans-serif / monospace
     fallbacks; only counts if the target font produces a width that
     differs from ALL three generics for a probe string.

detectInstalledWithContext is a pure function taking an injected
measurement context, which keeps the canvas / DOM behind a seam and
lets the logic be unit-tested without a browser. 11 tests cover
quoted-family parsing, the three-generic-fallback rule, bundled
short-circuit, and document.fonts.check fast-path.

Results are cached per process; clearFontAvailabilityCache() invalidates.

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

* feat(fonts): filter dropdowns to fonts actually installed on this machine

Layer 3 of #931 added Sarasa Mono SC / Maple Mono CN / Microsoft YaHei UI
/ PingFang SC etc. to the terminal font dropdown, but users who don't
have these installed would still see them and pick them — resulting in
"I changed the font and nothing happened" confusion.

This commit filters both dropdowns through isFontInstalled():

  - TerminalFontSelect: drops any built-in or system-discovered font
    that detection can't render. If filtering would leave fewer than 4
    fonts (detection misfire safety net), shows the full list.

  - TerminalCjkFontSelect: keeps the "Auto" sentinel always, drops
    concrete CJK choices that aren't present on this machine.

Both selects always keep the currently-selected value visible — even
when the underlying font is missing — so users can read and clear
their setting without surprise.

Also expands `npm test` globs to pick up infrastructure/config/*.test.ts
and lib/*.test.ts, which previously matched no patterns and meant the
new cjkFonts and fontAvailability suites were silently excluded from
CI runs.

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

* fix(fonts): never recommend proportional CJK fonts for terminal use

The previous PingFang SC / Microsoft YaHei UI / Hiragino Sans GB choices
were proportional sans-serif fonts whose CJK glyphs aren't designed to
fit a terminal's 2x cell grid — the rendered Chinese ended up visibly
wider than its allocated cells, breaking grid alignment (reported on
macOS with PingFang SC selected as the CJK font).

Changes:
  - TerminalCjkFontSelect: drops PingFang SC / Microsoft YaHei UI /
    Hiragino Sans GB from the dropdown. Legacy explicit selections
    still surface as a synthetic "not recommended" option so users can
    see and re-pick.
  - CJK_SYSTEM_FALLBACK_FONTS: monospace-only list. Sarasa Mono SC/TC,
    Maple Mono CN, LXGW WenKai Mono, Noto Sans Mono CJK SC, Source Han
    Mono SC, NSimSun, SimSun. Proportional fonts removed.
  - PER_FONT_CJK_PAIRING: every entry now points at a true monospace
    CJK font. Cascadia / Consolas / Menlo etc. all recommend Sarasa
    Mono SC, which the next commit bundles via @font-face.
  - getDefaultCjkFallback: Windows = SimSun (always installed,
    monospace); macOS = Sarasa Mono SC (will be bundled); Linux =
    Noto Sans Mono CJK SC. A regression test enforces that no
    per-OS default is a known proportional font.

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

* feat(fonts): bundle Sarasa Mono SC as the universal CJK monospace

Previous commit removed proportional CJK fonts (PingFang SC, etc.)
from the picker and switched per-OS defaults to true monospace, but
macOS ships NO system-installed monospace CJK font — leaving macOS
users with a broken default unless they manually install Sarasa or
similar. This commit closes that gap by bundling Sarasa Mono SC as
an @font-face webfont, so the recommended pairings and macOS default
"just work" out of the box.

Details:
  - public/fonts/SarasaMonoSC-Regular.woff2 (~4.8 MB): subsetted from
    be5invis/Sarasa-Gothic v1.0.37 SarasaMonoSC-Regular.ttf (24 MB).
    Covers ASCII, Latin-1, common punctuation/symbols, CJK Unified
    Ideographs main block, Hiragana/Katakana, halfwidth/fullwidth,
    box-drawing — the everyday-Chinese coverage that matters for a
    terminal. Rare CJK Ext-A/B/historical chars fall through to the
    system fallback stack.
  - public/fonts/SarasaMono-LICENSE.txt: OFL-1.1 verbatim, required
    by the license.
  - index.css: @font-face declaration with font-display: swap so the
    user doesn't see a flash of nothing while the woff2 loads.
  - KNOWN_BUNDLED_FAMILIES: "Sarasa Mono SC" added so the dropdown
    availability filter doesn't hide it.

Installer impact: ~+4.8 MB (vs current ~100-200 MB Electron baseline).
The font replaces what would otherwise have been "Chinese chars look
broken in the terminal" for every macOS user without a manually
installed CJK monospace font.

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

* fix(fonts): use Local Font Access API as the authoritative install check

document.fonts.check() turned out to be unreliable as an installed-font
signal in Chromium — it returns true for any syntactically-valid family
name regardless of whether the font is actually installed, as a
deliberate fingerprinting-mitigation. The previous detector took it as
a positive signal and ended up keeping uninstalled fonts in the dropdown
(reported by a macOS user seeing dozens of fonts they don't have).

This commit pivots the detection chain:

  - lib/localFonts.ts: getAllSystemFontFamilies() exposes the unfiltered
    set of installed family names from queryLocalFonts(), reusing the
    same underlying call as getMonospaceFonts() via a shared cache.

  - lib/fontAvailability.ts: drops the document.fonts.check fast-path.
    Adds setSystemFamilies() / hasAuthoritativeData(). When the set has
    been populated, isFontInstalled answers from membership lookup
    directly — no canvas guessing. Canvas remains as a fallback for
    environments where the Local Font Access API is unavailable or
    permission is denied.

  - application/state/fontStore.ts: during initialize(), runs the
    monospace-only query and the full-system-families query together,
    then pipes the result into fontAvailability.

  - TerminalFontSelect: with authoritative data, drops the "if filtered
    list is suspiciously small, show all" safety net. Empty would now
    really mean empty (highly unlikely since Sarasa Mono SC is bundled).

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

* fix(fonts): drop PingFang SC / Microsoft YaHei UI from primary dropdown

Step 1 of this PR removed proportional CJK fonts from the CJK fallback
picker but left them in BASE_TERMINAL_FONTS, so PingFang SC and
Microsoft YaHei UI were still selectable as the *primary* terminal
font. Picking PingFang SC as primary produced visibly bloated Latin
character spacing (xterm.js samples cell width from the primary font;
the wide proportional 'M' inflates every cell), reported by a macOS
user in the same thread that opened #931.

Both entries are removed from BASE_TERMINAL_FONTS. A new
infrastructure/config/fonts.test.ts asserts that no known proportional
CJK font name (including PingFang TC/HK, Microsoft YaHei variants,
Hiragino Sans GB, Heiti SC/TC) is ever shipped in TERMINAL_FONTS as a
primary choice.

Migration for users already saved to one of the removed ids:
useSettingsState rewrites STORAGE_KEY_TERM_FONT_FAMILY to the default
(Menlo) on read when it sees a deprecated id, so the bad value also
stops getting carried into cloud-sync uploads. Per-host fontFamily
overrides are NOT migrated automatically — they still gracefully
fall through to the dropdown's first entry via the existing
getFontById fallback; users can re-pick from the host settings UI.

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

* fix(fonts): drop Comic Sans MS — it's a proportional handwriting font

Same symptom as the PingFang SC / Microsoft YaHei UI removal: Comic
Sans MS was historically in the primary font dropdown labeled
"Casual, non-traditional terminal font", but Comic Sans is a
handwriting-style proportional sans-serif. Picking it as the terminal
primary inflates cell width and spaces every Latin character far
apart (reported in the same #931 thread).

- BASE_TERMINAL_FONTS: comic-sans-ms entry removed.
- DEPRECATED_PRIMARY_FONT_IDS: gains comic-sans-ms so existing
  selections silently migrate to Menlo on read.
- fonts.test.ts: the proportional-font ban list now also covers
  Latin proportional fonts (Comic Sans MS, Arial, Helvetica, Times
  New Roman, Georgia, Verdana, Trebuchet MS, Tahoma) so the test
  catches any future mislabeled body-text font from being added to
  the terminal dropdown.

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

* fix(fonts): keep monospace ahead of CJK fallbacks in composed stack

Addresses codex P1 review comment on PR #940
(https://github.com/binaricat/Netcatty/pull/940#discussion_r3216017737).

The previous behavior of withCjkFallback() had monospace immediately
after the primary family, before any CJK fallback. composeFontFamilyStack
had moved monospace to the very end, which means: when the primary
font isn't installed on the user's machine (common for Layer 3 CJK
choices that aren't bundled and not present on a given OS, or for any
built-in id like cascadia-code on a Linux system without it), CSS
per-glyph fallback resolves Latin glyphs from a CJK font's full-width
Latin variants before ever reaching monospace generic. That breaks
xterm.js's fixed cell-grid alignment.

The composed stack now reads:
  <primary>, monospace, <userFallback>, <recommended-cjk>,
  <system-cjk-stack>, <nerd-font-stack>

Per-glyph CSS fallback behavior:
  - Latin → primary if installed → monospace generic. Cell width
    stays consistent.
  - CJK → primary (no) → monospace (no Chinese glyphs) → walks into
    CJK fallbacks.
  - Nerd PUA → falls past all of the above into the Nerd Font stack.

Updates the position-invariant tests and adds a regression test that
explicitly asserts monospace appears before every CJK family in the
output stack.

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

* fix(fonts): dedupe Local Font Access API calls under concurrent init

Addresses codex P2 review on PR #940:
  https://github.com/binaricat/Netcatty/pull/940#discussion_r3216246xxx

fontStore.initialize() runs getMonospaceFonts() and
getAllSystemFontFamilies() in Promise.all; both internally called
queryAllSystemFontsOnce(), whose cache check (`if (cache) return`) was
only useful once the result had been written. Concurrent callers both
passed the empty-cache check and fired their own queryLocalFonts()
request — two real Local Font Access API invocations on cold start,
with the risk of one succeeding while the other was denied (leaving
the authoritative set unset).

Fix: cache the *in-flight promise itself*, so subsequent callers
await the same single invocation. The first await populates the
family-set cache as a side effect, and the resolved promise keeps
returning the same value to every subsequent caller.

Adds lib/localFonts.test.ts with three regression tests:
  - concurrent getMonospaceFonts + getAllSystemFontFamilies = 1 API call
  - sequential repeats also reuse the resolved promise
  - missing API returns null authoritative set (canvas fallback signal)

Exports __resetLocalFontsCacheForTesting() so each test gets a fresh
module-level state.

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

* fix(fonts): retry LFA on transient failure + notify on availability changes

Two follow-up fixes from codex P2 review on PR #940:

1) queryAllSystemFontsOnce() previously kept its in-flight promise even
   when queryLocalFonts threw. Subsequent callers reused the cached
   empty result for the rest of the session, so any transient failure
   at boot (permission state not ready, AbortError, etc.) permanently
   blinded the rest of the app to installed fonts. Catch now clears
   queryPromise so the next caller retries. Regression test added.

2) TerminalCjkFontSelect.visibleOptions and TerminalFontSelect
   .visibleFonts were memoized on [value] / [fonts, value] only, but
   the filter calls isFontInstalled() which reads module-level
   systemFamilies — a value that arrives asynchronously after the
   initial render. The memos never recomputed when authoritative
   availability data landed, so the dropdowns could continue showing
   stale "filtered" results until the user changed selection.

   fontAvailability now exposes subscribeFontAvailability() and
   getFontAvailabilityVersion() (monotonic counter bumped on
   setSystemFamilies / clearFontAvailabilityCache). Both selects
   subscribe via useSyncExternalStore and include the version in
   their memo deps; tests cover subscriber notification and version
   monotonicity.

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

* fix(fonts): migrate host/group deprecated font ids + localize CJK labels

Two follow-up fixes from codex review on PR #940:

P2 — Host/group level font migration
====================================
The earlier deprecated-id migration only rewrote
STORAGE_KEY_TERM_FONT_FAMILY, so hosts and group configs that had
explicitly opted into a now-removed font id (e.g. pingfang-sc,
microsoft-yahei, comic-sans-ms) kept `fontFamily` set with
`fontFamilyOverride=true`. After the dropdown entries were dropped
in 9f2bd282/c9b622d8, those records silently fell through to the
first font in the registry (Menlo) while the override flag still
read "true" — users saw a host claiming a custom font but rendering
the global default with no way to tell what happened.

Fix:
  - infrastructure/config/fonts.ts gains migrateDeprecatedFontOverride(),
    a structurally-shared helper that drops fontFamily and clears
    fontFamilyOverride when the id is deprecated.
  - sanitizeHost now runs it on every host load.
  - domain/groupConfig.ts grows sanitizeGroupConfig(); useVaultState
    applies it both on initial load and on cross-tab storage events.
  - Existing decrypt → sanitize → encrypt round-trip in useVaultState
    means the migrated values are persisted back to localStorage and
    propagate through cloud sync naturally.

Tests: two each in domain/host.test.ts and domain/groupConfig.test.ts
covering deprecated-id reset and untouched-valid-id preservation.

P3 — Localize CJK font option labels
====================================
TerminalCjkFontSelect previously hardcoded Chinese option labels
("Auto · 按主字体智能搭配", "Sarasa Mono SC (更纱黑体 简)", etc.) and
the synthetic "not recommended" warning. Non-Chinese locales saw a
mixed-language UI despite the rest of the setting going through i18n.

OPTIONS now references i18n keys; the component looks them up via
useI18n(). Both en and zh-CN locales gain matching keys, including
`...option.legacy` with `{font}` interpolation for the synthetic
"not recommended" item that surfaces saved-but-removed values.

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

* fix(fonts): also sanitize group configs on the write/import path

Addresses codex P2 review on PR #940:
  https://github.com/binaricat/Netcatty/pull/940#discussion_r3216314xxx

The previous commit (09c87820) added sanitizeGroupConfig() but only
plumbed it into the decrypt paths (initial load + storage event).
updateGroupConfigs() — which is also the write path used by
applySyncPayload / importVaultData when ingesting a legacy payload —
still set state from raw input. A sync from an older client carrying
{ fontFamily: "pingfang-sc", fontFamilyOverride: true } would land in
memory unsanitized AND be re-persisted with the bad override active
until the next reload re-ran the decrypt path.

Fix mirrors updateHosts → sanitizeHost: map every incoming entry
through sanitizeGroupConfig before both setGroupConfigs and the
encrypt-and-persist step. Same call site now feeds the cleaned data
to localStorage, so legacy values are scrubbed on first import.

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

* fix(fonts): migrate deprecated terminal font ids on every ingest path

Addresses codex P2 review on PR #940:
  https://github.com/binaricat/Netcatty/pull/940#discussion_r3216517xxx

The previous migration only ran in the initial useState() initializer
for terminalFontFamilyId, so deprecated ids (pingfang-sc /
microsoft-yahei / comic-sans-ms) could still re-enter state via:

  - rehydrateAllFromStorage() at line ~527 — runs on remote-import
    completion and re-reads STORAGE_KEY_TERM_FONT_FAMILY raw.
  - The notifySettingsChanged IPC handler at line ~663 — fires when a
    cloud sync or programmatic localStorage write announces a change.
  - The cross-window storage event handler at line ~873.

Any of these paths could pull a deprecated id back into state after
the initial migration ran, leaving the font selector with no matching
option and silently rendering the global default while continuing to
propagate the stale value through subsequent sync uploads.

Centralizes the migration in migrateIncomingTerminalFontId(raw):
  - returns null when raw is empty
  - if raw is deprecated, writes DEFAULT_FONT_FAMILY back to
    localStorage AND returns it
  - otherwise returns raw unchanged

All four ingest sites (initial init, rehydrate, IPC, storage event)
now route through this helper. The rewrite-on-deprecated semantics
also guarantee that the moment any path sees a bad value, the next
sync upload carries the cleaned default — not the deprecated id.

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

* fix(fonts): use bundled Latin-only fallback instead of monospace generic

Resolves the tension between codex's two P1 reviews on PR #940:

  Round 1 (da1fe4cd): "monospace must come BEFORE CJK fallbacks" —
    otherwise Latin glyphs fall into a CJK font's full-width Latin
    when the primary font is missing.

  Round 2 (this commit): "monospace must come AFTER CJK fallbacks" —
    otherwise on macOS Chrome, the generic `monospace` pulls in
    PingFang via Chromium's CJK system fallback and silently masks
    the user's CJK picker.

Both are right; using a single `monospace` token can't satisfy both
roles because `monospace` is a generic family whose CJK-glyph
coverage is platform-dependent.

Fix mirrors Tabby's approach (their "monospace-fallback" SourceCodePro
sitting before any CJK in the chain): insert a known Latin-only
bundled font between the primary and CJK fallbacks. JetBrains Mono is
already shipped via @fontsource/jetbrains-mono and carries no CJK
glyphs, so it catches Latin without intercepting Chinese.

New stack order:
  <primary>, "JetBrains Mono", <userFallback>, <recommended-cjk>,
  <system-cjk-stack>, <nerd-font-stack>, monospace

Per-glyph CSS fallback now behaves as intended on every platform:
  - Latin: primary (if installed) → JetBrains Mono. Cells stay aligned.
  - CJK: primary (no) → JetBrains Mono (no CJK glyphs) → user CJK pick.
  - Nerd PUA: all of the above → Nerd Font stack.

Replaces the two prior positional-invariant tests with one for each
codex review concern: JetBrains Mono precedes every CJK family
(Latin alignment), and user CJK precedes generic monospace (CJK
picker effectiveness).

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

* fix(fonts): use OR-of-fallbacks for canvas font detection

Addresses codex P2 review on PR #940:
  https://github.com/binaricat/Netcatty/pull/940#discussion_r3216556xxx

detectInstalledWithContext required the target font to produce a
different rendered width from *all three* generic fallbacks (serif,
sans-serif, monospace) to be counted as installed. That's too strict:
on macOS the `monospace` generic resolves to Menlo itself, so
measure(`"Menlo", monospace`) === measure(`monospace`), and the
detector reported Menlo as missing even when it was clearly installed.
The same false-negative trap exists for any font that happens to
share metrics with one of the three generics on a given platform.

Switches to OR-of-fallbacks: a font counts as installed if its
rendered width differs from at least one generic baseline. A truly
uninstalled font still falls through to each generic in turn and
matches all three baselines, so this doesn't introduce false positives.

Regression tests added for both directions:
  - Menlo with metrics identical to `monospace` generic → installed.
  - "Definitely Not Installed" font → still reported missing.

The path only fires when the Local Font Access API is unavailable or
denied — when LFA succeeds, `setSystemFamilies` short-circuits ahead of
canvas — so this primarily improves the degraded-permission scenario.

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

* fix(fonts): quote-aware tokenizer for font-family lists

Addresses codex P2 review on PR #940:
  https://github.com/binaricat/Netcatty/pull/940#discussion_r3216559xxx

composeFontFamilyStack and extractPrimaryFamily both tokenized their
input with a raw String.split(',') — which corrupts any CSS family
list whose quoted family name contains a comma (CSS allows that, e.g.
`"Foo, Inc. Mono"` is a single family). A naive split would shred
that into `"Foo` / `Inc. Mono"` and emit a malformed font-family back
out.

No current TERMINAL_FONTS entry hits this case, but lib/localFonts.ts
builds family strings from arbitrary system fonts via the Local Font
Access API — a user with a comma-bearing family name would have
silently broken filtering until now.

Adds splitFontFamilyList(css) in cjkFonts.ts: an exported quote-aware
tokenizer that splits on commas only when outside quoted segments
(handles both " and '). composeFontFamilyStack uses it instead of raw
split; extractPrimaryFamily in lib/fontAvailability.ts imports it for
symmetry so the two call sites can't drift.

Tests cover the tokenizer directly (simple list, quoted-with-comma,
single quotes, double commas) and end-to-end (a quoted primary with
an internal comma survives composition intact).

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

* fix(fonts): translate Layer 3 CJK font descriptions to English

The 4 CJK-coverage entries added in earlier commits (Sarasa Mono SC,
Sarasa Mono TC, Maple Mono CN, LXGW WenKai Mono) had hardcoded Chinese
description strings, while every other TERMINAL_FONTS entry uses
English ('Adobe's professional programming font', 'Iosevka variant
mimicking Berkeley Mono style', etc.). The dropdown rendered a
mixed-language list — flagged by the maintainer.

Converted the 4 descriptions to English in the same style as the
existing entries. No i18n scaffolding added; the existing convention
is "English-only `description` field, not routed through t()", and
the rest of the registry stays consistent with that.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:07:15 +08:00
bincxz
de7057183c Increase AI code block top spacing 2026-05-01 13:48:42 +08:00
bincxz
dd910cc53d Tighten AI code block spacing 2026-05-01 13:43:06 +08:00
陈大猫
6a39ed05a9 [codex] Tighten AI chat spacing (#883)
* Tighten AI chat spacing

* Scope AI table spacing styles
2026-05-01 11:33:07 +08:00
陈大猫
c941038e68 [codex] Bundle Symbols Nerd Font Mono for terminal icon fallback (#846)
* Bundle Symbols Nerd Font Mono as terminal icon fallback

PR #845 added "Symbols Nerd Font Mono" to the terminal fontFamily
fallback chain so PUA glyphs (powerline / devicons / etc.) resolve
even when the user's primary font lacks them. That only worked if the
user had separately installed the symbol font; ship it ourselves so
icons render out of the box regardless of the chosen base font.

- Drop SymbolsNerdFontMono-Regular.ttf into public/fonts (~2.5 MB);
  Vite copies it to dist/fonts and the existing app:// protocol
  handler already knows the font/ttf MIME type.
- Register an @font-face in index.css pointing at the bundled file.
  font-display: block prevents tofu while the (instantly-available
  bundled) face loads, only affecting PUA glyphs since the base font
  is listed earlier in the fallback chain.
- Include the upstream LICENSE next to the font.

Source: ryanoasis/nerd-fonts NerdFontsSymbolsOnly v3.4.0 (MIT).

Refs #843

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

* Reference bundled font by absolute path so prod build resolves

Address Codex P2 on PR #846: the relative `./fonts/...` URL was emitted
verbatim into dist/assets/index-*.css, where the browser resolved it
against the CSS file's location and 404'd on
dist/assets/fonts/SymbolsNerdFontMono-Regular.ttf — the actual file
lives in dist/fonts/, so the icon fallback never loaded in packaged
builds and Nerd Font glyphs still rendered as tofu.

Switch the @font-face url() to `/fonts/...`. Vite's `base: "./"`
config rewrites that to the correct dist-relative form during build
(`../fonts/SymbolsNerdFontMono-Regular.ttf` from dist/assets/), and in
dev the same path is served by the Vite dev server out of public/.
Verified by re-running `vite build` and grepping the produced CSS.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:39:01 +08:00
秋秋
df25d6c4b0 fix: resolve WebGL blank frame on resize and keep split pane bright on context menu (#837) 2026-04-26 05:45:22 +08:00
libalpm64
dd1d97ffff Fix Midnight brightness, optimize backdrop-blur, and remove unused radials. (#817)
- 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)
2026-04-23 10:01:02 +08:00
陈大猫
70b05bfaaf New app logo + sidebar ripple + manager UI polish (#786)
* 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>
2026-04-21 22:16:49 +08:00
Eric Chan
7e566efe9c Add push-style host details panels (#649)
Refs: https://github.com/binaricat/Netcatty/issues/640
2026-04-08 16:42:32 +08:00
bincxz
b770dbe6f5 fix: widen scrollbar hit area (12px track, 6px slider) for smoother dragging 2026-04-02 12:42:03 +08:00
bincxz
2ae1219bb7 fix: make scrollbar thinner (5px) 2026-04-02 11:05:04 +08:00
bincxz
591b2ba010 fix: slim down xterm 6.0 scrollbar width to 8px with rounded corners
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:04:02 +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
bincxz
10ff2cc092 ui: increase unfocused workspace terminal opacity from 0.65 to 0.82
Some checks failed
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:44:59 +08:00
陈大猫
dd50f95583 feat: add workspace focus indicator style setting (dim vs border) (#557)
* feat: add workspace focus indicator style setting (dim vs border)

Users can now choose between two focus indicator styles for split
terminal panes:
- Dim: reduces opacity of unfocused panes (current default)
- Border: shows a colored border on the focused pane (old style)

The setting is in Settings > Terminal > Workspace Focus Indicator.
Implementation uses a CSS data attribute on documentElement to
toggle between the two styles, avoiding prop threading.

Closes #556

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

* fix: sync workspace focus style across windows

Add cross-window notification handling for the workspace focus style
setting so changes in the Settings window take effect in the main
terminal window immediately.

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 21:31:15 +08:00
Rory Chou
7639191c50 fix: preserve AI chat history across reconnects (#541)
* fix: preserve AI chat history across reconnects

* fix: retarget restored AI sessions on reconnect

* feat: format tool call results with proper line breaks

Extract stdout/stderr from structured results and unescape \n/\t
so command output displays with real line breaks like terminal output.
Supports both JSON object {stdout,stderr} and executor text formats.

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

* fix: restrict unescape to stdout/stderr fields only

Plain strings may contain legitimate backslash sequences (file paths,
regex patterns) that should not be converted. Only apply unescape to
stdout/stderr fields extracted from command execution results.

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

* fix: address review findings for AI chat reconnect

1. Add explicit activeTerminalTargetIds guard in shouldRetargetActiveSession
   to prevent retargeting sessions owned by other terminals, making the
   invariant locally verifiable.

2. Only preserve orphaned terminal sessions with hostIds — workspace,
   local, and serial sessions generate fresh IDs and would be permanently
   unreachable, wasting MAX_STORED_SESSIONS quota.

3. Clear stale streaming state when restoring a session whose ACP handle
   was already cleaned up (e.g., reconnect during mid-response), so the
   user can send new messages.

4. Restore overflow-hidden on user message bubbles to prevent content
   bleeding past rounded border corners.

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

* fix: address round 2 review findings

1. Fix streaming state clear: only clear for sessions whose targetId
   doesn't match current scope (restored from different terminal),
   not for built-in Catty chats that never set externalSessionId.

2. Exclude local/serial sessions from preservation: their synthetic
   hostIds (local-*/serial-*) change on every open and can never be
   matched back.

3. Preserve non-zero exitCode in formatted tool results so failed
   commands show a visible failure signal.

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

* fix: only clear streaming state during retarget, not for all restored sessions

The previous condition (targetId !== scopeTargetId) also fired for
built-in Catty sessions during normal operation, killing active streams.
Now streaming is only cleared when shouldRetargetActiveSession is true,
meaning the session came from a disconnected terminal where any
in-flight response is guaranteed to be dead.

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

* fix: address round 3 review findings

1. Clear externalSessionId during retarget to prevent stale ACP handle
   from surviving if retarget runs before orphan cleanup.

2. Only retarget in visible AI panels — hidden/background panels should
   not race to claim orphaned sessions.

3. Remove unescapeTerminalOutput — data flow trace confirms real newline
   characters arrive at the component. The unescape was corrupting
   legitimate backslash sequences in paths and patterns.

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

* fix: only ACP-cleanup deleted sessions, not preserved ones

Preserved sessions may be reused on reconnect. Running aiAcpCleanup
on them asynchronously could race with a newly started ACP conversation
on the same session ID, tearing down the fresh provider.

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

* fix: abort in-flight streams during retarget and restore ACP cleanup

1. Abort the active request's AbortController when retargeting a session
   with stale streaming state. Prevents late chunks from the old run
   appending into the restored chat.

2. Restore ACP cleanup for all orphaned sessions (not just deleted ones).
   Preserved sessions get a new externalSessionId on next use, so
   cleaning the old one prevents subprocess leaks without affecting
   future conversations.

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

* fix: guard hidden panels from session ownership and skip null map entries

1. Only assign restored sessions in visible panels — hidden panels
   should not race to own sessions via setActiveSessionId, preventing
   MCP/tool calls from being bound to the wrong terminal.

2. Skip null entries in activeSessionIdMap when building
   activeTerminalTargetIds — deleted chats should not block same-host
   history matching on other terminals.

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

* fix: guard MCP sync behind visibility and cancel exec/approvals on retarget

1. Only sync MCP session metadata from visible panels to prevent
   hidden panels from overwriting the scope mapping.

2. Cancel pending approvals and in-flight exec (Catty + ACP) during
   retarget, matching handleStop behavior. Prevents stale tool results
   and approval prompts from reappearing after session retarget.

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

* fix: restore MCP sync for hidden panels

MCP scope is keyed by chatSessionId so hidden panels don't overwrite
visible panels' mappings. The isVisible guard was breaking background
chats that need updated terminal session metadata after reconnects
or workspace changes.

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

* chore: remove unused deletedIds variable

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

---------

Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:47:32 +08:00
bincxz
eeb42b1d20 fix: make vault and sftp theme switching instant 2026-03-27 02:51:23 +08:00
陈大猫
5b6f45c896 perf: reduce workspace and theme switch rerenders (#537)
* fix: replace workspace pane border with text dimming for unfocused panes

Replace the 2px primary-color border and Tailwind ring with a subtler
focus indicator: unfocused panes reduce xterm canvas opacity to 70%,
making text slightly dimmer without adding visual clutter.

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

* fix: use visibility:hidden for terminal caching and restore focus on tab switch

- Replace display:none with visibility:hidden for TerminalLayer and
  workspace panes to preserve xterm canvas state across tab switches
- Restore focus to the correct pane when terminal layer becomes visible
  again, preventing opacity flash from :focus-within CSS
- Reduce autocomplete popup box-shadow intensity

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-27 01:03:12 +08:00
Eric Chan
9136569809 feat: Add session activity indicator and store (#528)
* Add session activity indicator and store

Introduce a SessionActivityStore (useSyncExternalStore) to track which tabs/workspaces have unread terminal activity. TerminalLayer now strips terminal control sequences, listens for session data, and marks tabs as active when not focused; it also clears activity on focus change and prunes stale IDs. TopTabs consumes the activity map to render a breathing activity dot on session/workspace tabs and adjusts the workspace tab layout to show the dot next to the pane count. Add CSS animation for the activity indicator.

* fix: buffer incomplete escape sequences across data chunks

Add ChunkedEscapeFilter to carry partial ANSI/OSC escape-sequence
tails between successive data chunks, preventing false-positive
activity badges from split control sequences on busy sessions.

Also fix missing trailing newline in sessionActivity.ts.

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

* fix: remove 256-byte cap on pending escape sequence tails

Long OSC sequences (e.g. clipboard/title payloads) can exceed 256
bytes. Removing the cap ensures they are fully buffered across
chunks instead of being misclassified as printable output.

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

* fix: buffer OSC tails that end on bare ESC awaiting backslash

OSC sequences terminated with ESC\ can split at the ESC boundary.
Extend the incomplete tail regex to also match an in-progress OSC
sequence ending with ESC (awaiting the closing backslash).

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

---------

Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:52:10 +08:00
陈大猫
0b7c52523e feat: 终端沉浸模式 (#517)
* feat: add terminal immersive mode

When enabled, the UI chrome (tab bar, sidebar, status bar) adapts its
colors to match the active terminal's theme, creating a visually
cohesive experience. Colors are derived from the terminal theme's
hex values and converted to HSL for CSS custom property overrides.

- Add useImmersiveMode hook with hex-to-HSL conversion and token derivation
- Add reapplyCurrentTheme to useSettingsState for restoring original theme
- Integrate with App.tsx to resolve active terminal's effective theme
- Add immersive mode toggle in Appearance settings with i18n (en/zh-CN)
- Add CSS transition class for smooth 300ms color changes
- Support cross-window sync via IPC for Settings window toggle
- Handle per-host theme overrides and workspace focused sessions

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

* fix: 沉浸模式多项改进与 bug 修复

- 修复 primaryForeground 硬编码白色导致浅色 cursor 对比度不足
- 修复 SettingsPage 直接导入 infrastructure 层违反架构约束
- 修复 TerminalSession 类型未导入导致 TS 编译错误
- 修复 TopTabs memo 缺少 logViews 导致 logView 变化不触发重渲染
- 重构 useImmersiveMode 为纯 effect hook,状态由 useSettingsState 统一管理
- Workspace 多终端主题不一致时禁用沉浸模式
- 排除 logView tab 误触发沉浸模式
- 沉浸模式下禁用 dark/light 切换按钮
- Agent 图标使用 CSS mask 跟随文字颜色
- Agent 下拉菜单 overflow-hidden 修复 hover 溢出
- 退出沉浸模式使用 overlay 淡出避免闪烁
- immersive-transition class 仅在沉浸实际生效时添加

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

* feat: 沉浸模式默认开启

新用户默认启用沉浸模式,已有设置的用户不受影响。

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

* perf: 沉浸模式主题切换性能优化

- 启动时预计算所有内置主题的 CSS 字符串,切换时 O(1) 查表
- 自定义主题懒计算并缓存,后续切换同样 O(1)
- useLayoutEffect 替代 useEffect,paint 前完成避免闪烁
- 跳过无效的 dark/light class 切换
- apply 和 restore 逻辑拆分为独立 effect
- 去掉主题列表 hover 渐变动画

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

* fix: 修复 Codex review 提出的三个问题

- [P1] base UI theme 变化时不再覆盖沉浸模式的 dark/light class
- [P2] fingerprint 加入 theme.type,检测自定义主题 dark↔light 编辑
- [P2] 沉浸模式设置接入 sync pipeline (collect/apply/rehydrate)

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

* fix: focus 模式 workspace 支持沉浸 + settingsVersion 加入 immersiveMode

- focus 模式 workspace 使用 focusedSessionId 的主题,不再要求所有 session 一致
- settingsVersion 加入 immersiveMode 依赖,确保 auto-sync 能检测到变化

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

* fix: 沉浸模式 sync 一致性修复

- 初始化时将默认值写入 localStorage,确保 collectSyncableSettings 能收集到
- rehydrateAllFromStorage 后通过 IPC 广播给其他窗口

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

* fix: focus 模式关闭 focused session 后 fallback 到剩余 session 的主题

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

* fix: 沉浸模式加入 storage event 跨窗口同步

将 immersiveMode 加入 settingsSnapshotRef 和 handleStorageChange,
确保 web/preview 场景下多窗口间沉浸模式状态同步。

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

* fix: 沉浸模式同步 Electron 原生窗口背景色

切换沉浸主题时同步调用 setTheme/setBackgroundColor,
使 Windows 上的窗口边框颜色与沉浸主题一致。

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 02:47:50 +08:00
bincxz
8949394756 feat: add Claude Agent SDK streaming and Codex OAuth integration
Integrate Claude Agent SDK for direct streaming chat, add Codex login/logout
flow with OAuth support in settings, improve AI chat panel UI and agent
discovery, and update build config for new dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 21:27:41 +08:00
bincxz
7f3214e088 feat: integrate external AI agents via ACP protocol
- Add auto-discovery of CLI agents (Claude Code, Codex, Gemini) from system PATH
- Integrate ACP (Agent Client Protocol) for real-time streaming with codex-acp
- Bundle @zed-industries/codex-acp binary for reliable agent spawning
- Add ThinkingBlock component with shimmer animation and auto-collapse
- Refactor chat UI: no avatars, bordered user bubbles, plain assistant text
- Support {prompt} placeholder in agent args for flexible invocation
- Add persistent ACP sessions with proper cleanup on app exit
- Detect auth errors and show actionable messages to users
- Fallback to raw process spawn for agents without ACP support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 17:21:28 +08:00
bincxz
929d7dbe74 Enhances UI font management and connection logs
Refactors UI font handling to support dynamic font loading and improves selection in settings.

Implements "Load More" pagination for connection logs, enhancing performance and user experience with large datasets. Adds lazy loading for the Connection Logs Manager.

Addresses an issue in `usePortForwardingState` to prevent duplicate backend sync calls in React StrictMode. Improves IPC communication robustness by checking window destruction status. Refines the visual presentation of the snippets empty state.
2026-01-21 12:23:21 +08:00
LAPTOP-O016UC3M\Qi Chen
01d446e98b Switches terminal engine to xterm.js and removes Ghostty
Replaces the Ghostty Web terminal backend with xterm.js, updating references, dependencies, and styles accordingly. Removes Ghostty-specific initialization, CSS, and dependencies for improved compatibility and maintainability. Updates terminal and SFTP mounting to use React lazy loading for better performance. Improves Electron window startup to avoid initial white flash. Cleans up cloud adapter dynamic imports for more efficient loading.

Updates documentation to reflect the terminal engine change.
2025-12-18 16:40:27 +08:00
bincxz
151cd4bfab Adds customizable global keyboard shortcut support
Implements a flexible system for configuring and persisting keyboard shortcuts across the app, enabling users to select hotkey schemes (Mac/PC), edit, disable, or reset shortcuts, and apply them globally and in terminal contexts.

Improves accessibility and workflow by allowing tailored key bindings, storing preferences in local storage, and integrating shortcut management into settings UI.

Refines styling for active UI elements, spinners, and borders for better theme consistency.
2025-12-12 03:24:28 +08:00
bincxz
e0146cf2d6 Removes unused xterm padding styles
Cleans up commented-out padding CSS to reduce clutter
and improve maintainability. No functional changes.
2025-12-12 01:55:09 +08:00
bincxz
be2073d94f Enables real-time terminal theme and font preview
Provides immediate feedback when customizing terminal appearance by applying theme, font, and font size changes in real time within the customization modal. Adds cancellation logic to revert unsaved changes, prevents duplicate IPC handler registration, and makes minor UI refinements for improved user experience.
2025-12-12 01:54:04 +08:00
LAPTOP-O016UC3M\Qi Chen
d96b583978 Improves code style and formatting consistency
Standardizes import statement spacing and adjusts whitespace for better readability in the component file. No logic changes are introduced.

Improves code style and import formatting consistency

Standardizes spacing in import statements and introduces helper constants for drag region styles, enhancing readability and maintainability. No logic changes are made.
2025-12-11 22:08:38 +08:00
LAPTOP-O016UC3M\Qi Chen
6fa883d7ba Improves window drag regions and controls for Electron UI
Refines app drag and no-drag regions for better window movement and interaction, especially in Electron environments. Adds support for double-click-to-maximize on titlebars (Windows/Linux), ensures controls/buttons remain non-draggable, and tunes visual padding for consistency. Enhances usability when UI is crowded with tabs, and improves cross-platform window management behavior.
2025-12-11 22:06:49 +08:00
LAPTOP-O016UC3M\Qi Chen
e00fa2733b Renames app and codebase from Nebula to Netcatty
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.
2025-12-11 18:42:01 +08:00
LAPTOP-O016UC3M\Qi Chen
5a687667b5 Improves code consistency and formatting across components
Refactors code for consistent indentation, spacing, and style.
Enhances readability and maintainability by aligning formatting.
No logic changes; only visual and stylistic improvements applied.
2025-12-11 17:08:03 +08:00
LAPTOP-O016UC3M\Qi Chen
ba32d0d75b Migrates to Tailwind CSS v4 and improves hook deps clarity
Switches to Tailwind CSS v4 with theme configuration via CSS, removing inline CDN config for better maintainability and security.

Adds explicit eslint-disable comments in hooks to clarify dependency intent, aiding future development and preventing unnecessary warnings.

Renames props and variables reserved for future features to underscore-prefixed names, improving clarity and reducing confusion.

Removes unused catch parameters for cleaner error handling.

References to future UI and logic enhancements are annotated for maintainability and roadmap visibility.
2025-12-11 17:07:55 +08:00
bincxz
89b8cdcbec Unifies card styling and improves grid layout consistency
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.
2025-12-11 04:25:10 +08:00
LAPTOP-O016UC3M\Qi Chen
7b444b0b85 Replaces SFTP panel with modal and improves terminal UI
Switches SFTP file manager from a side panel to a modal to prevent terminal width issues and streamline the UI. Updates terminal status indicators for clearer error and connection states. Adds CSS tweaks for better terminal canvas fit and scrollbar appearance, enhancing visual consistency and usability.
2025-12-09 20:31:25 +08:00
bincxz
05adb89879 feat: Add TrafficDiagram component with animated port forwarding visualization
- Implemented TrafficDiagram component to visualize local, remote, and dynamic port forwarding.
- Added SVG icons for cloud, firewall, and server.
- Created animated line components for visual representation of connections.
- Integrated AppLogo and icons into the diagram for better UX.
- Included SVG assets for cloud, firewall, and server in the public folder.
2025-12-09 02:11:15 +08:00
LAPTOP-O016UC3M\Qi Chen
82f091d666 style: format CSS keyframes for improved readability and consistency 2025-12-08 21:03:37 +08:00
LAPTOP-O016UC3M\Qi Chen
47ee220be7 feat: enhance SFTP transfer functionality with progress tracking and cancellation support
- Added helper functions to format transfer bytes and speed for better UI representation.
- Improved TransferItem component to display bytes transferred and remaining time.
- Implemented streaming transfer with real-time progress updates and cancellation capability in the Electron main process.
- Introduced IPC handlers for file operations including delete, rename, and stat for both SFTP and local filesystems.
- Updated preload script to handle transfer progress, completion, and error events.
- Enhanced global type definitions to support new streaming transfer API.
- Modified CSS animations for a smoother progress indication.
2025-12-08 21:03:31 +08:00
bincxz
39d926065b feat: Refactor TerminalLayer to remove focused session state and enhance workspace pane styling 2025-12-08 00:56:48 +08:00
bincxz
5d7257ffca feat: add sftp worspace support 2025-12-07 19:34:33 +08:00