mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
1d91c8fbf4e22d48fe2ff0130784fadfe2062853
19 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
90b6e514ef | Address terminal side panel auto-open review feedback | ||
|
|
05231d3fba | Add terminal side panel auto-open setting | ||
|
|
0c8370477c | Unify AI permission auto mode (#1688) | ||
|
|
cff74b6e49 | polish terminal and settings UI | ||
|
|
b74b5a442a |
[codex] Add vault notes markdown workspace (#1558)
* Add vault notes markdown workspace * Improve notes tree drag interactions * Harden notes tree move behavior * Refine vault notes interactions * Address vault notes review feedback * Tighten notes tree review fixes |
||
|
|
982569532c |
Sync AI API keys across devices
Sync AI API keys through cloud sync while keeping device-local external tool paths local. Also preserves local AI keys in backups and stops sync if a local encrypted key cannot be read. |
||
|
|
05b713ab18 |
[codex] Add configurable middle-click terminal behavior (#1443)
* Add configurable middle-click terminal behavior * Fix middle-click terminal behavior edge cases |
||
|
|
64a6986d01 |
Sync terminal selection AI preference (#1441)
Syncs the terminal selection Add to Conversation preference with AI settings so cloud sync, local backups, restores, and settings-only auto-sync detect the value. Follow-up for #1436. |
||
|
|
1ebcd017bd | Make terminal timestamps per-host | ||
|
|
4b5993cad6 | fix host sidebar behavior for editor tabs (#1348) | ||
|
|
e3f68e1a3f |
[codex] fix cloud sync master key refresh (#1175)
* fix cloud sync master key refresh * Protect first sync from default settings * Avoid recovery prompt for settings-only sync * Cancel stale syncs after master key changes |
||
|
|
53d3e05bb4 |
Per-agent provider switcher chip in the Catty Agent chat input (#1107)
* feat(ai): provider switcher chip in the Catty Agent chat input Catty Agent currently has no model chip in the chat input — modelPresets is empty for the catty agentId so `hasModelPicker` is false. The provider/model the chat ends up using comes from the global `activeProviderId` / `activeModelId`, which means switching providers requires opening Settings → AI → Providers. Closes the gap surfaced in the original feedback on #1101 (problem 2) and partially addresses #986 (per-context model switching) by going per-agent. State layer adds an `agentProviderMap` (`Record<agentId, providerId>`) alongside the existing `agentModelMap`. Both are written together when the user picks from the new dropdown; together they form a per-agent override that beats the global active provider/model when set. Cross- window sync mirrors the agentModelMap pattern. ChatInput grows a `providerSwitcher` prop carrying the enabled ProviderConfigs, the selected (providerId, modelId), and an onSelect callback. When supplied, the model chip switches from the Cpu glyph to the provider's ProviderIconBadge + `providerName · modelId`, and the popover renders a two-column layout — providers on the left (icon + name + default model caption), that provider's known model(s) on the right. ACP agents (Claude/Codex) are untouched because they plumb their provider through the CLI, not the Vercel AI SDK. AIChatSidePanel resolves `effectiveActiveProvider` / `effectiveActiveModelId` for the catty agent from the per-agent map, falling back to the global selection. handleSend now passes those through to sendToCattyAgent so a provider picked in one Catty session applies everywhere Catty runs. For v1, each provider's right column shows just its configured `defaultModel`. The two-level structure is in place to absorb richer listings (cached /models, manual additions) later without UI surgery — left a note in the popover code. Refs #1101, #986. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * polish(ai): flatten provider picker to a single list Two-level dropdown was theatre — each ProviderConfig exposes exactly one model (its `defaultModel`), so the right column ended up showing the same string already captioned on the left row. Picking a provider implicitly picks its model; one click is enough. Also drops the `p.enabled !== false` filter on the picker list. The user's expectation, confirmed by feedback on the first cut, is that the chat-input list mirrors what Settings → AI → Providers shows. The per-provider `enabled` toggle is an "active-ish" flag, not a visibility gate; hiding disabled providers in the picker silently made everything but the one currently active disappear, which is what the reviewer hit. Pares the popover down to a vertical list — provider icon + name + default-model caption (mono if set, italic "configure default model" hint if not) + a Check on the bound row. Removes the `hoveredProviderId` state along with the right-column rendering since it's no longer driving anything. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * polish(ai): tune provider icon sizes in the chat input Chip icon was sitting at sm (20px badge) and crowded the h-6 toolbar chip alongside the truncated label; popover rows used the same sm, so nothing differentiated the picker from the chip visually. Adds an xs size to ProviderIconBadge (16px badge, 10px glyph, 9px letter fallback) and uses it on the chip. Popover rows step up to md (32px badge, 16px glyph) and the row padding grows accordingly so the larger brand mark has room to breathe — the picker now reads like a list of choices, the chip reads like a status line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): keep per-agent override honest in model fallback + chip label Two codex P1/P2 findings on #1107: P1 — When Catty has a per-agent provider override but no per-agent model (agentModelMap['catty'] empty), cattyAgentModelId fell through to the global `activeModelId`. That id belongs to whichever provider was globally active, not the one Catty is now bound to, so e.g. overriding to DeepSeek without a defaultModel happily sent gpt-4o (global) to DeepSeek and produced a wrong-model error. The fallback is now: stored agent model → override provider's defaultModel → empty. Only the no-override path keeps the activeModelId tail; the no-provider send guard catches the empty case for everyone else. P2 — ChatInput's selectedSwitcherProvider used `?? providers[0]` so the chip always displayed *some* provider even when none was actually bound (selectedProviderId missing). The rest of the pipeline (send guard, agentProviderMap) treats that as "no provider", so the chip was lying. Dropped the fallback; when nothing is bound the chip now shows a generic Cpu glyph + "Select provider" label until the user picks one from the popover. Adds `ai.chat.selectProvider` in en / zh-CN / ru. Refs codex review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): purge per-agent binding when the bound provider is deleted Codex local review (round 2) flagged that a saved Catty model id could outlive its provider. The chain: - catty bound to provider A (DeepSeek): agentProviderMap['catty']='A', agentModelMap['catty']='deepseek-v4-flash' - user deletes provider A - cattyAgentProvider falls back to the global active provider (B, OpenAI) - cattyAgentModelId still returned the saved 'deepseek-v4-flash' - send dispatched the DeepSeek model id to OpenAI → wrong-model error Two layers of fix: 1. **Defensive resolution** (AIChatSidePanel) — cattyAgentModelId now looks at the override provider before trusting the stored model id. If the override is stale (provider deleted), the stored model id is treated as orphan and the resolution falls back to the global active selection just like cattyAgentProvider already does. 2. **Cleanup on remove** (useAIState.removeProvider) — when a provider is deleted, any agent whose agentProviderMap entry pointed at it gets both maps cleared (provider override + saved model). Same rationale: that model id is now meaningless against every other provider. Mirrors the existing activeProviderId cleanup. Adds a small agentProviderMapRef so removeProvider can snapshot which agents to clean up without relying on the captured-at-callback-create state (which would be stale). Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): include agentProviderMap in settings sync + block empty-model send Codex local review (round 3) found two issues: P2 — `agentProviderMap` was missing from the cross-device sync surface. Added it to the sync key list, the build path, the apply path, and the SyncPayload type alongside the existing `agentModelMap`. Sync tests in `syncPayload.test.ts` now cover the new field on both the build and apply sides. P2 — Provider rows with no `defaultModel` were still clickable in the chat-input picker, so selecting one would save a binding with empty model id; the send path then dispatched an empty model name and the SDK would surface a vague backend error. Two changes: 1. ChatInput disables the row (\`disabled\`, \`aria-disabled\`, dimmed styling, tooltip pointing the user to Settings) when the provider has no \`defaultModel\`. Click is suppressed. 2. AIChatSidePanel \`handleSend\` adds a model-required guard mirroring the existing no-provider guard — surfaces \`ai.chat.noProviderModel\` as an assistant message. Catches stale bindings (e.g. user edited the provider's \`defaultModel\` to empty after binding) before they reach the SDK. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): trim whitespace-only model ids + reconcile orphan bindings on sync Codex local review (round 4) found two more leaks: P2 — Whitespace-only `defaultModel` passed the picker's `disabled` gate (which trims) but every other layer (cattyAgentModelId resolution, the send-guard `!sendActiveModelId` check, the SDK call) compared the raw string. A provider whose default model was " " could still reach the SDK and surface a vague backend error. Normalized: trim at the resolution boundary (catty model fallback chain) and trim before the send-guard's existence check. P2 — Sync apply did not reconcile per-agent bindings against the incoming provider set. A payload could change `providers` without shipping a fresh `agentProviderMap`, leaving local overrides pointing to provider ids the synced set no longer includes — the same ghost- binding bug that `removeProvider` already handles for explicit user deletes. Added `pruneOrphanPerAgentBindings()` that runs after every AI settings apply: it walks `agentProviderMap`, drops any entry whose provider id isn't in the current `providers` list, and clears the saved model id for those agents alongside it (mirroring removeProvider cleanup). A new test in `syncPayload.test.ts` exercises the ghost- binding case end-to-end. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): notify open AI state of cross-device sync apply in the same window Codex local review (round 4) flagged that `applySyncPayload` writes straight to localStorage but `useAIState` only re-reads on browser `storage` events. Those events only fire in *other* windows — the window doing the apply (the one with the active chat panel) keeps showing pre-sync providers and per-agent bindings until reload. Concrete leak: cloud sync swaps in a new provider set, pruning runs, localStorage is correct, but the open Catty chip still references the ghost provider binding until the user reloads. Extracts the existing `AI_STATE_CHANGED_EVENT` constant + `emitAIStateChanged` helper out of `useAIState` and into a new `application/state/aiStateEvents.ts` so non-React call sites (sync apply, future IPC handlers) can fire it without pulling in the hook. After AI settings apply, `syncPayload.ts` walks every AI key it touched (including the agentProviderMap / agentModelMap that the reconcile step may have mutated even when the payload didn't ship them) and emits a same-window nudge. `useAIState`'s existing local listener routes unknown keys back through its storage handler, so each affected piece of React state rehydrates from localStorage. Adds a regression test in `syncPayload.test.ts` that asserts the expected `netcatty:ai-state-changed` keys are dispatched for the providers + per-agent map keys. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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 ( |
||
|
|
684c094d40 |
Drop externalAgents from cloud sync (device-local config)
ExternalAgentConfig.command/acpCommand/args/env are OS- and machine-specific (binary paths, .exe suffixes, platform-dependent environment values). Pushing them to other devices either fails to resolve or silently runs the wrong thing. Stop collecting/applying STORAGE_KEY_AI_EXTERNAL_AGENTS and remove the field from the SyncPayload type. apply silently ignores the field on legacy snapshots that still carry it, so existing remote data is safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d84c2cc902 |
Preserve local AI apiKeys when applying synced settings
`collectSyncableSettings` strips device-bound encrypted apiKeys from provider entries and webSearchConfig before upload, but `applySyncableSettings` was writing them back wholesale, silently wiping local credentials whenever any other setting changed on a second device. Merge by id (providers) and by providerId (web search) so a synced payload only overrides the apiKey when it explicitly carries one. Also include `application/*.test.ts` in the npm test glob so the syncPayload tests added in this PR actually run in CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c9da2a5893 |
Sync AI/UI settings and fix multi-display settings window
Extend cloud sync to cover AI provider config, external agents, permission/tool modes, command policy, web search settings, workspace focus style, terminal follow-app theme, SFTP default view, and additional terminal options. Device-bound encrypted apiKey placeholders are stripped from providers and webSearchConfig before upload. Auto-sync now reacts to syncable localStorage changes via a new adapter-level event. Center the Settings window on the display of the window that opened it instead of always using the main window, fixing issue #920. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
72e305fb7a | Add reusable proxy profiles | ||
|
|
155463f77c | add reusable proxy profiles | ||
|
|
4dd2465388 | Keep known hosts local during sync (#863) |