Commit Graph

18 Commits

Author SHA1 Message Date
陈大猫 b1f199c4d7 fix(sync): show sync dates and pending-only green dot (#1765) (#1770)
Add yyyymmdd hhmm timestamps to cloud sync history and only show the green status dot when local changes are still pending upload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 00:12:03 +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
陈大猫 42b58efc5c fix: align top bar right-side icon buttons (#1298) (#1303)
* fix: align top bar right-side icon buttons (#1298)

Unify AI/sync/theme/settings buttons to h-7 in one row aligned with tabs.
SyncStatusButton was h-8 and settings lived in a separate container, causing
misalignment. Preserve Windows spacing before window controls (mr-2).

Fixes #1298

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

* fix: align window controls with utility icons in top bar

Merge window controls into the same row as utility buttons, match h-7 height, add left margin separation, and restore rectangular gray hover for all three buttons.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:07:07 +08:00
陈大猫 ea5320d94a Fix #954: unify Tooltip styling + replace native selects (#961)
* 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>
2026-05-12 20:14:24 +08:00
陈大猫 54b26511a1 Cloud sync data-loss prevention (4-layer defense) (#742)
* feat(sync-guard): extend SyncState with BLOCKED + add shrink event variants

* feat(sync-guard): add detectSuspiciousShrink pure function with 12 unit tests

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

* polish(sync-guard): drop unnecessary cast, sharpen test naming, pin priority invariant

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

* fix(test): include domain/*.test.ts in npm test glob

* feat(sync-guard): gate syncToProvider with shrink detection + force-push override

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

* fix(sync-guard): reset overrideShrinkOnce before early return for invariant strictness

* fix(sync-guard): extend shrink guard to syncAllProviders (the actual sync entry point)

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

* feat(sync-guard): apply empty-vault guard uniformly to auto and manual sync

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

* feat(sync-guard): preserve merge base on same-account re-auth

Adds providerAccountId persistence; completePKCEAuth and completeGitHubAuth
now only clear syncBase/anchor when the authenticated account id differs from
the previously stored one, preventing zombie-entry resurrection on token
refresh. disconnectProvider clears the stored id so a reconnect starts fresh.

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

* feat(sync-guard): add i18n strings for sync-blocked banner + force-push modal

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

* feat(sync-guard): add SyncBlockedBanner showing shrink findings with restore/force-push actions

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

* fix(sync-guard): stable subscribeToEvents reference + type-safe finding narrowing

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

* feat(sync-guard): force-push confirmation modal + scroll restore button into view

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

* ux(local-backups): show version as title, demote reason+timestamp to meta line

* feat(local-backups): record + display sync data version (v5/v6...) on each backup

Each backup now captures the live CloudSyncManager.localVersion at creation
time. UI shows it as title (v5, v6, ...) with timestamp + reason demoted to
the meta line. Backups created before this field existed (or before any
successful cloud sync) fall back to timestamp as title.

Replaces the earlier app-version-transition title which conflated app
version with sync data version.

* fix(sync-guard): consume override flag at sync entry + restore provider status on block

- Snapshot+clear overrideShrinkOnce at top of syncToProvider and
  syncAllProviders so an early-return cannot leak the flag to a later
  unrelated sync (Codex P1).
- Restore provider status to 'connected' when shrink-block returns from
  syncToProvider; previously left provider stuck on 'syncing' in the
  UI (Codex P2).
- Process pre-existing check errors before returning from the
  shouldBlockAll branch in syncAllProviders so a check-failed provider
  isn't dropped from results (Codex P2).

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

* fix(sync-guard): refactor force-push to parameter passing + add credential-availability guard

The previous design used a one-shot boolean flag on CloudSyncManager set
by forcePushOverrideShrink(). Even with snapshot+clear at sync entry
points, the renderer wrapper's await ensureUnlocked() could throw before
the flag was consumed, leaving it armed for the next unrelated sync.

Fix: pass overrideShrink as a call-time parameter through the chain.
Eliminates the persistent flag and its leak surface.

Also: force-push now runs the same ensureSyncablePayload(...) guard the
other manual sync entry points use, so a vault with encrypted-credential
placeholders won't be uploaded via the force path either.

Addresses the latest two Codex P1/P2 findings on #742.

* fix(sync-guard): backfill account id from in-memory state for upgrade-path re-auth

Users upgrading to this PR have no netcatty.sync.accountId.* persisted yet.
On their first re-auth the guard saw previousId=null and cleared the
merge base anyway, defeating the point of the same-account preservation.

Snapshot the in-memory account id BEFORE overwriting providers[provider]
and use it as a fallback when the persisted id is missing. New users
(no prior connection at all) still get the clear-on-first-auth path.

Addresses Codex P1 on #742.

* fix(sync-guard): inspect force-push results + mark blocked single-provider as error

- Force-push handler now inspects syncNow result entries: applies any
  mergedPayload to local state, only clears the banner when all providers
  report success, surfaces a toast error otherwise. Previously the banner
  cleared unconditionally regardless of network/auth failures (Codex P1).

- syncToProvider shrink-block branches now mark provider status as
  'error' with a 'Sync blocked: would delete too much' message instead
  of 'connected'. Status aggregators treat 'connected' as healthy, so
  the blocked upload was surfacing as 'synced' in the UI (Codex P2).
  syncAllProviders already used this pattern; this brings the
  single-provider path in line.

* fix(sync-guard): exempt USE_LOCAL conflict + clear post-merge BLOCKED + expose 'blocked' status

- USE_LOCAL conflict resolution now passes { overrideShrink: true }: the
  conflict modal already served as user confirmation, and shrink-blocking
  it left users with a closed modal and an opaque banner (Review C-1).

- Post-merge round-trip in useAutoSync now detects shrink-blocked results
  and resets syncState to IDLE via new manager.clearShrinkBlockedState().
  The merged data is already applied locally; the next user-triggered
  sync will re-check, and we don't wedge the manager in BLOCKED with no
  visible banner outside the Settings tab (Review I-1).

- overallSyncStatus now reports 'blocked' as a distinct value from
  'error', so downstream UI (status icon, future badges) can offer
  shrink-block-specific affordances (Review I-2).

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

* fix(sync-guard): stabilize banner subscription dep + map 'blocked' status to error indicator

- The SyncBlockedBanner subscription useEffect depended on [sync] (the
  whole hook return object), which gets a new reference every render.
  This caused the listener to be unsubscribed+resubscribed on every
  render, opening a tiny race window where a SYNC_BLOCKED_SHRINK event
  could be missed and the banner would never appear. Destructure
  subscribeToEvents (already useCallback-stable) and depend on it
  directly, so the effect runs exactly once on mount.

- SyncStatusButton's status mapping had no arm for the new 'blocked'
  value, falling through to 'none' (idle). The global status indicator
  said healthy while the in-page banner said paused. Map 'blocked' to
  the same error indicator used for 'conflict' so the UI is consistent.

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

* fix(sync-guard): only clear banner on actual success + hydrate from manager state

- Banner subscription now clears only on SYNC_COMPLETED with result.success.
  SYNC_STARTED (auto-sync timer ticks) and SYNC_FORCED (fires BEFORE upload)
  could clear the banner prematurely, removing the user's recovery affordance
  while the underlying issue was unresolved (Codex P2).

- Manager now persists the last shrink finding in state.lastShrinkFinding
  alongside the SYNC_BLOCKED_SHRINK emission. New public getter
  getShrinkBlockedFinding() returns it when syncState is BLOCKED. Renderer
  hydrates the banner on mount so a block that happened off-screen
  (auto-sync while user was on another tab) is still visible when they
  open Sync Settings (Codex P2).

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

* fix(sync-guard): unified BLOCKED-cleared event + USE_LOCAL inspects results

- USE_LOCAL conflict resolution now inspects syncNow() results, applies
  any mergedPayload to local state, surfaces a toast error and KEEPS the
  modal open on failure (so user can switch to USE_REMOTE). Mirrors the
  force-push handler pattern. Without this, USE_LOCAL silently 'succeeded'
  even when providers failed (Codex CLI P1).

- New SYNC_BLOCKED_CLEARED event emitted on every BLOCKED -> non-BLOCKED
  transition via a private exitBlockedState() helper. Banner subscribes to
  this single signal instead of guessing from per-provider SYNC_COMPLETED
  events. Fixes:
    - Multi-provider scenarios where first SYNC_COMPLETED clears the banner
      while a later provider was still going to fail (Codex CLI P1).
    - clearShrinkBlockedState() (post-merge self-heal) silently leaving
      the banner stuck because no event was emitted (Codex CLI P2).

- disconnectProvider() now also exits BLOCKED state. Disconnecting
  implicitly resolves any pending shrink-block warning, otherwise the
  stale alert carried over to the next-account reconnect (Codex CLI P2).

- All BLOCKED -> non-BLOCKED transitions consolidated through
  exitBlockedState() so lastShrinkFinding cleanup + event emission are
  always paired (Codex CLI P3 #6 covered).

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

* fix(sync-guard): only clear BLOCKED on actual success, not on transient ERROR/SYNCING/CONFLICT

Previous patch called exitBlockedState() at every BLOCKED -> non-BLOCKED
transition, but this clears the banner on transitions that don't actually
resolve the shrink concern:

- SYNCING (sync just started — about to try, may fail)
- ERROR (transient transport failure, shrink concern still real)
- CONFLICT (separate concern; doesn't resolve the shrink)

If a user was in BLOCKED then triggered a sync that failed for an unrelated
reason (network, auth), the banner cleared and they lost the warning.

Restrict exitBlockedState() to terminal-success transitions:
- IDLE on successful upload (data made it to cloud — concern resolved)
- explicit clears (disconnectProvider, clearShrinkBlockedState)
- conflict resolution (USE_REMOTE/USE_LOCAL also end in IDLE)

Found by Codex CLI review of commit 12d7fa7b.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:43:19 +08:00
陈大猫 117ec260b6 fix: address issue #294 follow-up regressions (#316)
* fix: address issue 294 regressions

* fix: scope sftp hidden files toggle per pane

* fix: restore terminal auto-follow behaviors

* fix: keep keypress auto-scroll scoped to keypress

* feat: add hidden files toggle to sftp modal

* fix: tighten sftp and terminal review findings
2026-03-12 16:19:22 +08:00
qi-xmu 6a76287bf7 Add local mono fonts support 2026-01-08 15:38:20 +08:00
LAPTOP-O016UC3M\Qi Chen 4652e78e6a Adds WebDAV and S3 sync provider support
Introduces encrypted cloud sync via WebDAV and S3-compatible storage, including UI for configuration, validation, and connection management. Enables selection as active sync providers alongside existing options. Updates i18n, state, and domain types to accommodate new providers, unifying connection logic and improving extensibility for future sync backends.
2025-12-22 15:36:23 +08:00
LAPTOP-O016UC3M\Qi Chen 4e9e5da13c Localizes UI and improves dynamic translation support
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.
2025-12-17 14:25:05 +08:00
LAPTOP-O016UC3M\Qi Chen d8347582b4 Adds full UI internationalization and language switching
Implements comprehensive i18n support, replacing hardcoded UI strings with translation keys and introducing an i18n provider across the app. Adds persistent UI language selection, cross-window sync, and system menu localization. Updates settings and state to handle locale, propagates language changes to Electron, and adjusts components for runtime translation.

Enables a user-friendly, multi-language experience and prepares the app for further language expansion.
2025-12-17 12:06:28 +08:00
LAPTOP-O016UC3M\Qi Chen 1c581f7b2a Adds group rename functionality to group context menu
Enables renaming of groups via a dialog accessed from the group context menu.
Validates input and updates related groups and hosts, improving group management usability.
2025-12-15 19:27:31 +08:00
LAPTOP-O016UC3M\Qi Chen 0c721613be Enables real-time cross-window sync and improves UI updates
Implements cross-window data synchronization to ensure state
remains consistent across multiple windows (such as settings
or separate Electron windows) using storage events and state
listeners. Improves UI responsiveness by forcing re-renders
on relevant state changes and ensuring immediate updates after
sync actions and conflict resolutions. Also enhances modal and
component reactivity for better user experience.
2025-12-15 10:51:29 +08:00
bincxz fcee0eb61b Refactors cloud sync state with useSyncExternalStore
Improves real-time synchronization of cloud sync state across React components by adopting useSyncExternalStore. Removes manual state management and event subscription in favor of a direct, consistent state snapshot from the manager. Updates the sync manager to support snapshot-based subscriptions and deep cloning for reliable UI updates.

Enhances user feedback on manual sync actions through integrated toast notifications. Simplifies UI state logic and reduces redundant state update calls, making sync operations more robust and maintainable.
2025-12-15 02:06:42 +08:00
bincxz 8518b01d67 Adds sync versioning and history to cloud sync UI
Improves the cloud sync experience by displaying local/remote version info and detailed sync history in both the sync settings and status button popovers. Tracks sync actions and outcomes for better transparency and debugging. Updates provider connection logic and disables redundant connect buttons when a provider is already active.

Enhances user trust and control by making sync status and activity more visible and actionable.
2025-12-15 01:47:02 +08:00
bincxz f070faffee Reformats avatar image element for consistent styling
Cleans up whitespace and formatting around the avatar image to improve code consistency and readability. No functional changes are introduced.
2025-12-15 00:54:25 +08:00
bincxz b297391f10 Enforces single cloud provider sync and improves UX
Ensures only one cloud provider is connected at a time by disconnecting others before authenticating a new provider, preventing sync conflicts and confusion. Improves error handling and logging for Google Drive sync file lookup, providing clearer user feedback for common API failures. Updates avatar image security and clarifies sync button behavior to enhance user experience.
2025-12-15 00:54:20 +08:00
bincxz 39dad4fbdf Reformats Cloud Sync components for consistent indentation
Updates indentation and formatting across cloud sync-related React components to improve code readability and maintain consistency. No logic or behavior is changed; all modifications are purely stylistic for easier maintenance.
2025-12-15 00:48:35 +08:00
bincxz d4399cd3d0 Integrates OAuth-based cloud sync and UI refactor
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.
2025-12-15 00:48:15 +08:00