From 30e79d003d6380f94d59958b7761a780adc6f5d9 Mon Sep 17 00:00:00 2001 From: t Date: Sun, 6 Sep 2026 05:19:24 +0900 Subject: [PATCH 01/20] feat(gui): manage Aside sync per profile with scoped recovery --- .../260906_aside_profiles/020_profiles_gui.md | 14 + .../public/screenshots/aside-profiles.jpg | Bin 0 -> 46201 bytes gui/src/i18n/de.ts | 18 +- gui/src/i18n/en.ts | 18 +- gui/src/i18n/fr.ts | 18 +- gui/src/i18n/ja.ts | 18 +- gui/src/i18n/ko.ts | 18 +- gui/src/i18n/ru.ts | 18 +- gui/src/i18n/tr.ts | 18 +- gui/src/i18n/zh-TW.ts | 18 +- gui/src/i18n/zh.ts | 18 +- gui/src/pages/Integrations.tsx | 3 +- .../pages/integrations/AsideProfilesPage.tsx | 166 ++++++++ .../integrations/FileIntegrationPage.tsx | 70 ++-- .../integrations/IntegrationsOverview.tsx | 3 +- gui/src/pages/integrations/RestoreDialog.tsx | 9 +- .../pages/integrations/aside-profile-api.ts | 46 +++ .../integrations/aside-profile-contract.ts | 55 +++ gui/src/pages/integrations/integration-api.ts | 78 +++- gui/src/pages/integrations/refusal-copy.ts | 24 +- gui/src/styles-integrations.css | 17 + gui/tests/aside-profiles-page.test.tsx | 386 ++++++++++++++++++ gui/tests/integrations-api.test.ts | 21 + src/cli/dispatch.ts | 2 +- src/cli/integrations.ts | 2 +- src/cli/runtime-api.ts | 7 +- src/integrations/aside-profiles.ts | 12 +- src/integrations/owned-refresh.ts | 4 + tests/cli/cli-headless-parity.test.ts | 82 ++++ tests/clients/aside-profiles.test.ts | 20 + 30 files changed, 1128 insertions(+), 55 deletions(-) create mode 100644 docs-site/public/screenshots/aside-profiles.jpg create mode 100644 gui/src/pages/integrations/AsideProfilesPage.tsx create mode 100644 gui/src/pages/integrations/aside-profile-api.ts create mode 100644 gui/src/pages/integrations/aside-profile-contract.ts create mode 100644 gui/tests/aside-profiles-page.test.tsx diff --git a/devlog/_plan/260906_aside_profiles/020_profiles_gui.md b/devlog/_plan/260906_aside_profiles/020_profiles_gui.md index 16e5798e04..6f35ffd552 100644 --- a/devlog/_plan/260906_aside_profiles/020_profiles_gui.md +++ b/devlog/_plan/260906_aside_profiles/020_profiles_gui.md @@ -13,3 +13,17 @@ UPDATE guides/integrations.md and operating CLI docs with all-profile default, - Verification: remote focused GUI/API tests, GUI lint/i18n/build, root typecheck and required CI. Browser QA on local dev UI against three synthetic profiles, never real user profile mutation: initial mixed state, global enable, one profile disable, return to list after details, correct request selector, reload retains off state, failed profile does not imply sibling success, keyboard switches and narrow viewport. Capture actual screenshot for PR body using existing browser plugin, view it, and fix layout if needed. Screenshot contains synthetic labels only. A PR mentioning GUI includes screenshot. No local test suite or local typecheck; local dev server/browser probes are permitted. Terminal: verify every PR current head and all applicable hosted checks; native stack registration, owner-authorized admin merge, async merge completion, fetch dev and prove every merge SHA ancestry. Resolve CI or reviews rather than bypass evidence. No release or live service deployment. All original Grok/Pi/Codex and added Aside-profile criteria must be met before host goal completion. + +## P revalidation at1d4da9f9b + +Backend primary routes are now dedicated nested profile paths; server-only POST /aside/sync owns synchronization and preserves exclusions. The UI Sync-now button uses it, while the bulk switch sets all desired flags and per-row switches set one. Shared Switch already supports mixed state and aria-pressed. Preserve existing monochrome tokens/ClientMark (variance2/motion1/densityD5). StatusDTO carries desired enabled plus actual state, counts and per-profile errors; show partial/error states explicitly. FileIntegrationPage and RestoreDialog/Overview actions carry optional profile IDs into paths and cache keys. + +Main owns AsideProfilesPage, integration-api profile DTO/functions, parentpage/FileIntegrationPage/RestoreDialog/Overview wiring and CSS. A disjoint locale/test worker may own gui/src/i18n/{en,de,fr,ko,zh,zh-TW,ru,ja,tr}.ts and new gui/tests/aside-profiles-page.test.tsx after exactkeys/APIcontract are fixed. All GUI checks run remotely; local Vite and browser probes only. Capture wide+narrow realcomponent screenshots against synthetic three-profile management fixtures, not real accounts. + +A audit passed: DTO error precedes empty rendering; stale errors remain visible; always refetch after refused mutations because intent may already be saved; use mixed Switch and void refresh semantics. Exact locale keys are locked in ignored .tmp/aside-profiles/ui-keys.json. Main exports loadAsideProfiles and syncAsideProfiles from integration-api, and adds profileId as last optional argument to existing state/toggle/history/restore/delete functions. Toggle rejects207 partial only through returnedokfalse; UI reports it and refetches. + +Implementation modularization: profile DTO validation and load/sync readers live in new aside-profile-api.ts, reusing the existing integration transport/error owner without a circular re-export. Existing integration-api functions keep their non-Aside signatures and unscoped cache identities; profile scope is an optional final argument. Scoped successful state/toggle/restore/delete responses and journal rows must match the requested profile. + +Review fold-back: per-profile refusal/recovery outcomes remain typed and visible in bulk and Sync-now failures, including snapshotPath/residual; failed Aside restore reconciles owner resources while retaining the dialog/error; list and detail share one pure profile-status validator. Add a localized no-snapshot recovery warning and the corresponding regression cases. No source permission or confirmation boundary is weakened. + +B verification: the final interface provides bulk and individual desired-state switches, actual applied counts, per-profile retry/refusal and recovery details, profile-scoped history/restore/delete, and nine-locale copy. Remote checks passed: 74 interface/API cases, then 54 affected interface/cache cases after type narrowing, GUI build and both lint commands; 27 engine cases with real failed compensation, 62 CLI cases, and root typecheck. Initial failing compensation fixture was corrected to fail ownership after a successful file write, then fail rollback. Browser probes on three synthetic profiles covered bulk enable, exclusion persistence, scoped Undo, keyboard control, narrow layout without horizontal overflow, and per-profile external-edit refusal. Screenshot: docs-site/public/screenshots/aside-profiles.jpg. No real Aside profile files were written. Hosted exact-head CI and stack landing remain open. diff --git a/docs-site/public/screenshots/aside-profiles.jpg b/docs-site/public/screenshots/aside-profiles.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7e788463214e1fbd613ac33a37ead18f15b8c738 GIT binary patch literal 46201 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 literal 0 HcmV?d00001 diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index cf04d02f67..a295a3295d 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1044,6 +1044,21 @@ export const de: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside-Profile", + "integrations.aside.profilesHint": "Wähle, welche Profile die ausgewählten Modelle erhalten. Das aktive Aside-Profil bleibt unverändert.", + "integrations.aside.all": "Alle Profile synchronisieren", + "integrations.aside.syncNow": "Jetzt synchronisieren", + "integrations.aside.applied": "Bei {count} von {total} Profilen angewendet", + "integrations.aside.current": "Aktuelles Profil", + "integrations.aside.profile": "Profil {id}", + "integrations.aside.toggle": "{name} synchronisieren", + "integrations.aside.details": "{name} verwalten", + "integrations.aside.back": "Alle Aside-Profile", + "integrations.aside.empty": "Öffne Aside und erstelle ein Profil, um es zu verbinden.", + "integrations.aside.partial": "Einige Profile benötigen Aufmerksamkeit. Deine Synchronisierungsauswahl ist gespeichert. Prüfe den Status der einzelnen Profile.", + "integrations.aside.pending": "Synchronisierungsauswahl gespeichert; Dateiaktualisierung steht aus.", + "integrations.aside.retry": "Für {name} erneut versuchen", + "integrations.aside.loadError": "Aside-Profile konnten nicht geladen werden. Versuche es erneut, um ihren Status zu prüfen.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Die Codex-Anbindung wird vom Proxy-Dienst verwaltet. Beim Start von opencodex wird sie angewendet; beim Stoppen des Dienstes wird das native Routing wiederhergestellt.", "integrations.codex.openService": "Dienststeuerung öffnen", @@ -1162,6 +1177,7 @@ export const de: Record = { "integrations.bulk.success": "Angewendete Client-Integrationen wurden deaktiviert.", "integrations.retention.degraded": "Die Sicherungsbereinigung ist im Rückstand; ältere Sicherungen könnten noch auf dem Datenträger liegen.", "integrations.error.residual": "Die Datei könnte sich in einem Zwischenzustand befinden: {message} Stellen Sie sie aus {path} wieder her.", + "integrations.error.residualNoSnapshot": "{message} Die automatische Wiederherstellung wurde nicht abgeschlossen. Prüfe die Client-Konfiguration, bevor du es erneut versuchst.", "integrations.error.recover": "{message} Eine Sicherung liegt unter {path}.", "integrations.kind.apply": "Angewendet", "integrations.kind.disable": "Deaktiviert", @@ -1186,7 +1202,7 @@ export const de: Record = { "integrations.semantics.mcode": "Verwaltet nur custom_provider.opencodex. Standardmodell und MiniMax-Anmeldung bleiben unverändert.", "integrations.semantics.zcode": "Verwaltet nur provider.opencodex in ~/.zcode/v2/config.json. Z.ai-Anmeldung und andere Provider bleiben unverändert. ZCode nach Änderungen neu starten.", "integrations.semantics.prime": "Verwaltet nur providers.opencodex in der models.json von Prime Agent — ~/.prime/agent, sofern PRIME_AGENT_CODING_AGENT_DIR sie nicht umleitet. Andere Provider und Modell-Overrides bleiben unverändert. Gilt für neue Sitzungen.", - "integrations.semantics.aside": "Verwaltet nur providers.opencodex in der models.json von Aside für das angemeldete Konto (~/.aside/u/). Andere Provider bleiben unverändert. Aside überschreibt diese Datei im laufenden Betrieb, daher nach dem Anwenden vollständig beenden und neu öffnen.", + "integrations.semantics.aside": "Verwaltet nur providers.opencodex in der models.json dieses Profils. Andere Provider bleiben unverändert. Beende Aside nach dem Anwenden vollständig und öffne es erneut.", "codexAuth.mainAccount": "Hauptkonto", "codexAuth.logLabel": "Log-Kennung", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 5d0c0b0983..2b57424437 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1551,6 +1551,21 @@ export const en = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside profiles", + "integrations.aside.profilesHint": "Choose which profiles receive the selected models. Aside’s active profile stays unchanged.", + "integrations.aside.all": "Sync all profiles", + "integrations.aside.syncNow": "Sync now", + "integrations.aside.applied": "{count} of {total} profiles applied", + "integrations.aside.current": "Current profile", + "integrations.aside.profile": "Profile {id}", + "integrations.aside.toggle": "Sync {name}", + "integrations.aside.details": "Manage {name}", + "integrations.aside.back": "All Aside profiles", + "integrations.aside.empty": "Open Aside and create a profile to connect it.", + "integrations.aside.partial": "Some profiles need attention. Your sync choices are saved; check each profile’s state.", + "integrations.aside.pending": "Sync choice saved; file update pending.", + "integrations.aside.retry": "Retry {name}", + "integrations.aside.loadError": "Could not load Aside profiles. Retry to check their state.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex wiring is owned by the proxy service. Starting opencodex applies it; stopping the service restores native routing.", "integrations.codex.openService": "Open service controls", @@ -1709,6 +1724,7 @@ export const en = { "integrations.bulk.success": "Applied client integrations were disabled.", "integrations.retention.degraded": "Backup cleanup is behind; older backups may still be on disk.", "integrations.error.residual": "The file may be in an intermediate state: {message} Restore it from {path}.", + "integrations.error.residualNoSnapshot": "{message} Automatic recovery did not finish. Check the client configuration before retrying.", "integrations.error.recover": "{message} A backup is at {path}.", "integrations.kind.apply": "Applied", "integrations.kind.disable": "Disabled", @@ -1733,7 +1749,7 @@ export const en = { "integrations.semantics.mcode": "Manages only custom_provider.opencodex. Your default model and MiniMax login stay unchanged.", "integrations.semantics.zcode": "Manages only provider.opencodex in ~/.zcode/v2/config.json. Your Z.ai login and other providers stay unchanged. Restart ZCode after changes.", "integrations.semantics.prime": "Manages only providers.opencodex in Prime Agent's models.json — ~/.prime/agent unless PRIME_AGENT_CODING_AGENT_DIR redirects it. Your other providers and model overrides stay unchanged. Applies to new sessions.", - "integrations.semantics.aside": "Manages only providers.opencodex in Aside's models.json for the signed-in account (~/.aside/u/). Your other providers stay unchanged. Aside rewrites this file while running, so fully quit and reopen it after applying.", + "integrations.semantics.aside": "Manages only providers.opencodex in this profile’s models.json. Your other providers stay unchanged. Fully quit and reopen Aside after applying.", "codexAuth.mainAccount": "Main Account", "codexAuth.logLabel": "Log label", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index ac0d9f17c3..2cbab0a955 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1523,6 +1523,21 @@ export const fr: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Profils Aside", + "integrations.aside.profilesHint": "Choisissez les profils qui recevront les modèles sélectionnés. Le profil actif dans Aside reste inchangé.", + "integrations.aside.all": "Synchroniser tous les profils", + "integrations.aside.syncNow": "Synchroniser maintenant", + "integrations.aside.applied": "Appliqué à {count} profils sur {total}", + "integrations.aside.current": "Profil actuel", + "integrations.aside.profile": "Profil {id}", + "integrations.aside.toggle": "Synchroniser {name}", + "integrations.aside.details": "Gérer {name}", + "integrations.aside.back": "Tous les profils Aside", + "integrations.aside.empty": "Ouvrez Aside et créez un profil pour le connecter.", + "integrations.aside.partial": "Certains profils nécessitent votre attention. Vos choix de synchronisation sont enregistrés ; vérifiez l’état de chaque profil.", + "integrations.aside.pending": "Choix de synchronisation enregistré ; mise à jour du fichier en attente.", + "integrations.aside.retry": "Réessayer pour {name}", + "integrations.aside.loadError": "Impossible de charger les profils Aside. Réessayez pour vérifier leur état.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Le câblage de Codex est géré par le service proxy. Le démarrage d’opencodex l’applique ; l’arrêt du service rétablit le routage natif.", "integrations.codex.openService": "Ouvrir les commandes du service", @@ -1641,6 +1656,7 @@ export const fr: Record = { "integrations.bulk.success": "Les intégrations client appliquées ont été désactivées.", "integrations.retention.degraded": "Le nettoyage des sauvegardes est en retard ; d’anciennes sauvegardes peuvent encore se trouver sur le disque.", "integrations.error.residual": "Le fichier peut être dans un état intermédiaire : {message} Restaurez-le depuis {path}.", + "integrations.error.residualNoSnapshot": "{message} La récupération automatique n’a pas abouti. Vérifiez la configuration du client avant de réessayer.", "integrations.error.recover": "{message} Une sauvegarde se trouve dans {path}.", "integrations.kind.apply": "Appliqué", "integrations.kind.disable": "Désactivé", @@ -1665,7 +1681,7 @@ export const fr: Record = { "integrations.semantics.mcode": "Gère uniquement custom_provider.opencodex. Votre modèle par défaut et votre connexion MiniMax restent inchangés.", "integrations.semantics.zcode": "Gère uniquement provider.opencodex dans ~/.zcode/v2/config.json. Votre connexion Z.ai et les autres fournisseurs restent inchangés. Redémarrez ZCode après toute modification.", "integrations.semantics.prime": "Gère uniquement providers.opencodex dans le models.json de Prime Agent — ~/.prime/agent, sauf si PRIME_AGENT_CODING_AGENT_DIR le redirige. Vos autres fournisseurs et surcharges de modèles restent inchangés. S'applique aux nouvelles sessions.", - "integrations.semantics.aside": "Gère uniquement providers.opencodex dans le models.json d'Aside pour le compte connecté (~/.aside/u/). Vos autres fournisseurs restent inchangés. Aside réécrit ce fichier pendant son exécution : quittez-le complètement et relancez-le après application.", + "integrations.semantics.aside": "Gère uniquement providers.opencodex dans le fichier models.json de ce profil. Vos autres fournisseurs restent inchangés. Quittez complètement Aside et relancez-le après application.", "codexAuth.mainAccount": "Compte principal", "codexAuth.logLabel": "Libellé du journal", "codexAuth.codexApp": "Application Codex", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index 7c6eab5675..9ad7767549 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1478,6 +1478,21 @@ export const ja: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Asideのプロファイル", + "integrations.aside.profilesHint": "選択したモデルを同期するプロファイルを選んでください。Asideで使用中のプロファイルは変わりません。", + "integrations.aside.all": "すべてのプロファイルを同期", + "integrations.aside.syncNow": "今すぐ同期", + "integrations.aside.applied": "{total}件中{count}件のプロファイルに適用済み", + "integrations.aside.current": "使用中のプロファイル", + "integrations.aside.profile": "プロファイル {id}", + "integrations.aside.toggle": "{name}を同期", + "integrations.aside.details": "{name}を管理", + "integrations.aside.back": "Asideの全プロファイル", + "integrations.aside.empty": "Asideを開き、接続するプロファイルを作成してください。", + "integrations.aside.partial": "一部のプロファイルに確認が必要です。同期設定は保存されています。各プロファイルの状態を確認してください。", + "integrations.aside.pending": "同期設定を保存しました。ファイルの更新待ちです。", + "integrations.aside.retry": "{name}を再試行", + "integrations.aside.loadError": "Asideのプロファイルを読み込めませんでした。再試行して状態を確認してください。", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex の接続はプロキシサービスが管理します。opencodex を起動すると適用され、サービスを停止するとネイティブのルーティングに戻ります。", "integrations.codex.openService": "サービス制御を開く", @@ -1596,6 +1611,7 @@ export const ja: Record = { "integrations.bulk.success": "適用済みのクライアント連携を無効にしました。", "integrations.retention.degraded": "バックアップの整理が遅れています。古いバックアップがディスクに残っている可能性があります。", "integrations.error.residual": "ファイルが中間状態のままの可能性があります: {message} {path} から復元してください。", + "integrations.error.residualNoSnapshot": "{message} 自動復旧が完了しませんでした。再試行する前にクライアントの設定を確認してください。", "integrations.error.recover": "{message} バックアップは {path} にあります。", "integrations.kind.apply": "適用", "integrations.kind.disable": "解除", @@ -1620,7 +1636,7 @@ export const ja: Record = { "integrations.semantics.mcode": "custom_provider.opencodex のみを管理します。既定モデルと MiniMax ログインは変更しません。", "integrations.semantics.zcode": "~/.zcode/v2/config.json の provider.opencodex のみを管理します。Z.ai ログインと他のプロバイダーは変更しません。変更後は ZCode を再起動してください。", "integrations.semantics.prime": "Prime Agent の models.json 内の providers.opencodex のみを管理します。場所は ~/.prime/agent ですが、PRIME_AGENT_CODING_AGENT_DIR が設定されている場合はそちらが優先されます。他のプロバイダーとモデルオーバーライドは変更しません。新しいセッションから適用されます。", - "integrations.semantics.aside": "サインイン中のアカウントの Aside models.json 内の providers.opencodex のみを管理します。場所は ~/.aside/u/<アカウント> です。他のプロバイダーは変更しません。Aside は実行中にこのファイルを書き換えるため、適用後は Aside を完全に終了して再度開いてください。", + "integrations.semantics.aside": "このプロファイルの models.json 内の providers.opencodex のみを管理します。他のプロバイダーは変更しません。適用後は Aside を完全に終了してから開き直してください。", "codexAuth.mainAccount": "メインアカウント", "codexAuth.logLabel": "ログラベル", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 21d9ab45eb..8bfa8f8b41 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1068,6 +1068,21 @@ export const ko: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside 프로필", + "integrations.aside.profilesHint": "선택한 모델을 동기화할 프로필을 고르세요. Aside에서 사용 중인 프로필은 바뀌지 않습니다.", + "integrations.aside.all": "모든 프로필 동기화", + "integrations.aside.syncNow": "지금 동기화", + "integrations.aside.applied": "{total}개 중 {count}개 적용됨", + "integrations.aside.current": "사용 중", + "integrations.aside.profile": "프로필 {id}", + "integrations.aside.toggle": "{name} 동기화", + "integrations.aside.details": "{name} 관리", + "integrations.aside.back": "전체 Aside 프로필", + "integrations.aside.empty": "Aside를 열고 연결할 프로필을 만드세요.", + "integrations.aside.partial": "일부 프로필을 확인해 주세요. 동기화 설정은 저장됐습니다. 각 프로필의 상태를 확인하세요.", + "integrations.aside.pending": "동기화 설정 저장됨 · 파일 반영 대기 중", + "integrations.aside.retry": "{name} 다시 시도", + "integrations.aside.loadError": "Aside 프로필을 불러오지 못했습니다. 다시 시도해 상태를 확인하세요.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex 연결은 프록시 서비스가 관리합니다. opencodex를 시작하면 적용되고 서비스를 중지하면 기본 라우팅으로 복원됩니다.", "integrations.codex.openService": "서비스 제어 열기", @@ -1186,6 +1201,7 @@ export const ko: Record = { "integrations.bulk.success": "적용된 클라이언트 연동을 해제했습니다.", "integrations.retention.degraded": "백업 정리가 밀려 있습니다 — 오래된 백업이 남아 있을 수 있습니다.", "integrations.error.residual": "파일이 중간 상태로 남았을 수 있습니다: {message} {path}에서 복원하세요.", + "integrations.error.residualNoSnapshot": "{message} 자동 복구를 마치지 못했습니다. 다시 시도하기 전에 클라이언트 설정을 확인하세요.", "integrations.error.recover": "{message} 백업 위치는 {path}입니다.", "integrations.kind.apply": "적용", "integrations.kind.disable": "해제", @@ -1210,7 +1226,7 @@ export const ko: Record = { "integrations.semantics.mcode": "custom_provider.opencodex만 관리하며 기본 모델과 MiniMax 로그인은 변경하지 않습니다.", "integrations.semantics.zcode": "~/.zcode/v2/config.json의 provider.opencodex만 관리하며 Z.ai 로그인과 다른 프로바이더는 변경하지 않습니다. 변경 후 ZCode를 재시작하세요.", "integrations.semantics.prime": "Prime Agent의 models.json에서 providers.opencodex만 관리합니다. 위치는 ~/.prime/agent이며 PRIME_AGENT_CODING_AGENT_DIR가 설정되면 그쪽이 우선합니다. 다른 프로바이더와 모델 오버라이드는 변경하지 않습니다. 새 세션부터 적용됩니다.", - "integrations.semantics.aside": "로그인된 계정의 Aside models.json에서 providers.opencodex만 관리합니다. 위치는 ~/.aside/u/<계정>이며 다른 프로바이더는 변경하지 않습니다. Aside는 실행 중에 이 파일을 다시 쓰기 때문에 적용한 뒤 Aside를 완전히 종료하고 다시 여세요.", + "integrations.semantics.aside": "이 프로필의 models.json에서 providers.opencodex만 관리합니다. 다른 프로바이더는 그대로 유지됩니다. 적용 후 Aside를 완전히 종료하고 다시 여세요.", "codexAuth.mainAccount": "메인 계정", "codexAuth.logLabel": "로그 라벨", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index c97b62e9ac..bee9f3714f 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1534,6 +1534,21 @@ export const ru: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Профили Aside", + "integrations.aside.profilesHint": "Выберите профили, в которые будут добавлены выбранные модели. Активный профиль Aside не изменится.", + "integrations.aside.all": "Синхронизировать все профили", + "integrations.aside.syncNow": "Синхронизировать сейчас", + "integrations.aside.applied": "Применено к профилям: {count} из {total}", + "integrations.aside.current": "Текущий профиль", + "integrations.aside.profile": "Профиль {id}", + "integrations.aside.toggle": "Синхронизировать {name}", + "integrations.aside.details": "Управление: {name}", + "integrations.aside.back": "Все профили Aside", + "integrations.aside.empty": "Откройте Aside и создайте профиль для подключения.", + "integrations.aside.partial": "Некоторые профили требуют внимания. Настройки синхронизации сохранены; проверьте состояние каждого профиля.", + "integrations.aside.pending": "Настройка синхронизации сохранена; обновление файла ожидается.", + "integrations.aside.retry": "Повторить для {name}", + "integrations.aside.loadError": "Не удалось загрузить профили Aside. Повторите попытку, чтобы проверить их состояние.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Подключением Codex управляет прокси-сервис. При запуске opencodex оно применяется, а при остановке сервиса восстанавливается нативная маршрутизация.", "integrations.codex.openService": "Открыть управление сервисом", @@ -1652,6 +1667,7 @@ export const ru: Record = { "integrations.bulk.success": "Применённые интеграции клиентов отключены.", "integrations.retention.degraded": "Очистка резервных копий отстаёт; старые копии могут всё ещё находиться на диске.", "integrations.error.residual": "Файл может остаться в промежуточном состоянии: {message} Восстановите его из {path}.", + "integrations.error.residualNoSnapshot": "{message} Автоматическое восстановление не завершено. Проверьте настройки клиента перед повторной попыткой.", "integrations.error.recover": "{message} Резервная копия находится в {path}.", "integrations.kind.apply": "Применено", "integrations.kind.disable": "Отключено", @@ -1676,7 +1692,7 @@ export const ru: Record = { "integrations.semantics.mcode": "Управляет только custom_provider.opencodex. Модель по умолчанию и вход MiniMax не меняются.", "integrations.semantics.zcode": "Управляет только provider.opencodex в ~/.zcode/v2/config.json. Вход Z.ai и другие провайдеры не меняются. Перезапустите ZCode после изменений.", "integrations.semantics.prime": "Управляет только providers.opencodex в models.json Prime Agent — ~/.prime/agent, если PRIME_AGENT_CODING_AGENT_DIR не переопределяет путь. Другие провайдеры и переопределения моделей не меняются. Применяется к новым сессиям.", - "integrations.semantics.aside": "Управляет только providers.opencodex в models.json Aside для выполнившего вход аккаунта (~/.aside/u/<аккаунт>). Другие провайдеры не меняются. Aside перезаписывает этот файл во время работы, поэтому после применения полностью закройте и снова откройте его.", + "integrations.semantics.aside": "Управляет только providers.opencodex в файле models.json этого профиля. Другие провайдеры остаются без изменений. После применения полностью закройте и снова откройте Aside.", "codexAuth.mainAccount": "Основной аккаунт", "codexAuth.logLabel": "Метка журнала", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 7a6f5107c0..124f3a41c6 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1541,6 +1541,21 @@ export const tr: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside profilleri", + "integrations.aside.profilesHint": "Seçili modellerin hangi profillere aktarılacağını seçin. Aside’ın etkin profili değişmez.", + "integrations.aside.all": "Tüm profilleri eşitle", + "integrations.aside.syncNow": "Şimdi eşitle", + "integrations.aside.applied": "{total} profilden {count} tanesine uygulandı", + "integrations.aside.current": "Geçerli profil", + "integrations.aside.profile": "Profil {id}", + "integrations.aside.toggle": "{name} profilini eşitle", + "integrations.aside.details": "{name} profilini yönet", + "integrations.aside.back": "Tüm Aside profilleri", + "integrations.aside.empty": "Bağlamak için Aside’ı açıp bir profil oluşturun.", + "integrations.aside.partial": "Bazı profillerle ilgilenmeniz gerekiyor. Eşitleme tercihleriniz kaydedildi; her profilin durumunu kontrol edin.", + "integrations.aside.pending": "Eşitleme tercihi kaydedildi; dosya güncellemesi bekleniyor.", + "integrations.aside.retry": "{name} için yeniden dene", + "integrations.aside.loadError": "Aside profilleri yüklenemedi. Durumlarını kontrol etmek için yeniden deneyin.", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex bağlantısı proxy servisine aittir.", "integrations.codex.openService": "Servis kontrollerini aç", @@ -1659,6 +1674,7 @@ export const tr: Record = { "integrations.bulk.success": "Uygulanan istemci entegrasyonları devre dışı bırakıldı.", "integrations.retention.degraded": "Yedek temizliği geride kaldı.", "integrations.error.residual": "{path} konumunda {message}", + "integrations.error.residualNoSnapshot": "{message} Otomatik kurtarma tamamlanamadı. Yeniden denemeden önce istemci yapılandırmasını kontrol edin.", "integrations.error.recover": "{path} kurtarılırken {message}", "integrations.kind.apply": "Uygulandı", "integrations.kind.disable": "Devre Dışı Bırakıldı", @@ -1682,7 +1698,7 @@ export const tr: Record = { "integrations.semantics.mcode": "Yalnızca custom_provider.opencodex bölümünü yönetir. Varsayılan model ve MiniMax oturumu değişmez.", "integrations.semantics.zcode": "Yalnızca ~/.zcode/v2/config.json içindeki provider.opencodex bölümünü yönetir. Z.ai oturumu ve diğer sağlayıcılar değişmez. Değişikliklerden sonra ZCode'u yeniden başlatın.", "integrations.semantics.prime": "Yalnızca Prime Agent'ın models.json dosyasındaki providers.opencodex bölümünü yönetir — PRIME_AGENT_CODING_AGENT_DIR ayarlı değilse ~/.prime/agent. Diğer sağlayıcılar ve model geçersiz kılmaları değişmez. Yeni oturumlarda geçerli olur.", - "integrations.semantics.aside": "Yalnızca oturum açmış hesabın Aside models.json dosyasındaki providers.opencodex bölümünü yönetir (~/.aside/u/). Diğer sağlayıcılar değişmez. Aside çalışırken bu dosyayı yeniden yazar; bu nedenle uyguladıktan sonra Aside'ı tamamen kapatıp yeniden açın.", + "integrations.semantics.aside": "Yalnızca bu profilin models.json dosyasındaki providers.opencodex bölümünü yönetir. Diğer sağlayıcılarınız değişmez. Uyguladıktan sonra Aside’ı tamamen kapatıp yeniden açın.", "integrations.semantics.omp": "Kataloğu yüklemek için OMP'yi yeniden başlatın.", "codexAuth.mainAccount": "Ana Hesap", "codexAuth.logLabel": "Günlük etiketi", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index ed0aeba2d4..5d35128824 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -2129,6 +2129,21 @@ export const zhTW: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside 設定檔", + "integrations.aside.profilesHint": "選擇要接收所選模型的設定檔。Aside 目前使用的設定檔不會改變。", + "integrations.aside.all": "同步所有設定檔", + "integrations.aside.syncNow": "立即同步", + "integrations.aside.applied": "已套用 {count}/{total} 個設定檔", + "integrations.aside.current": "目前的設定檔", + "integrations.aside.profile": "設定檔 {id}", + "integrations.aside.toggle": "同步 {name}", + "integrations.aside.details": "管理 {name}", + "integrations.aside.back": "所有 Aside 設定檔", + "integrations.aside.empty": "開啟 Aside 並建立設定檔以進行連線。", + "integrations.aside.partial": "部分設定檔需要處理。同步設定已儲存,請檢查各設定檔的狀態。", + "integrations.aside.pending": "同步設定已儲存,等待更新檔案。", + "integrations.aside.retry": "重試 {name}", + "integrations.aside.loadError": "無法載入 Aside 設定檔。請重試以查看狀態。", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex 連線由代理服務管理。啟動 opencodex 時套用;停止服務時還原原生路由。", "integrations.codex.openService": "開啟服務控制", @@ -2247,6 +2262,7 @@ export const zhTW: Record = { "integrations.bulk.success": "已套用的用戶端整合已停用。", "integrations.retention.degraded": "備份清理進度落後;磁碟上可能仍有較舊的備份。", "integrations.error.residual": "檔案可能處於中間狀態:{message} 請從 {path} 還原。", + "integrations.error.residualNoSnapshot": "{message} 自動復原未完成。請先檢查用戶端設定,再重試。", "integrations.error.recover": "{message} 備份位於 {path}。", "integrations.kind.apply": "已套用", "integrations.kind.disable": "已停用", @@ -2271,7 +2287,7 @@ export const zhTW: Record = { "integrations.semantics.mcode": "僅管理 custom_provider.opencodex,不會變更預設模型或 MiniMax 登入狀態。", "integrations.semantics.zcode": "僅管理 ~/.zcode/v2/config.json 中的 provider.opencodex,不會變更 Z.ai 登入狀態或其他供應商。變更後請重新啟動 ZCode。", "integrations.semantics.prime": "僅管理 Prime Agent 的 models.json 中的 providers.opencodex;預設位於 ~/.prime/agent,若設定 PRIME_AGENT_CODING_AGENT_DIR 則以其為準。不會變更其他供應商或模型覆寫設定。對新工作階段生效。", - "integrations.semantics.aside": "僅管理已登入帳號的 Aside models.json 中的 providers.opencodex,位於 ~/.aside/u/<帳號>。不會變更其他供應商。Aside 在執行時會重寫該檔案,因此套用後請完全結束並重新開啟 Aside。", + "integrations.semantics.aside": "僅管理此設定檔的 models.json 中的 providers.opencodex。其他供應商維持不變。套用後請完全結束並重新開啟 Aside。", "codexAuth.pinned": "已固定", "codexAuth.pinnedHint": "你手動選取了此帳號,因此較高的選擇順序不會越過它。此固定會持續到該帳號用盡、你改選其他帳號,或你變更任一選擇順序為止。", "codexAuth.requestUserInput": "在 Default 模式中要求輸入", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 44450d8785..d6819f9104 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1061,6 +1061,21 @@ export const zh: Record = { "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", + "integrations.aside.profilesTitle": "Aside 配置文件", + "integrations.aside.profilesHint": "选择要接收所选模型的配置文件。Aside 当前使用的配置文件不会改变。", + "integrations.aside.all": "同步所有配置文件", + "integrations.aside.syncNow": "立即同步", + "integrations.aside.applied": "已应用 {count}/{total} 个配置文件", + "integrations.aside.current": "当前配置文件", + "integrations.aside.profile": "配置文件 {id}", + "integrations.aside.toggle": "同步 {name}", + "integrations.aside.details": "管理 {name}", + "integrations.aside.back": "所有 Aside 配置文件", + "integrations.aside.empty": "打开 Aside 并创建配置文件以进行连接。", + "integrations.aside.partial": "部分配置文件需要处理。同步设置已保存,请检查各配置文件的状态。", + "integrations.aside.pending": "同步设置已保存,等待更新文件。", + "integrations.aside.retry": "重试 {name}", + "integrations.aside.loadError": "无法加载 Aside 配置文件。请重试以查看状态。", "integrations.codex.title": "Codex CLI", "integrations.codex.body": "Codex 连接由代理服务管理。启动 opencodex 时应用该连接;停止服务时恢复原生路由。", "integrations.codex.openService": "打开服务控制", @@ -1179,6 +1194,7 @@ export const zh: Record = { "integrations.bulk.success": "已禁用已应用的客户端集成。", "integrations.retention.degraded": "备份清理进度滞后;磁盘上可能仍有较旧的备份。", "integrations.error.residual": "文件可能处于中间状态:{message} 请从 {path} 恢复。", + "integrations.error.residualNoSnapshot": "{message} 自动恢复未完成。请先检查客户端配置,再重试。", "integrations.error.recover": "{message} 备份位于 {path}。", "integrations.kind.apply": "已应用", "integrations.kind.disable": "已停用", @@ -1203,7 +1219,7 @@ export const zh: Record = { "integrations.semantics.mcode": "仅管理 custom_provider.opencodex,不会更改默认模型或 MiniMax 登录状态。", "integrations.semantics.zcode": "仅管理 ~/.zcode/v2/config.json 中的 provider.opencodex,不会更改 Z.ai 登录状态或其他提供商。更改后请重启 ZCode。", "integrations.semantics.prime": "仅管理 Prime Agent 的 models.json 中的 providers.opencodex;默认位于 ~/.prime/agent,若设置 PRIME_AGENT_CODING_AGENT_DIR 则以其为准。不会更改其他提供商或模型覆盖设置。对新会话生效。", - "integrations.semantics.aside": "仅管理已登录账号的 Aside models.json 中的 providers.opencodex,位于 ~/.aside/u/<账号>。不会更改其他提供商。Aside 在运行时会重写该文件,因此应用后请完全退出并重新打开 Aside。", + "integrations.semantics.aside": "仅管理此配置文件的 models.json 中的 providers.opencodex。其他提供商保持不变。应用后请完全退出并重新打开 Aside。", "codexAuth.mainAccount": "主账号", "codexAuth.logLabel": "日志标签", "codexAuth.codexApp": "Codex App", diff --git a/gui/src/pages/Integrations.tsx b/gui/src/pages/Integrations.tsx index b173771106..b7d7d86fc2 100644 --- a/gui/src/pages/Integrations.tsx +++ b/gui/src/pages/Integrations.tsx @@ -8,6 +8,7 @@ import Claude from "./Claude"; import Grok from "./Grok"; import CursorIntegrationPage from "./integrations/CursorIntegrationPage"; import IntegrationsOverview from "./integrations/IntegrationsOverview"; +import AsideProfilesPage from "./integrations/AsideProfilesPage"; import FileIntegrationPage, { type FileIntegrationClientId, } from "./integrations/FileIntegrationPage"; @@ -197,7 +198,7 @@ export default function Integrations({ apiBase, machineApiBase = apiBase, connec {definition.id === "grok" && } {definition.id === "cursor" && } {FILE_CLIENTS.has(definition.id as FileIntegrationClientId) && ( - : (null); + const [pending, setPending] = useState(null); + const pendingRef = useRef(false); + const [failure, setFailure] = useState(null); + const [profileFailures, setProfileFailures] = useState>(new Map()); + const fetchProfiles = useCallback((signal: AbortSignal) => loadAsideProfiles(apiBase, signal), [apiBase]); + const resource = useDataSurface(`aside-profiles:${apiBase}`, [apiBase], fetchProfiles, { + enabled: active, + isEmpty: value => !value.error && value.profiles.length === 0, + sessionCacheKey: `ocx.integrations.aside-profiles.v1:${apiBase}`, + }); + const data = resource.state.data; + const profiles = data?.profiles ?? []; + const loadError = resource.state.showError || Boolean(data?.error); + const busy = pending !== null || resource.state.refreshing; + const label = (profile: AsideProfileStatus) => profile.name || t("integrations.aside.profile", { id: profile.profileId }); + + const reconcileOutcomes = (outcomes: AsideProfileOutcome[]) => setProfileFailures(previous => { + const next = new Map(previous); + for (const row of outcomes) { + if (row.ok) next.delete(row.profileId); + else next.set(row.profileId, describeAsideProfileOutcome(t, row)); + } + return next; + }); + const failed = (error: unknown, profileId?: number) => { + if (error instanceof IntegrationApiError && "results" in error.body && error.body.results?.length) { + reconcileOutcomes(error.body.results); + setFailure(t("integrations.aside.partial")); + } else if (profileId !== undefined) { + setProfileFailures(previous => new Map(previous).set(profileId, describeRefusal(t, error))); + } else setFailure(describeRefusal(t, error)); + }; + + const mutate = async (enabled: boolean, profileId?: number) => { + if (pendingRef.current) return; + pendingRef.current = true; + setPending(profileId ?? "all"); + setFailure(null); + try { + const result = await toggleIntegration(apiBase, "aside", enabled, undefined, undefined, profileId); + reconcileOutcomes(result.results ?? (profileId === undefined ? profiles.map(row => row.profileId) : [profileId]) + .map(id => ({ profileId: id, ok: true }))); + } + catch (error) { failed(error, profileId); } + finally { + // Even a refused writer may have durably saved desired sync preferences. + resource.refresh(); + pendingRef.current = false; + setPending(null); + } + }; + const sync = async () => { + if (pendingRef.current) return; + pendingRef.current = true; + setPending("sync"); + setFailure(null); + try { + const result = await syncAsideProfiles(apiBase); + reconcileOutcomes(result.results); + if (!result.ok) setFailure(t("integrations.aside.partial")); + } catch (error) { failed(error); } + finally { + resource.refresh(); + pendingRef.current = false; + setPending(null); + } + }; + + if (selected) return ( +
+ + {profileFailures.has(selected.profileId) && {profileFailures.get(selected.profileId)}} + +
+ ); + + return ( +
+
+ +

{t("integrations.aside.profilesTitle")}

+
+

{t("integrations.aside.profilesHint")}

+
+ 0 && data.enabledCount < data.total)} + onClick={() => void mutate(!data?.allEnabled)} disabled={busy || loadError || profiles.length === 0} + label={t("integrations.aside.all")} showLabel /> + {t("integrations.aside.applied", { count: data?.appliedCount ?? 0, total: data?.total ?? 0 })} + +
+ {failure && {failure}} + {[...profileFailures].filter(([id]) => !profiles.some(row => row.profileId === id)).map(([id, detail]) => ( + {t("integrations.aside.profile", { id })}: {detail} + ))} + {loadError && ( + + {t("integrations.aside.loadError")} + {data?.error && {data.error}} + + + )} + {resource.state.showSkeleton && } + {!resource.state.showSkeleton && !loadError && data && profiles.length === 0 &&

{t("integrations.aside.empty")}

} + {profiles.length > 0 && ( +
+ {profiles.map(profile => { + const name = label(profile); + const applied = profile.state === "current" || profile.state === "stale"; + const needsUpdate = profile.enabled !== applied || (profile.enabled && profile.state === "stale"); + const locked = (!profile.installed || profile.state === "unsafe" || profile.state === "conflict") && !profile.enabled; + return ( +
+
+ +
+ {profile.profileId} + {profile.current && {t("integrations.aside.current")}} +
+ {needsUpdate && {t("integrations.aside.pending")}} + {profile.error && {profile.error}} + {profileFailures.has(profile.profileId) && {profileFailures.get(profile.profileId)}} +
+
+ + {needsUpdate && } + void mutate(!profile.enabled, profile.profileId)} + disabled={busy || loadError || locked} label={t("integrations.aside.toggle", { name })} /> +
+
+ ); + })} +
+ )} +
+ ); +} diff --git a/gui/src/pages/integrations/FileIntegrationPage.tsx b/gui/src/pages/integrations/FileIntegrationPage.tsx index 8bbe794954..51db75bd9f 100644 --- a/gui/src/pages/integrations/FileIntegrationPage.tsx +++ b/gui/src/pages/integrations/FileIntegrationPage.tsx @@ -78,12 +78,17 @@ export default function FileIntegrationPage({ apiBase, client, active = true, + profileId, + profileLabel, }: { apiBase: string; client: FileIntegrationClientId; active?: boolean; + profileId?: number; + profileLabel?: string; }) { const t = useT(); + const scopeKey = profileId === undefined ? client : `${client}:${profileId}`; const [pending, setPending] = useState(false); const [failure, setFailure] = useState(null); const [restoring, setRestoring] = useState(null); @@ -93,36 +98,38 @@ export default function FileIntegrationPage({ const [overwriting, setOverwriting] = useState(false); const fetchState = useCallback( - (signal: AbortSignal) => loadIntegrationState(apiBase, client, signal), - [apiBase, client], + (signal: AbortSignal) => loadIntegrationState(apiBase, client, signal, profileId), + [apiBase, client, profileId], ); const fetchHistory = useCallback( - async (signal: AbortSignal) => (await loadIntegrationJournal(apiBase, client, signal)).operations, - [apiBase, client], + async (signal: AbortSignal) => (await loadIntegrationJournal(apiBase, client, signal, profileId)).operations, + [apiBase, client, profileId], ); const stateResource = useDataSurface( - `integration-state:${apiBase}:${client}`, - [apiBase, client], + `integration-state:${apiBase}:${scopeKey}`, + [apiBase, client, profileId], fetchState, { isEmpty: () => false, enabled: active, - sessionCacheKey: `ocx.integrations.state.v1:${apiBase}:${client}`, + sessionCacheKey: `ocx.integrations.state.v1:${apiBase}:${scopeKey}`, }, ); const historyResource = useDataSurface( - `integration-journal:${apiBase}:${client}`, - [apiBase, client], + `integration-journal:${apiBase}:${scopeKey}`, + [apiBase, client, profileId], fetchHistory, { isEmpty: rows => rows.length === 0, enabled: active, - sessionCacheKey: `ocx.integrations.client-journal.v1:${apiBase}:${client}`, + sessionCacheKey: `ocx.integrations.client-journal.v1:${apiBase}:${scopeKey}`, }, ); - const status = stateResource.state.data ?? null; + const candidate = stateResource.state.data ?? null; + const status = candidate && (profileId === undefined + || (candidate.profileId === profileId && typeof candidate.enabled === "boolean")) ? candidate : null; const history = historyResource.state.data ?? []; const refresh = () => { @@ -135,11 +142,11 @@ export default function FileIntegrationPage({ setPending(true); setFailure(null); try { - await toggleIntegration(apiBase, client, enabled); - refresh(); + await toggleIntegration(apiBase, client, enabled, undefined, undefined, profileId); } catch (error) { setFailure(describeRefusal(t, error)); } finally { + refresh(); setPending(false); } }; @@ -157,10 +164,11 @@ export default function FileIntegrationPage({ if (!status) return; setFailure(null); try { - await toggleIntegration(apiBase, client, true, undefined, true); + await toggleIntegration(apiBase, client, true, undefined, true, profileId); refresh(); } catch (error) { setFailure(describeRefusal(t, error)); + refresh(); throw error; } }; @@ -174,12 +182,12 @@ export default function FileIntegrationPage({ * to remove the block; updating a stale block is a separate action with its * own button below. */ - const toggle = () => void mutate(!(status && (status.state === "current" || status.state === "stale"))); + const toggle = () => void mutate(!(status && (profileId !== undefined ? status.enabled : (status.state === "current" || status.state === "stale")))); if (!status) { return (
- {stateResource.state.kind === "failed-cold" + {stateResource.state.showError ? {t("integrations.error.load")} :

{t("common.loading")}

}
@@ -187,34 +195,42 @@ export default function FileIntegrationPage({ } const applied = status.state === "current" || status.state === "stale"; - // Conflict and unsafe are never auto-resolved: the switch is locked and the - // user is told why, because the alternative is deleting an edit we do not own. - const locked = !status.installed || status.state === "conflict" || status.state === "unsafe"; + const enabled = profileId !== undefined ? status.enabled === true : applied; + const profileUnavailable = profileId !== undefined && (stateResource.state.showError || stateResource.state.refreshing); + // A profile may stop future sync even when its file cannot be changed; the + // writer still refuses unsafe deletion and reports the actual state separately. + const locked = (!status.installed || status.state === "conflict" || status.state === "unsafe") + && !(profileId !== undefined && enabled); return (
-

{t(TAB_LABEL_KEY[client])}

+

{profileLabel ?? t(TAB_LABEL_KEY[client])}

+ {profileId !== undefined && stateResource.state.showError && ( + {t("integrations.aside.loadError")} + + + )} {/* Updating a stale block is its own action. Folding it into the switch made "off" mean "refresh", which is the opposite of what the control said it would do. */} - {status.state === "stale" && ( + {status.state === "stale" && enabled && (