diff --git a/.claude/skills/release/references/hotfix.md b/.claude/skills/release/references/hotfix.md index 34550b02..be85e95a 100644 --- a/.claude/skills/release/references/hotfix.md +++ b/.claude/skills/release/references/hotfix.md @@ -18,21 +18,28 @@ Use for critical production bugs, security vulnerabilities, or data loss issues git checkout -b hotfix/v0.6.4 v0.6.3 ``` -### 2. Implement the Fix +### 2. Implement the Fix and Update CHANGELOG Apply the fix directly on the hotfix branch. Keep it minimal — only the targeted change. +**Always update `CHANGELOG.md`** in the same commit or as a follow-up commit on the hotfix branch — not as a separate PR afterwards. The changelog entry must be present before tagging. + ```bash git add git commit -m "fix: " +# Update CHANGELOG.md, then: +git add CHANGELOG.md +git commit -m "chore: update CHANGELOG for v0.6.4" git push -u origin hotfix/v0.6.4 ``` -### 3. Wait for CI +### 3. Open a PR to Main and Wait for CI -**MANDATORY before tagging.** The CI workflow runs on `hotfix/*` branches. +**Open a PR targeting `main` before tagging.** CI runs on the PR — do not tag until it passes. ```bash +gh pr create --base main --title "fix: hotfix v0.6.4" \ + --body "Hotfix release v0.6.4. Cherry-picks onto v0.6.3." gh run list --branch hotfix/v0.6.4 --workflow=ci.yml --limit 1 gh run watch ``` @@ -75,7 +82,7 @@ gh pr create --base develop --title "fix: forward-port hotfix v0.6.4" \ Merge once CI passes. If the cherry-pick has conflicts (develop has diverged significantly), resolve them before pushing. -**Auto-generated files (appcasts):** The `update-appcast.yml` workflow updates `Docs/appcast.xml` and `Docs/appcast-beta.xml` on main automatically after each release. These changes are never automatically forward-ported. After every release — stable or hotfix — create a forward-port PR that includes the updated appcast files. +**Auto-generated files (appcasts):** The `update-appcast.yml` workflow updates `Docs/appcast.xml` and `Docs/appcast-beta.xml` on main automatically after each release. These changes are never automatically forward-ported. After every release — stable or hotfix — create a forward-port PR that includes the updated appcast files AND any CHANGELOG changes from the hotfix branch. ## What NOT to Do diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30ff1c2a..bdc82842 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -453,14 +453,6 @@ jobs: TermQ-${{ steps.version.outputs.VERSION }}.zip.sig checksums.txt - - name: Trigger appcast update - env: - GH_TOKEN: ${{ secrets.APPCAST_TOKEN }} - run: | - echo "Triggering appcast update workflow..." - gh workflow run update-appcast.yml - echo "✅ Appcast update triggered" - - name: Cleanup keychain if: always() run: | diff --git a/.github/workflows/update-appcast.yml b/.github/workflows/update-appcast.yml index ee08c7fe..e20dae3e 100644 --- a/.github/workflows/update-appcast.yml +++ b/.github/workflows/update-appcast.yml @@ -1,11 +1,15 @@ name: Update Appcast on: - release: - types: [published] + workflow_run: + workflows: ["Release"] + types: [completed] workflow_dispatch: # Allow manual trigger to regenerate appcast +# Only update appcast when the Release workflow succeeds +# (workflow_dispatch always proceeds — used for manual regeneration) + permissions: contents: write pull-requests: write @@ -14,6 +18,7 @@ jobs: update-appcast: name: Update Appcast runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' steps: - name: Checkout @@ -52,7 +57,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" BRANCH="hotfix/appcast-update" - RELEASE_TAG="${{ github.event.release.tag_name || 'manual' }}" + RELEASE_TAG="${{ github.event.workflow_run.head_branch || 'manual' }}" git checkout -B "$BRANCH" git add Docs/appcast.xml Docs/appcast-beta.xml diff --git a/Sources/TermQ/Resources/ar.lproj/Localizable.strings b/Sources/TermQ/Resources/ar.lproj/Localizable.strings index 093561bc..4db83281 100644 --- a/Sources/TermQ/Resources/ar.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ar.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "إلغاء تثبيت هذا الـ harness"; "harnesses.uninstall.alert.title %@" = "إلغاء تثبيت \"%@\"؟"; "harnesses.uninstall.alert.message" = "سيؤدي هذا إلى إزالة الـ harness من نظامك."; +"harnesses.uninstall.alert.message.local" = "سيؤدي هذا إلى إزالة harness من YNH. لن يتم حذف ملفاتك المصدر المحلية."; +"harnesses.uninstall.alert.message.untracked" = "لا يوجد لهذا harness أي سجل تثبيت في YNH. سيتم حذفه مباشرةً من القرص."; "harnesses.uninstall.alert.worktrees %ld" = "هذا الـ harness مرتبط بـ %ld مستودع(ات) عمل — ستُمسح تلك الارتباطات."; "harnesses.uninstall.alert.terminals %ld" = "%ld محطة (محطات) طرفية تُشغّل هذا الـ harness حاليًا."; "harnesses.uninstall.alert.confirm" = "إلغاء التثبيت"; diff --git a/Sources/TermQ/Resources/ca.lproj/Localizable.strings b/Sources/TermQ/Resources/ca.lproj/Localizable.strings index 58d742d2..42dfe5a4 100644 --- a/Sources/TermQ/Resources/ca.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ca.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Desinstal·la aquest harness"; "harnesses.uninstall.alert.title %@" = "Desinstal·lar \"%@\"?"; "harnesses.uninstall.alert.message" = "Això eliminarà el harness del vostre sistema."; +"harnesses.uninstall.alert.message.local" = "Això eliminarà el harness de YNH. Els vostres fitxers font locals no s'eliminaran."; +"harnesses.uninstall.alert.message.untracked" = "Aquest harness no té cap registre d'instal·lació a YNH. S'eliminarà directament del disc."; "harnesses.uninstall.alert.worktrees %ld" = "Aquest harness està vinculat a %ld arbre(s) de treball — aquestes associacions s'esboraran."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) estan executant aquest harness ara mateix."; "harnesses.uninstall.alert.confirm" = "Desinstal·la"; diff --git a/Sources/TermQ/Resources/cs.lproj/Localizable.strings b/Sources/TermQ/Resources/cs.lproj/Localizable.strings index 8f1acf9c..ecc2aa1a 100644 --- a/Sources/TermQ/Resources/cs.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/cs.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Odinstalovat tento harness"; "harnesses.uninstall.alert.title %@" = "Odinstalovat \"%@\"?"; "harnesses.uninstall.alert.message" = "Tím se harness odebere z vašeho systému."; +"harnesses.uninstall.alert.message.local" = "Tím se odstraní harness z YNH. Vaše místní zdrojové soubory nebudou smazány."; +"harnesses.uninstall.alert.message.untracked" = "Tento harness nemá žádný záznam o instalaci v YNH. Bude odstraněn přímo z disku."; "harnesses.uninstall.alert.worktrees %ld" = "Tento harness je propojen s %ld pracovním stromem (stromy) — tato propojení budou vymazána."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminál(ů) aktuálně spouští tento harness."; "harnesses.uninstall.alert.confirm" = "Odinstalovat"; diff --git a/Sources/TermQ/Resources/da.lproj/Localizable.strings b/Sources/TermQ/Resources/da.lproj/Localizable.strings index cd998299..13d46067 100644 --- a/Sources/TermQ/Resources/da.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/da.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Afinstaller denne harness"; "harnesses.uninstall.alert.title %@" = "Afinstaller \"%@\"?"; "harnesses.uninstall.alert.message" = "Dette fjerner harness fra dit system."; +"harnesses.uninstall.alert.message.local" = "Dette fjerner harness fra YNH. Dine lokale kildefiler slettes ikke."; +"harnesses.uninstall.alert.message.untracked" = "Denne harness har ingen YNH-installationspost. Den slettes direkte fra disken."; "harnesses.uninstall.alert.worktrees %ld" = "Denne harness er knyttet til %ld worktree(s) — disse tilknytninger ryddes."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(er) kører i øjeblikket denne harness."; "harnesses.uninstall.alert.confirm" = "Afinstaller"; diff --git a/Sources/TermQ/Resources/de.lproj/Localizable.strings b/Sources/TermQ/Resources/de.lproj/Localizable.strings index 195ccfc4..32b02afc 100644 --- a/Sources/TermQ/Resources/de.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/de.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Diesen Harness deinstallieren"; "harnesses.uninstall.alert.title %@" = "\"%@\" deinstallieren?"; "harnesses.uninstall.alert.message" = "Dadurch wird der Harness von Ihrem System entfernt."; +"harnesses.uninstall.alert.message.local" = "Dadurch wird der Harness aus YNH entfernt. Ihre lokalen Quelldateien werden nicht gelöscht."; +"harnesses.uninstall.alert.message.untracked" = "Dieser Harness hat keinen YNH-Installationseintrag. Er wird direkt von der Festplatte gelöscht."; "harnesses.uninstall.alert.worktrees %ld" = "Dieser Harness ist mit %ld Worktree(s) verknüpft — diese Verknüpfungen werden aufgehoben."; "harnesses.uninstall.alert.terminals %ld" = "%ld Terminal(s) führen diesen Harness gerade aus."; "harnesses.uninstall.alert.confirm" = "Deinstallieren"; diff --git a/Sources/TermQ/Resources/el.lproj/Localizable.strings b/Sources/TermQ/Resources/el.lproj/Localizable.strings index 1ccd141c..efcc7d3d 100644 --- a/Sources/TermQ/Resources/el.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/el.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Απεγκατάσταση αυτού του harness"; "harnesses.uninstall.alert.title %@" = "Απεγκατάσταση \"%@\";"; "harnesses.uninstall.alert.message" = "Αυτό θα αφαιρέσει το harness από το σύστημά σας."; +"harnesses.uninstall.alert.message.local" = "Αυτό θα αφαιρέσει το harness από το YNH. Τα τοπικά αρχεία πηγαίου κώδικα δεν θα διαγραφούν."; +"harnesses.uninstall.alert.message.untracked" = "Αυτό το harness δεν έχει εγγραφή εγκατάστασης στο YNH. Θα διαγραφεί απευθείας από τον δίσκο."; "harnesses.uninstall.alert.worktrees %ld" = "Αυτό το harness είναι συνδεδεμένο με %ld worktree(s) — αυτές οι συσχετίσεις θα διαγραφούν."; "harnesses.uninstall.alert.terminals %ld" = "%ld τερματικό(ά) εκτελεί αυτήν τη στιγμή αυτό το harness."; "harnesses.uninstall.alert.confirm" = "Απεγκατάσταση"; diff --git a/Sources/TermQ/Resources/en-AU.lproj/Localizable.strings b/Sources/TermQ/Resources/en-AU.lproj/Localizable.strings index 07ca660b..dab62510 100644 --- a/Sources/TermQ/Resources/en-AU.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/en-AU.lproj/Localizable.strings @@ -715,6 +715,8 @@ "harnesses.uninstall.help" = "Uninstall this harness"; "harnesses.uninstall.alert.title %@" = "Uninstall \"%@\"?"; "harnesses.uninstall.alert.message" = "This will remove the harness from your system."; +"harnesses.uninstall.alert.message.local" = "This will remove the harness from YNH. Your local source files will not be deleted."; +"harnesses.uninstall.alert.message.untracked" = "This harness has no YNH install record. It will be deleted directly from disk."; "harnesses.uninstall.alert.worktrees %ld" = "This harness is linked to %ld worktree(s) — those associations will be cleared."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) are currently running this harness."; "harnesses.uninstall.alert.confirm" = "Uninstall"; diff --git a/Sources/TermQ/Resources/en-GB.lproj/Localizable.strings b/Sources/TermQ/Resources/en-GB.lproj/Localizable.strings index 7cdb177d..ffa1f8e6 100644 --- a/Sources/TermQ/Resources/en-GB.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/en-GB.lproj/Localizable.strings @@ -715,6 +715,8 @@ "harnesses.uninstall.help" = "Uninstall this harness"; "harnesses.uninstall.alert.title %@" = "Uninstall \"%@\"?"; "harnesses.uninstall.alert.message" = "This will remove the harness from your system."; +"harnesses.uninstall.alert.message.local" = "This will remove the harness from YNH. Your local source files will not be deleted."; +"harnesses.uninstall.alert.message.untracked" = "This harness has no YNH install record. It will be deleted directly from disk."; "harnesses.uninstall.alert.worktrees %ld" = "This harness is linked to %ld worktree(s) — those associations will be cleared."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) are currently running this harness."; "harnesses.uninstall.alert.confirm" = "Uninstall"; diff --git a/Sources/TermQ/Resources/en.lproj/Localizable.strings b/Sources/TermQ/Resources/en.lproj/Localizable.strings index 83a5743e..695bbff7 100644 --- a/Sources/TermQ/Resources/en.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/en.lproj/Localizable.strings @@ -765,6 +765,8 @@ "harnesses.uninstall.help" = "Uninstall this harness"; "harnesses.uninstall.alert.title %@" = "Uninstall \"%@\"?"; "harnesses.uninstall.alert.message" = "This will remove the harness from your system."; +"harnesses.uninstall.alert.message.local" = "This will remove the harness from YNH. Your local source files will not be deleted."; +"harnesses.uninstall.alert.message.untracked" = "This harness has no YNH install record. It will be deleted directly from disk."; "harnesses.uninstall.alert.worktrees %ld" = "This harness is linked to %ld worktree(s) — those associations will be cleared."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) are currently running this harness."; "harnesses.uninstall.alert.confirm" = "Uninstall"; diff --git a/Sources/TermQ/Resources/es-419.lproj/Localizable.strings b/Sources/TermQ/Resources/es-419.lproj/Localizable.strings index e35acb7e..6592f399 100644 --- a/Sources/TermQ/Resources/es-419.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/es-419.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Desinstalar este harness"; "harnesses.uninstall.alert.title %@" = "¿Desinstalar \"%@\"?"; "harnesses.uninstall.alert.message" = "Esto eliminará el harness de tu sistema."; +"harnesses.uninstall.alert.message.local" = "Esto eliminará el harness de YNH. Tus archivos fuente locales no se eliminarán."; +"harnesses.uninstall.alert.message.untracked" = "Este harness no tiene ningún registro de instalación en YNH. Se eliminará directamente del disco."; "harnesses.uninstall.alert.worktrees %ld" = "Este harness está vinculado a %ld árbol(es) de trabajo — esas asociaciones se eliminarán."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(es) están ejecutando este harness actualmente."; "harnesses.uninstall.alert.confirm" = "Desinstalar"; diff --git a/Sources/TermQ/Resources/es.lproj/Localizable.strings b/Sources/TermQ/Resources/es.lproj/Localizable.strings index b84227f4..01481cb9 100644 --- a/Sources/TermQ/Resources/es.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/es.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Desinstalar este harness"; "harnesses.uninstall.alert.title %@" = "¿Desinstalar \"%@\"?"; "harnesses.uninstall.alert.message" = "Esto eliminará el harness de tu sistema."; +"harnesses.uninstall.alert.message.local" = "Esto eliminará el harness de YNH. Sus archivos fuente locales no se eliminarán."; +"harnesses.uninstall.alert.message.untracked" = "Este harness no tiene ningún registro de instalación en YNH. Se eliminará directamente del disco."; "harnesses.uninstall.alert.worktrees %ld" = "Este harness está vinculado a %ld árbol(es) de trabajo — esas asociaciones se eliminarán."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(es) están ejecutando este harness actualmente."; "harnesses.uninstall.alert.confirm" = "Desinstalar"; diff --git a/Sources/TermQ/Resources/fi.lproj/Localizable.strings b/Sources/TermQ/Resources/fi.lproj/Localizable.strings index e02bb906..2307a6e9 100644 --- a/Sources/TermQ/Resources/fi.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/fi.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "Poista tämän harnessin asennus"; "harnesses.uninstall.alert.title %@" = "Poistetaanko \"%@\"?"; "harnesses.uninstall.alert.message" = "Tämä poistaa harnessin järjestelmästäsi."; +"harnesses.uninstall.alert.message.local" = "Tämä poistaa harnessin YNH:sta. Paikallisia lähdetiedostojasi ei poisteta."; +"harnesses.uninstall.alert.message.untracked" = "Tällä harnessilla ei ole YNH-asennustietuetta. Se poistetaan suoraan levyltä."; "harnesses.uninstall.alert.worktrees %ld" = "Tämä harness on linkitetty %ld työtree(hen/hin) — nämä yhteydet poistetaan."; "harnesses.uninstall.alert.terminals %ld" = "%ld pääte(ttä) käyttää tällä hetkellä tätä harnessia."; "harnesses.uninstall.alert.confirm" = "Poista asennus"; diff --git a/Sources/TermQ/Resources/fr-CA.lproj/Localizable.strings b/Sources/TermQ/Resources/fr-CA.lproj/Localizable.strings index a8ebe335..62c14e67 100644 --- a/Sources/TermQ/Resources/fr-CA.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/fr-CA.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "Désinstaller ce harnais"; "harnesses.uninstall.alert.title %@" = "Désinstaller \"%@\" ?"; "harnesses.uninstall.alert.message" = "Le harnais sera supprimé de votre système."; +"harnesses.uninstall.alert.message.local" = "Cela supprimera le harness de YNH. Vos fichiers sources locaux ne seront pas supprimés."; +"harnesses.uninstall.alert.message.untracked" = "Ce harness n'a aucun enregistrement d'installation dans YNH. Il sera supprimé directement du disque."; "harnesses.uninstall.alert.worktrees %ld" = "Ce harnais est lié à %ld arbre(s) de travail — ces associations seront supprimées."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) exécutent actuellement ce harnais."; "harnesses.uninstall.alert.confirm" = "Désinstaller"; diff --git a/Sources/TermQ/Resources/fr.lproj/Localizable.strings b/Sources/TermQ/Resources/fr.lproj/Localizable.strings index 1d6d6208..344af5de 100644 --- a/Sources/TermQ/Resources/fr.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/fr.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "Désinstaller ce harnais"; "harnesses.uninstall.alert.title %@" = "Désinstaller \"%@\" ?"; "harnesses.uninstall.alert.message" = "Le harnais sera supprimé de votre système."; +"harnesses.uninstall.alert.message.local" = "Cela supprimera le harness de YNH. Vos fichiers sources locaux ne seront pas supprimés."; +"harnesses.uninstall.alert.message.untracked" = "Ce harness n'a aucun enregistrement d'installation dans YNH. Il sera supprimé directement du disque."; "harnesses.uninstall.alert.worktrees %ld" = "Ce harnais est lié à %ld arbre(s) de travail — ces associations seront supprimées."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) exécutent actuellement ce harnais."; "harnesses.uninstall.alert.confirm" = "Désinstaller"; diff --git a/Sources/TermQ/Resources/he.lproj/Localizable.strings b/Sources/TermQ/Resources/he.lproj/Localizable.strings index 7430ddd1..2a078450 100644 --- a/Sources/TermQ/Resources/he.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/he.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "הסר את ה-harness הזה"; "harnesses.uninstall.alert.title %@" = "להסיר את \"%@\"?"; "harnesses.uninstall.alert.message" = "פעולה זו תסיר את ה-harness מהמערכת שלך."; +"harnesses.uninstall.alert.message.local" = "פעולה זו תסיר את ה-harness מ-YNH. הקבצים המקומיים שלך לא יימחקו."; +"harnesses.uninstall.alert.message.untracked" = "ל-harness זה אין רשומת התקנה ב-YNH. הוא יימחק ישירות מהדיסק."; "harnesses.uninstall.alert.worktrees %ld" = "ה-harness הזה מקושר ל-%ld worktree(s) — הקשרים הללו יימחקו."; "harnesses.uninstall.alert.terminals %ld" = "%ld מסוף(ים) מריצים כעת את ה-harness הזה."; "harnesses.uninstall.alert.confirm" = "הסר התקנה"; diff --git a/Sources/TermQ/Resources/hi.lproj/Localizable.strings b/Sources/TermQ/Resources/hi.lproj/Localizable.strings index 46bb565d..68e24982 100644 --- a/Sources/TermQ/Resources/hi.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/hi.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "इस harness को अनइंस्टॉल करें"; "harnesses.uninstall.alert.title %@" = "\"%@\" को अनइंस्टॉल करें?"; "harnesses.uninstall.alert.message" = "यह आपके सिस्टम से harness को हटा देगा।"; +"harnesses.uninstall.alert.message.local" = "यह YNH से harness को हटा देगा। आपकी स्थानीय स्रोत फ़ाइलें हटाई नहीं जाएंगी।"; +"harnesses.uninstall.alert.message.untracked" = "इस harness का YNH में कोई इंस्टॉल रिकॉर्ड नहीं है। इसे सीधे डिस्क से हटाया जाएगा।"; "harnesses.uninstall.alert.worktrees %ld" = "यह harness %ld worktree(s) से जुड़ा है — वे संबद्धताएं साफ़ कर दी जाएंगी।"; "harnesses.uninstall.alert.terminals %ld" = "%ld टर्मिनल वर्तमान में इस harness को चला रहे हैं।"; "harnesses.uninstall.alert.confirm" = "अनइंस्टॉल करें"; diff --git a/Sources/TermQ/Resources/hr.lproj/Localizable.strings b/Sources/TermQ/Resources/hr.lproj/Localizable.strings index bde2b164..9f791af5 100644 --- a/Sources/TermQ/Resources/hr.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/hr.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "Deinstaliraj ovaj harness"; "harnesses.uninstall.alert.title %@" = "Deinstalirati \"%@\"?"; "harnesses.uninstall.alert.message" = "Time će se harness ukloniti s vašeg sustava."; +"harnesses.uninstall.alert.message.local" = "Ovo će ukloniti harness iz YNH-a. Vaše lokalne izvorne datoteke neće biti izbrisane."; +"harnesses.uninstall.alert.message.untracked" = "Ovaj harness nema zapisa o instalaciji u YNH-u. Bit će izbrisan izravno s diska."; "harnesses.uninstall.alert.worktrees %ld" = "Ovaj harness je povezan s %ld worktree(om/ovima) — te će asocijacije biti obrisane."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(a) trenutno izvodi ovaj harness."; "harnesses.uninstall.alert.confirm" = "Deinstaliraj"; diff --git a/Sources/TermQ/Resources/hu.lproj/Localizable.strings b/Sources/TermQ/Resources/hu.lproj/Localizable.strings index ff8abce8..ac4bb4c2 100644 --- a/Sources/TermQ/Resources/hu.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/hu.lproj/Localizable.strings @@ -723,6 +723,8 @@ "harnesses.uninstall.help" = "Ez a harness eltávolítása"; "harnesses.uninstall.alert.title %@" = "Eltávolítja: \"%@\"?"; "harnesses.uninstall.alert.message" = "Ez eltávolítja a harness-t a rendszeréből."; +"harnesses.uninstall.alert.message.local" = "Ez eltávolítja a harness-t a YNH-ból. A helyi forrásfájlok nem törlődnek."; +"harnesses.uninstall.alert.message.untracked" = "Ennek a harness-nek nincs YNH-telepítési bejegyzése. Közvetlenül a lemezről törlődik."; "harnesses.uninstall.alert.worktrees %ld" = "Ez a harness %ld munkafahoz van csatolva — ezek a kapcsolatok törlődnek."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminál jelenleg ezt a harness-t futtatja."; "harnesses.uninstall.alert.confirm" = "Eltávolítás"; diff --git a/Sources/TermQ/Resources/id.lproj/Localizable.strings b/Sources/TermQ/Resources/id.lproj/Localizable.strings index 0d3f39a5..ae47eb60 100644 --- a/Sources/TermQ/Resources/id.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/id.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Hapus instalasi harness ini"; "harnesses.uninstall.alert.title %@" = "Hapus instalasi \"%@\"?"; "harnesses.uninstall.alert.message" = "Ini akan menghapus harness dari sistem Anda."; +"harnesses.uninstall.alert.message.local" = "Ini akan menghapus harness dari YNH. File sumber lokal Anda tidak akan dihapus."; +"harnesses.uninstall.alert.message.untracked" = "Harness ini tidak memiliki catatan instalasi YNH. Harness akan dihapus langsung dari disk."; "harnesses.uninstall.alert.worktrees %ld" = "Harness ini terhubung ke %ld worktree — asosiasi tersebut akan dihapus."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal sedang menjalankan harness ini."; "harnesses.uninstall.alert.confirm" = "Hapus Instalasi"; diff --git a/Sources/TermQ/Resources/it.lproj/Localizable.strings b/Sources/TermQ/Resources/it.lproj/Localizable.strings index 95f26f23..db9bca84 100644 --- a/Sources/TermQ/Resources/it.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/it.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Disinstalla questo harness"; "harnesses.uninstall.alert.title %@" = "Disinstallare \"%@\"?"; "harnesses.uninstall.alert.message" = "Questo rimuoverà il harness dal sistema."; +"harnesses.uninstall.alert.message.local" = "Questa operazione rimuoverà il harness da YNH. I file sorgente locali non verranno eliminati."; +"harnesses.uninstall.alert.message.untracked" = "Questo harness non ha alcun record di installazione YNH. Verrà eliminato direttamente dal disco."; "harnesses.uninstall.alert.worktrees %ld" = "Questo harness è collegato a %ld worktree — le relative associazioni verranno cancellate."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminale/i sta attualmente eseguendo questo harness."; "harnesses.uninstall.alert.confirm" = "Disinstalla"; diff --git a/Sources/TermQ/Resources/ja.lproj/Localizable.strings b/Sources/TermQ/Resources/ja.lproj/Localizable.strings index 4934a645..0d6e56ff 100644 --- a/Sources/TermQ/Resources/ja.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ja.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "このHarnessをアンインストール"; "harnesses.uninstall.alert.title %@" = "「%@」をアンインストールしますか?"; "harnesses.uninstall.alert.message" = "Harnessがシステムから削除されます。"; +"harnesses.uninstall.alert.message.local" = "これにより YNH から harness が削除されます。ローカルのソースファイルは削除されません。"; +"harnesses.uninstall.alert.message.untracked" = "この harness には YNH のインストール記録がありません。ディスクから直接削除されます。"; "harnesses.uninstall.alert.worktrees %ld" = "このHarnessは%ld個のWorktreeにリンクされています — それらの関連付けが解除されます。"; "harnesses.uninstall.alert.terminals %ld" = "%ld個のターミナルが現在このHarnessを実行中です。"; "harnesses.uninstall.alert.confirm" = "アンインストール"; diff --git a/Sources/TermQ/Resources/ko.lproj/Localizable.strings b/Sources/TermQ/Resources/ko.lproj/Localizable.strings index ae10b68d..f4f69c85 100644 --- a/Sources/TermQ/Resources/ko.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ko.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "이 Harness 제거"; "harnesses.uninstall.alert.title %@" = "\"%@\"을(를) 제거하시겠습니까?"; "harnesses.uninstall.alert.message" = "이 Harness가 시스템에서 제거됩니다."; +"harnesses.uninstall.alert.message.local" = "YNH에서 harness가 제거됩니다. 로컬 소스 파일은 삭제되지 않습니다."; +"harnesses.uninstall.alert.message.untracked" = "이 harness는 YNH 설치 기록이 없습니다. 디스크에서 직접 삭제됩니다."; "harnesses.uninstall.alert.worktrees %ld" = "이 Harness는 %ld개의 Worktree에 연결되어 있습니다 — 해당 연결이 해제됩니다."; "harnesses.uninstall.alert.terminals %ld" = "현재 %ld개의 터미널이 이 Harness를 실행 중입니다."; "harnesses.uninstall.alert.confirm" = "제거"; diff --git a/Sources/TermQ/Resources/ms.lproj/Localizable.strings b/Sources/TermQ/Resources/ms.lproj/Localizable.strings index 2441af49..c29852d3 100644 --- a/Sources/TermQ/Resources/ms.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ms.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Nyahpasang harness ini"; "harnesses.uninstall.alert.title %@" = "Nyahpasang \"%@\"?"; "harnesses.uninstall.alert.message" = "Ini akan membuang harness daripada sistem anda."; +"harnesses.uninstall.alert.message.local" = "Ini akan mengalih keluar harness daripada YNH. Fail sumber tempatan anda tidak akan dipadam."; +"harnesses.uninstall.alert.message.untracked" = "Harness ini tidak mempunyai rekod pemasangan YNH. Ia akan dipadam terus dari cakera."; "harnesses.uninstall.alert.worktrees %ld" = "Harness ini dikaitkan dengan %ld worktree — perkaitan tersebut akan dipadamkan."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal sedang menjalankan harness ini."; "harnesses.uninstall.alert.confirm" = "Nyahpasang"; diff --git a/Sources/TermQ/Resources/nl.lproj/Localizable.strings b/Sources/TermQ/Resources/nl.lproj/Localizable.strings index f7ee4b4b..a0adaddd 100644 --- a/Sources/TermQ/Resources/nl.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/nl.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Deze harness verwijderen"; "harnesses.uninstall.alert.title %@" = "\"%@\" verwijderen?"; "harnesses.uninstall.alert.message" = "Hiermee wordt de harness van uw systeem verwijderd."; +"harnesses.uninstall.alert.message.local" = "Hierdoor wordt de harness uit YNH verwijderd. Uw lokale bronbestanden worden niet verwijderd."; +"harnesses.uninstall.alert.message.untracked" = "Deze harness heeft geen YNH-installatierecord. Hij wordt rechtstreeks van de schijf verwijderd."; "harnesses.uninstall.alert.worktrees %ld" = "Deze harness is gekoppeld aan %ld worktree(s) — die koppelingen worden gewist."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(s) voeren deze harness momenteel uit."; "harnesses.uninstall.alert.confirm" = "Verwijderen"; diff --git a/Sources/TermQ/Resources/no.lproj/Localizable.strings b/Sources/TermQ/Resources/no.lproj/Localizable.strings index d27007fe..fab19078 100644 --- a/Sources/TermQ/Resources/no.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/no.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Avinstaller denne harness"; "harnesses.uninstall.alert.title %@" = "Avinstaller \"%@\"?"; "harnesses.uninstall.alert.message" = "Dette fjerner harness fra systemet ditt."; +"harnesses.uninstall.alert.message.local" = "Dette vil fjerne harness fra YNH. Dine lokale kildefiler vil ikke bli slettet."; +"harnesses.uninstall.alert.message.untracked" = "Denne harness har ingen YNH-installasjonspost. Den slettes direkte fra disken."; "harnesses.uninstall.alert.worktrees %ld" = "Denne harness er koblet til %ld worktree(r) — disse koblingene vil bli fjernet."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(er) kjører denne harness for øyeblikket."; "harnesses.uninstall.alert.confirm" = "Avinstaller"; diff --git a/Sources/TermQ/Resources/pl.lproj/Localizable.strings b/Sources/TermQ/Resources/pl.lproj/Localizable.strings index b7f7dfa5..ded51ce2 100644 --- a/Sources/TermQ/Resources/pl.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/pl.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Odinstaluj ten harness"; "harnesses.uninstall.alert.title %@" = "Odinstalować \"%@\"?"; "harnesses.uninstall.alert.message" = "Spowoduje to usunięcie harness z systemu."; +"harnesses.uninstall.alert.message.local" = "Spowoduje to usunięcie harness z YNH. Twoje lokalne pliki źródłowe nie zostaną usunięte."; +"harnesses.uninstall.alert.message.untracked" = "Ten harness nie ma rekordu instalacji YNH. Zostanie usunięty bezpośrednio z dysku."; "harnesses.uninstall.alert.worktrees %ld" = "Ten harness jest powiązany z %ld drzewem/drzewami roboczymi — te powiązania zostaną usunięte."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(i) aktualnie uruchamia ten harness."; "harnesses.uninstall.alert.confirm" = "Odinstaluj"; diff --git a/Sources/TermQ/Resources/pt-PT.lproj/Localizable.strings b/Sources/TermQ/Resources/pt-PT.lproj/Localizable.strings index 73692c68..40321717 100644 --- a/Sources/TermQ/Resources/pt-PT.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/pt-PT.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Desinstalar este harness"; "harnesses.uninstall.alert.title %@" = "Desinstalar \"%@\"?"; "harnesses.uninstall.alert.message" = "Isto irá remover o harness do seu sistema."; +"harnesses.uninstall.alert.message.local" = "Isto irá remover o harness do YNH. Os seus ficheiros de origem locais não serão eliminados."; +"harnesses.uninstall.alert.message.untracked" = "Este harness não tem registo de instalação no YNH. Será eliminado diretamente do disco."; "harnesses.uninstall.alert.worktrees %ld" = "Este harness está ligado a %ld worktree(s) — essas associações serão eliminadas."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(is) estão actualmente a executar este harness."; "harnesses.uninstall.alert.confirm" = "Desinstalar"; diff --git a/Sources/TermQ/Resources/pt.lproj/Localizable.strings b/Sources/TermQ/Resources/pt.lproj/Localizable.strings index 2d7042c9..25bc22a5 100644 --- a/Sources/TermQ/Resources/pt.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/pt.lproj/Localizable.strings @@ -722,6 +722,8 @@ "harnesses.uninstall.help" = "Desinstalar este harness"; "harnesses.uninstall.alert.title %@" = "Desinstalar \"%@\"?"; "harnesses.uninstall.alert.message" = "Isso removerá o harness do seu sistema."; +"harnesses.uninstall.alert.message.local" = "Isto irá remover o harness do YNH. Os seus ficheiros de origem locais não serão eliminados."; +"harnesses.uninstall.alert.message.untracked" = "Este harness não tem registro de instalação no YNH. Ele será excluído diretamente do disco."; "harnesses.uninstall.alert.worktrees %ld" = "Este harness está vinculado a %ld worktree(s) — essas associações serão removidas."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(is) estão executando este harness no momento."; "harnesses.uninstall.alert.confirm" = "Desinstalar"; diff --git a/Sources/TermQ/Resources/ro.lproj/Localizable.strings b/Sources/TermQ/Resources/ro.lproj/Localizable.strings index 54447317..44a30f10 100644 --- a/Sources/TermQ/Resources/ro.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ro.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Dezinstalați acest harness"; "harnesses.uninstall.alert.title %@" = "Dezinstalați \"%@\"?"; "harnesses.uninstall.alert.message" = "Aceasta va elimina harness-ul din sistemul dvs."; +"harnesses.uninstall.alert.message.local" = "Aceasta va elimina harness din YNH. Fișierele sursă locale nu vor fi șterse."; +"harnesses.uninstall.alert.message.untracked" = "Acest harness nu are niciun înregistrare de instalare YNH. Va fi șters direct de pe disc."; "harnesses.uninstall.alert.worktrees %ld" = "Acest harness este legat de %ld worktree(uri) — aceste asocieri vor fi șterse."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(e) rulează în prezent acest harness."; "harnesses.uninstall.alert.confirm" = "Dezinstalare"; diff --git a/Sources/TermQ/Resources/ru.lproj/Localizable.strings b/Sources/TermQ/Resources/ru.lproj/Localizable.strings index 8df75c3f..98703257 100644 --- a/Sources/TermQ/Resources/ru.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/ru.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Удалить этот harness"; "harnesses.uninstall.alert.title %@" = "Удалить \"%@\"?"; "harnesses.uninstall.alert.message" = "Это удалит harness с вашей системы."; +"harnesses.uninstall.alert.message.local" = "Это удалит harness из YNH. Ваши локальные исходные файлы удалены не будут."; +"harnesses.uninstall.alert.message.untracked" = "У этого harness нет записи об установке в YNH. Он будет удалён непосредственно с диска."; "harnesses.uninstall.alert.worktrees %ld" = "Этот harness связан с %ld рабочим деревом (деревьями) — эти связи будут удалены."; "harnesses.uninstall.alert.terminals %ld" = "%ld терминал(а/ов) в данный момент запускает этот harness."; "harnesses.uninstall.alert.confirm" = "Удалить"; diff --git a/Sources/TermQ/Resources/sk.lproj/Localizable.strings b/Sources/TermQ/Resources/sk.lproj/Localizable.strings index b3902801..322579f1 100644 --- a/Sources/TermQ/Resources/sk.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/sk.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "Odinštalovať tento harness"; "harnesses.uninstall.alert.title %@" = "Odinštalovať \"%@\"?"; "harnesses.uninstall.alert.message" = "Týmto sa harness odstráni z vášho systému."; +"harnesses.uninstall.alert.message.local" = "Týmto sa harness odstráni z YNH. Vaše lokálne zdrojové súbory nebudú vymazané."; +"harnesses.uninstall.alert.message.untracked" = "Tento harness nemá žiadny záznam o inštalácii v YNH. Bude odstránený priamo z disku."; "harnesses.uninstall.alert.worktrees %ld" = "Tento harness je prepojený s %ld pracovným stromom (stromami) — tieto prepojenia budú vymazané."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminál(ov) momentálne spúšťa tento harness."; "harnesses.uninstall.alert.confirm" = "Odinštalovať"; diff --git a/Sources/TermQ/Resources/sl.lproj/Localizable.strings b/Sources/TermQ/Resources/sl.lproj/Localizable.strings index 09e03494..e56c6ea9 100644 --- a/Sources/TermQ/Resources/sl.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/sl.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "Odstrani ta harness"; "harnesses.uninstall.alert.title %@" = "Odstraniti \"%@\"?"; "harnesses.uninstall.alert.message" = "S tem boste odstranili harness iz vašega sistema."; +"harnesses.uninstall.alert.message.local" = "S tem boste odstranili harness iz YNH. Vaše lokalne izvorne datoteke ne bodo izbrisane."; +"harnesses.uninstall.alert.message.untracked" = "Ta harness nima zapisa o namestitvi v YNH. Izbrisal se bo neposredno z diska."; "harnesses.uninstall.alert.worktrees %ld" = "Ta harness je povezan z %ld delovnim drevesom (drevesi) — te povezave bodo počiščene."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(ov) trenutno poganja ta harness."; "harnesses.uninstall.alert.confirm" = "Odstrani"; diff --git a/Sources/TermQ/Resources/sv.lproj/Localizable.strings b/Sources/TermQ/Resources/sv.lproj/Localizable.strings index 8ca9e29a..340c81f0 100644 --- a/Sources/TermQ/Resources/sv.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/sv.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "Avinstallera denna harness"; "harnesses.uninstall.alert.title %@" = "Avinstallera \"%@\"?"; "harnesses.uninstall.alert.message" = "Detta tar bort harness från ditt system."; +"harnesses.uninstall.alert.message.local" = "Det här tar bort harness från YNH. Dina lokala källfiler tas inte bort."; +"harnesses.uninstall.alert.message.untracked" = "Den här harness har inget YNH-installationspost. Den kommer att raderas direkt från disken."; "harnesses.uninstall.alert.worktrees %ld" = "Denna harness är länkad till %ld worktree(s) — dessa kopplingar kommer att rensas."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal(er) kör för närvarande denna harness."; "harnesses.uninstall.alert.confirm" = "Avinstallera"; diff --git a/Sources/TermQ/Resources/th.lproj/Localizable.strings b/Sources/TermQ/Resources/th.lproj/Localizable.strings index 599c4736..5dbf7aa7 100644 --- a/Sources/TermQ/Resources/th.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/th.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "ถอนการติดตั้ง harness นี้"; "harnesses.uninstall.alert.title %@" = "ถอนการติดตั้ง \"%@\" หรือไม่?"; "harnesses.uninstall.alert.message" = "การดำเนินการนี้จะลบ harness ออกจากระบบของคุณ"; +"harnesses.uninstall.alert.message.local" = "การดำเนินการนี้จะลบ harness ออกจาก YNH ไฟล์ต้นฉบับในเครื่องของคุณจะไม่ถูกลบ"; +"harnesses.uninstall.alert.message.untracked" = "harness นี้ไม่มีบันทึกการติดตั้งใน YNH จะถูกลบออกจากดิสก์โดยตรง"; "harnesses.uninstall.alert.worktrees %ld" = "harness นี้เชื่อมโยงกับ %ld worktree — การเชื่อมโยงเหล่านั้นจะถูกล้าง"; "harnesses.uninstall.alert.terminals %ld" = "ขณะนี้มี %ld เทอร์มินัลที่กำลังรัน harness นี้"; "harnesses.uninstall.alert.confirm" = "ถอนการติดตั้ง"; diff --git a/Sources/TermQ/Resources/tr.lproj/Localizable.strings b/Sources/TermQ/Resources/tr.lproj/Localizable.strings index 879abeac..33b682c6 100644 --- a/Sources/TermQ/Resources/tr.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/tr.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "Bu harness'i kaldır"; "harnesses.uninstall.alert.title %@" = "\"%@\" kaldırılsın mı?"; "harnesses.uninstall.alert.message" = "Bu, harness'i sisteminizden kaldıracak."; +"harnesses.uninstall.alert.message.local" = "Bu işlem harness'i YNH'dan kaldıracak. Yerel kaynak dosyalarınız silinmeyecek."; +"harnesses.uninstall.alert.message.untracked" = "Bu harness'in YNH'da kurulum kaydı yok. Doğrudan diskten silinecek."; "harnesses.uninstall.alert.worktrees %ld" = "Bu harness %ld worktree ile bağlantılı — bu ilişkilendirmeler temizlenecek."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal şu anda bu harness'i çalıştırıyor."; "harnesses.uninstall.alert.confirm" = "Kaldır"; diff --git a/Sources/TermQ/Resources/uk.lproj/Localizable.strings b/Sources/TermQ/Resources/uk.lproj/Localizable.strings index 72f4a7b9..6a9b8a1d 100644 --- a/Sources/TermQ/Resources/uk.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/uk.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "Видалити цей harness"; "harnesses.uninstall.alert.title %@" = "Видалити \"%@\"?"; "harnesses.uninstall.alert.message" = "Це видалить harness з вашої системи."; +"harnesses.uninstall.alert.message.local" = "Це видалить harness із YNH. Ваші локальні вихідні файли не буде видалено."; +"harnesses.uninstall.alert.message.untracked" = "Цей harness не має запису про встановлення в YNH. Його буде видалено безпосередньо з диска."; "harnesses.uninstall.alert.worktrees %ld" = "Цей harness пов'язаний з %ld робочим деревом (деревами) — ці зв'язки буде очищено."; "harnesses.uninstall.alert.terminals %ld" = "%ld термінал(и) зараз виконують цей harness."; "harnesses.uninstall.alert.confirm" = "Видалити"; diff --git a/Sources/TermQ/Resources/vi.lproj/Localizable.strings b/Sources/TermQ/Resources/vi.lproj/Localizable.strings index 8956cff6..83d3ba4c 100644 --- a/Sources/TermQ/Resources/vi.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/vi.lproj/Localizable.strings @@ -724,6 +724,8 @@ "harnesses.uninstall.help" = "Gỡ cài đặt harness này"; "harnesses.uninstall.alert.title %@" = "Gỡ cài đặt \"%@\"?"; "harnesses.uninstall.alert.message" = "Thao tác này sẽ xóa harness khỏi hệ thống của bạn."; +"harnesses.uninstall.alert.message.local" = "Thao tác này sẽ xóa harness khỏi YNH. Các tệp nguồn cục bộ của bạn sẽ không bị xóa."; +"harnesses.uninstall.alert.message.untracked" = "Harness này không có bản ghi cài đặt trong YNH. Nó sẽ bị xóa trực tiếp khỏi ổ đĩa."; "harnesses.uninstall.alert.worktrees %ld" = "Harness này được liên kết với %ld worktree — các liên kết đó sẽ bị xóa."; "harnesses.uninstall.alert.terminals %ld" = "%ld terminal đang chạy harness này."; "harnesses.uninstall.alert.confirm" = "Gỡ cài đặt"; diff --git a/Sources/TermQ/Resources/zh-HK.lproj/Localizable.strings b/Sources/TermQ/Resources/zh-HK.lproj/Localizable.strings index c9084b84..a07ccbda 100644 --- a/Sources/TermQ/Resources/zh-HK.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/zh-HK.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "解除安裝此 harness"; "harnesses.uninstall.alert.title %@" = "解除安裝「%@」?"; "harnesses.uninstall.alert.message" = "這將從您的系統中移除該 harness。"; +"harnesses.uninstall.alert.message.local" = "呢個操作會喺 YNH 移除該 harness。您嘅本地原始碼檔案唔會被刪除。"; +"harnesses.uninstall.alert.message.untracked" = "呢個 harness 喺 YNH 冇安裝記錄,將會直接從磁碟刪除。"; "harnesses.uninstall.alert.worktrees %ld" = "此 harness 已連結 %ld 個工作樹——這些關聯將被清除。"; "harnesses.uninstall.alert.terminals %ld" = "目前有 %ld 個終端機正在執行此 harness。"; "harnesses.uninstall.alert.confirm" = "解除安裝"; diff --git a/Sources/TermQ/Resources/zh-Hans.lproj/Localizable.strings b/Sources/TermQ/Resources/zh-Hans.lproj/Localizable.strings index a2269fad..991c0718 100644 --- a/Sources/TermQ/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/zh-Hans.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "卸载此 harness"; "harnesses.uninstall.alert.title %@" = "卸载\"%@\"?"; "harnesses.uninstall.alert.message" = "这将从您的系统中移除该 harness。"; +"harnesses.uninstall.alert.message.local" = "这将从 YNH 中移除该 harness。您的本地源文件不会被删除。"; +"harnesses.uninstall.alert.message.untracked" = "此 harness 在 YNH 中没有安装记录,将直接从磁盘删除。"; "harnesses.uninstall.alert.worktrees %ld" = "此 harness 已关联 %ld 个工作树——这些关联将被清除。"; "harnesses.uninstall.alert.terminals %ld" = "当前有 %ld 个终端正在运行此 harness。"; "harnesses.uninstall.alert.confirm" = "卸载"; diff --git a/Sources/TermQ/Resources/zh-Hant.lproj/Localizable.strings b/Sources/TermQ/Resources/zh-Hant.lproj/Localizable.strings index 9d0af79d..7b5617a7 100644 --- a/Sources/TermQ/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/TermQ/Resources/zh-Hant.lproj/Localizable.strings @@ -717,6 +717,8 @@ "harnesses.uninstall.help" = "移除此 harness 的安裝"; "harnesses.uninstall.alert.title %@" = "移除安裝「%@」?"; "harnesses.uninstall.alert.message" = "這將從您的系統中移除該 harness。"; +"harnesses.uninstall.alert.message.local" = "這將從 YNH 中移除該 harness。您的本機原始碼檔案不會被刪除。"; +"harnesses.uninstall.alert.message.untracked" = "此 harness 在 YNH 中沒有安裝記錄,將直接從磁碟中刪除。"; "harnesses.uninstall.alert.worktrees %ld" = "此 harness 已連結 %ld 個工作樹——這些關聯將被清除。"; "harnesses.uninstall.alert.terminals %ld" = "目前有 %ld 個終端機正在執行此 harness。"; "harnesses.uninstall.alert.confirm" = "移除安裝"; diff --git a/Sources/TermQ/Utilities/Strings+Sidebar.swift b/Sources/TermQ/Utilities/Strings+Sidebar.swift index 914d7bdf..f22626f6 100644 --- a/Sources/TermQ/Utilities/Strings+Sidebar.swift +++ b/Sources/TermQ/Utilities/Strings+Sidebar.swift @@ -1,5 +1,6 @@ import Foundation import SwiftUI +import TermQShared // MARK: - Sidebar @@ -272,6 +273,17 @@ extension Strings { String(format: localized("harnesses.uninstall.alert.title %@"), name) } static var uninstallAlertMessage: String { localized("harnesses.uninstall.alert.message") } + static var uninstallAlertMessageLocal: String { localized("harnesses.uninstall.alert.message.local") } + static var uninstallAlertMessageUntracked: String { localized("harnesses.uninstall.alert.message.untracked") } + static func uninstallBaseMessage(for harness: Harness) -> String { + if harness.installedFrom == nil { + return uninstallAlertMessageUntracked + } else if harness.installedFrom?.sourceType == "local" { + return uninstallAlertMessageLocal + } else { + return uninstallAlertMessage + } + } static func uninstallAlertWorktrees(_ count: Int) -> String { String(format: localized("harnesses.uninstall.alert.worktrees %ld"), count) } diff --git a/Sources/TermQ/Views/ContentView.swift b/Sources/TermQ/Views/ContentView.swift index 22dc002d..13007a6b 100644 --- a/Sources/TermQ/Views/ContentView.swift +++ b/Sources/TermQ/Views/ContentView.swift @@ -782,8 +782,22 @@ extension ContentView { viewModel.selectedCard = card } - /// Uninstall a harness in a transient terminal; clears associations when the shell exits. + /// Uninstall a harness. Harnesses with no YNH install record are deleted directly + /// from the filesystem; YNH-managed harnesses use a transient terminal and clear + /// associations when the shell exits. func uninstallHarness(name: String) { + let harness = harnessRepo.harnesses.first(where: { $0.name == name }) + + // Harnesses with no YNH install record can't be uninstalled via `ynh uninstall`. + // Delete them directly and clean up associations. + if let harness, harness.installedFrom == nil { + try? FileManager.default.removeItem(at: URL(fileURLWithPath: harness.path)) + YNHPersistence.shared.removeAllAssociations(for: name) + harnessRepo.selectedHarnessName = nil + Task { await harnessRepo.refresh() } + return + } + guard case .ready(let ynhPath, _, _) = ynhDetector.status else { return } let column: Column if let current = viewModel.selectedCard, diff --git a/Sources/TermQ/Views/HarnessDetailView.swift b/Sources/TermQ/Views/HarnessDetailView.swift index 0233e228..d281e1b0 100644 --- a/Sources/TermQ/Views/HarnessDetailView.swift +++ b/Sources/TermQ/Views/HarnessDetailView.swift @@ -460,7 +460,7 @@ extension HarnessDetailView { } fileprivate var uninstallAlertMessage: String { - var parts = [Strings.Harnesses.uninstallAlertMessage] + var parts = [Strings.Harnesses.uninstallBaseMessage(for: harness)] let linkedCount = ynhPersistence.worktrees(for: harness.name).count if linkedCount > 0 { parts.append(Strings.Harnesses.uninstallAlertWorktrees(linkedCount)) diff --git a/Sources/TermQ/Views/Sidebar/HarnessesSidebarTab.swift b/Sources/TermQ/Views/Sidebar/HarnessesSidebarTab.swift index 3d5b0b4a..5daa6e76 100644 --- a/Sources/TermQ/Views/Sidebar/HarnessesSidebarTab.swift +++ b/Sources/TermQ/Views/Sidebar/HarnessesSidebarTab.swift @@ -194,7 +194,7 @@ struct HarnessesSidebarTab: View { Text( linked > 0 ? Strings.Harnesses.uninstallAlertWorktrees(linked) - : Strings.Harnesses.uninstallAlertMessage + : Strings.Harnesses.uninstallBaseMessage(for: harness) ) } } @@ -574,7 +574,6 @@ extension HarnessesSidebarTab { fileprivate func performDeleteLocalHarness(_ harness: Harness) { onUninstall?(harness.name) - try? FileManager.default.removeItem(at: URL(fileURLWithPath: harness.path)) } private func revealLocalGroupInFinder(_ group: HarnessGroup) { diff --git a/Tests/TermQSharedTests/HarnessModelTests.swift b/Tests/TermQSharedTests/HarnessModelTests.swift index 4e1ae94a..0d9169fc 100644 --- a/Tests/TermQSharedTests/HarnessModelTests.swift +++ b/Tests/TermQSharedTests/HarnessModelTests.swift @@ -199,6 +199,120 @@ final class HarnessModelTests: XCTestCase { XCTAssertNil(h.description) } + // MARK: - Harness uninstall provenance + + /// A harness with no install record (e.g. a locally-authored harness that was never + /// `ynh install`ed) must have `installedFrom == nil`. The UI uses this to skip + /// `ynh uninstall` and delete the directory directly. + func testHarness_noInstallRecord_hasNilProvenance() throws { + let json = """ + { + "name": "github-tester", + "version": "0.1.0", + "default_vendor": "claude", + "path": "/Users/dev/harnesses/github-tester", + "installed_from": null, + "artifacts": {"skills": 0, "agents": 0, "rules": 0, "commands": 0}, + "includes": [], + "delegates_to": [] + } + """ + let h = try JSONDecoder().decode(Harness.self, from: json.data(using: .utf8)!) + XCTAssertNil(h.installedFrom) + } + + /// A harness installed via `ynh install ./local-path` has `source_type == "local"`. + /// The UI routes these through `ynh uninstall`, which succeeds because ynh has an install record. + func testHarness_locallyInstalledViaYNH_hasLocalSourceType() throws { + let json = """ + { + "name": "assistants-dev", + "version": "0.1.0", + "default_vendor": "claude", + "path": "/Users/dev/.ynh/harnesses/assistants-dev", + "installed_from": { + "source_type": "local", + "source": "/Users/dev/harnesses/assistants-dev", + "path": null, + "registry_name": null, + "installed_at": "2026-01-01T00:00:00Z" + }, + "artifacts": {"skills": 1, "agents": 0, "rules": 0, "commands": 0}, + "includes": [], + "delegates_to": [] + } + """ + let h = try JSONDecoder().decode(Harness.self, from: json.data(using: .utf8)!) + XCTAssertNotNil(h.installedFrom) + XCTAssertEqual(h.installedFrom?.sourceType, "local") + } + + /// A harness with no install record uninstalled from HarnessDetailView shows the untracked + /// alert message, not the generic or local-specific one. + func testHarness_noInstallRecord_isDistinctFromLocalSourceType() throws { + let nilProvenanceJSON = """ + { + "name": "untracked", + "version": "0.1.0", + "default_vendor": "claude", + "path": "/Users/dev/harnesses/untracked", + "installed_from": null, + "artifacts": {"skills": 0, "agents": 0, "rules": 0, "commands": 0}, + "includes": [], + "delegates_to": [] + } + """ + let localJSON = """ + { + "name": "assistants-dev", + "version": "0.1.0", + "default_vendor": "claude", + "path": "/Users/dev/.ynh/harnesses/assistants-dev", + "installed_from": { + "source_type": "local", + "source": "/Users/dev/harnesses/assistants-dev", + "path": null, + "registry_name": null, + "installed_at": "2026-01-01T00:00:00Z" + }, + "artifacts": {"skills": 0, "agents": 0, "rules": 0, "commands": 0}, + "includes": [], + "delegates_to": [] + } + """ + let untracked = try JSONDecoder().decode(Harness.self, from: nilProvenanceJSON.data(using: .utf8)!) + let local = try JSONDecoder().decode(Harness.self, from: localJSON.data(using: .utf8)!) + XCTAssertNil(untracked.installedFrom) + XCTAssertNotNil(local.installedFrom) + XCTAssertEqual(local.installedFrom?.sourceType, "local") + XCTAssertNotEqual(untracked.installedFrom?.sourceType, local.installedFrom?.sourceType) + } + + /// A registry harness has `source_type == "registry"` and is always uninstalled via ynh. + func testHarness_registryInstalled_hasRegistrySourceType() throws { + let json = """ + { + "name": "david", + "version": "0.1.0", + "default_vendor": "claude", + "path": "/Users/dev/.ynh/harnesses/david", + "installed_from": { + "source_type": "registry", + "source": "https://github.com/eyelock/assistants", + "path": null, + "registry_name": "eyelock-assistants", + "installed_at": "2026-01-01T00:00:00Z" + }, + "artifacts": {"skills": 2, "agents": 1, "rules": 0, "commands": 0}, + "includes": [], + "delegates_to": [] + } + """ + let h = try JSONDecoder().decode(Harness.self, from: json.data(using: .utf8)!) + XCTAssertNotNil(h.installedFrom) + XCTAssertEqual(h.installedFrom?.sourceType, "registry") + } + // MARK: - JSONFragment func testJSONFragment_decodesObject() throws { diff --git a/Tests/TermQTests/HarnessRepositoryTests.swift b/Tests/TermQTests/HarnessRepositoryTests.swift index c2605e88..838c3578 100644 --- a/Tests/TermQTests/HarnessRepositoryTests.swift +++ b/Tests/TermQTests/HarnessRepositoryTests.swift @@ -173,3 +173,57 @@ final class HarnessRepositoryInvalidationTests: XCTestCase { XCTAssertNil(repo.selectedDetail) } } + +// MARK: - Strings.Harnesses.uninstallBaseMessage + +@MainActor +final class HarnessUninstallMessageTests: XCTestCase { + + private func makeHarness(sourceType: String?) -> Harness { + let installedFrom: HarnessProvenance? = sourceType.map { type in + try! JSONDecoder().decode( + HarnessProvenance.self, + from: """ + {"source_type":"\(type)","source":"/tmp/h","path":null,"registry_name":null,"installed_at":"2026-01-01"} + """.data(using: .utf8)! + ) + } + return Harness( + name: "h", version: "1", defaultVendor: "claude", path: "/tmp/h", + installedFrom: installedFrom, + artifacts: HarnessArtifactCounts(skills: 0, agents: 0, rules: 0, commands: 0) + ) + } + + func test_untrackedHarness_usesUntrackedMessage() { + let harness = makeHarness(sourceType: nil) + XCTAssertEqual( + Strings.Harnesses.uninstallBaseMessage(for: harness), + Strings.Harnesses.uninstallAlertMessageUntracked + ) + } + + func test_localInstalledHarness_usesLocalMessage() { + let harness = makeHarness(sourceType: "local") + XCTAssertEqual( + Strings.Harnesses.uninstallBaseMessage(for: harness), + Strings.Harnesses.uninstallAlertMessageLocal + ) + } + + func test_registryHarness_usesGenericMessage() { + let harness = makeHarness(sourceType: "registry") + XCTAssertEqual( + Strings.Harnesses.uninstallBaseMessage(for: harness), + Strings.Harnesses.uninstallAlertMessage + ) + } + + func test_gitHarness_usesGenericMessage() { + let harness = makeHarness(sourceType: "git") + XCTAssertEqual( + Strings.Harnesses.uninstallBaseMessage(for: harness), + Strings.Harnesses.uninstallAlertMessage + ) + } +} diff --git a/scripts/generate-appcast.sh b/scripts/generate-appcast.sh index 39e8d93d..62d2795c 100755 --- a/scripts/generate-appcast.sh +++ b/scripts/generate-appcast.sh @@ -48,26 +48,54 @@ check_dependencies() { fetch_releases() { log_info "Fetching releases from GitHub..." - local api_url="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases" - local tmpfile - tmpfile=$(mktemp) - - # Fetch to temp file and sanitize JSON (remove control characters that break jq) - if ! curl -sS "$api_url" 2>/dev/null | tr -d '\000-\011\013-\037' > "$tmpfile"; then - log_error "Failed to fetch releases from $api_url" - rm -f "$tmpfile" - exit 1 + local base_url="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases" + local all_releases="[]" + local page=1 + + # Use GH_TOKEN if available — authenticated requests bypass the API cache so a + # freshly published release is visible without waiting for cache expiry + local auth_header=() + if [ -n "${GH_TOKEN:-}" ]; then + auth_header=(-H "Authorization: Bearer $GH_TOKEN") fi - # Validate JSON - if ! jq empty "$tmpfile" 2>/dev/null; then - log_error "Invalid JSON response from GitHub API" + while true; do + local tmpfile + tmpfile=$(mktemp) + local url="${base_url}?per_page=100&page=${page}" + + if ! curl -sS "${auth_header[@]}" "$url" 2>/dev/null | tr -d '\000-\011\013-\037' > "$tmpfile"; then + log_error "Failed to fetch releases page $page" + rm -f "$tmpfile" + exit 1 + fi + + if ! jq empty "$tmpfile" 2>/dev/null; then + log_error "Invalid JSON from GitHub API (page $page)" + rm -f "$tmpfile" + exit 1 + fi + + local count + count=$(jq 'length' "$tmpfile") + + if [[ "$count" -eq 0 ]]; then + rm -f "$tmpfile" + break + fi + + local page_data + page_data=$(cat "$tmpfile") rm -f "$tmpfile" - exit 1 - fi - cat "$tmpfile" - rm -f "$tmpfile" + all_releases=$(printf '%s\n%s' "$all_releases" "$page_data" | jq -s 'add') + + [[ "$count" -lt 100 ]] && break + page=$((page + 1)) + done + + log_info "Found $(echo "$all_releases" | jq 'length') release(s)" + echo "$all_releases" } # Convert GitHub release date to RFC 2822 format