chore(typo): retire tous les tirets cadratins/demi-cadratins + garde CI
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s
Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts). Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
This commit is contained in:
@@ -157,7 +157,7 @@ watch(open, async (v) => {
|
||||
}
|
||||
});
|
||||
|
||||
// raccourci global ⌘K / Ctrl+K — en phase de capture pour passer devant xterm (SessionView).
|
||||
// raccourci global ⌘K / Ctrl+K : en phase de capture pour passer devant xterm (SessionView).
|
||||
function onKey(e: KeyboardEvent): void {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<script setup lang="ts">
|
||||
// P4-A : répondre à un dialogue Claude sans ouvrir le terminal. Le composant gère sa
|
||||
// propre attache interactive LÉGÈRE (sink no-op, sans xterm), créée à la demande au
|
||||
// premier clic et libérée au démontage — c'est ce qui réalise la réponse « mobile ».
|
||||
// premier clic et libérée au démontage : c'est ce qui réalise la réponse « mobile ».
|
||||
import { computed, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { SessionSummary } from '@arboretum/shared';
|
||||
|
||||
@@ -65,7 +65,7 @@ onMounted(async () => {
|
||||
try {
|
||||
const { WebglAddon } = await import('@xterm/addon-webgl');
|
||||
const webgl = new WebglAddon();
|
||||
// perte de contexte GPU : on dispose (retour au renderer DOM) PUIS on repeint — sinon l'écran
|
||||
// perte de contexte GPU : on dispose (retour au renderer DOM) PUIS on repeint, sinon l'écran
|
||||
// reste figé sur le dernier rendu WebGL jusqu'à la prochaine interaction (« texte qui ne s'affiche
|
||||
// que si ça bouge »).
|
||||
webgl.onContextLoss(() => {
|
||||
@@ -132,7 +132,7 @@ onMounted(async () => {
|
||||
resizeObserver = new ResizeObserver(refit);
|
||||
resizeObserver.observe(container.value);
|
||||
// Re-révélation RÉELLE du conteneur : cellule de grille démontée/remontée (maximize↔grille), scroll
|
||||
// hors/dans le viewport, ou navigation SPA — autant de cas que `visibilitychange` (onglet only) ne
|
||||
// hors/dans le viewport, ou navigation SPA : autant de cas que `visibilitychange` (onglet only) ne
|
||||
// couvre PAS. Au retour visible, on repeint (le rendu était gelé hors-écran) ET on flushe l'ACK pour
|
||||
// sortir d'une éventuelle pause serveur (le callback de xterm.write était throttlé tant que caché).
|
||||
intersectionObserver = new IntersectionObserver((entries) => {
|
||||
|
||||
@@ -191,7 +191,7 @@ async function onStart(): Promise<void> {
|
||||
}
|
||||
|
||||
const branchLabel = computed(() =>
|
||||
props.worktree.branch ?? (props.worktree.detached ? `${t('worktrees.detached')} ${props.worktree.head.slice(0, 7)}` : '—'),
|
||||
props.worktree.branch ?? (props.worktree.detached ? `${t('worktrees.detached')} ${props.worktree.head.slice(0, 7)}` : '·'),
|
||||
);
|
||||
|
||||
// --- actions git : commit / push / promotion en principal ---
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// État vide soigné (icône + titre + indice + CTA) — remplace les <p text-zinc-500> plats.
|
||||
// État vide soigné (icône + titre + indice + CTA) : remplace les <p text-zinc-500> plats.
|
||||
import type { Component } from 'vue';
|
||||
|
||||
defineProps<{ icon?: Component; title: string; hint?: string }>();
|
||||
|
||||
@@ -47,7 +47,7 @@ const error = ref<string | null>(null);
|
||||
const children = ref<FsEntry[]>([]);
|
||||
let loaded = false;
|
||||
|
||||
// chemin relatif au worktree (POSIX) — clé utilisée par l'API fichiers / diff.
|
||||
// chemin relatif au worktree (POSIX) : clé utilisée par l'API fichiers / diff.
|
||||
const relPath = computed(() => (props.entry.path.startsWith(props.wt) ? props.entry.path.slice(props.wt.length + 1) : props.entry.name));
|
||||
const isActive = computed(() => props.entry.isFile && props.active === relPath.value);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ref, watch, onUnmounted, getCurrentInstance, type Ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Renvoie un miroir débouncé d'un ref source. Le ref source reste instantané (idéal pour un
|
||||
* v-model d'input) ; le miroir ne se met à jour qu'après `delay` ms de stabilité — c'est lui
|
||||
* v-model d'input) ; le miroir ne se met à jour qu'après `delay` ms de stabilité : c'est lui
|
||||
* que le pipeline de tri/filtre consomme, pour ne pas recalculer à chaque frappe.
|
||||
*/
|
||||
export function useDebouncedRef<T>(source: Ref<T>, delay = 150): Ref<T> {
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface ListControlsOptions<T> {
|
||||
urlKey?: string;
|
||||
}
|
||||
|
||||
/** Sous-ensemble consommé par ListToolbar (sans pagination) — implémentable aussi par un store partagé. */
|
||||
/** Sous-ensemble consommé par ListToolbar (sans pagination) : implémentable aussi par un store partagé. */
|
||||
export interface ListToolbarControls<T> {
|
||||
search: Ref<string>;
|
||||
sortKey: Ref<string>;
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface WorktreeView extends ListToolbarControls<WorktreeSummary> {
|
||||
|
||||
/**
|
||||
* État de tri/filtre/recherche des worktrees, PARTAGÉ entre toutes les RepoSection du dashboard
|
||||
* (une seule toolbar pilote toutes les sections). Singleton module-level — et non un store Pinia,
|
||||
* (une seule toolbar pilote toutes les sections). Singleton module-level, et non un store Pinia,
|
||||
* car Pinia déballe les refs, ce que ListToolbar attend justement comme refs. Pas de pagination
|
||||
* (liste groupée par repo) ; `apply()` filtre+trie le sous-ensemble d'un repo donné.
|
||||
*/
|
||||
|
||||
@@ -21,13 +21,13 @@ export default {
|
||||
submit: 'Sign in',
|
||||
submitting: 'Signing in…',
|
||||
invalidToken: 'Invalid token',
|
||||
rateLimited: 'Too many attempts — please wait before retrying',
|
||||
rateLimited: 'Too many attempts. Please wait before retrying',
|
||||
genericError: 'Sign-in failed ({status})',
|
||||
},
|
||||
sessions: {
|
||||
title: 'Sessions',
|
||||
refresh: 'Refresh',
|
||||
empty: 'No sessions yet — start one below.',
|
||||
empty: 'No sessions yet. Start one below.',
|
||||
newSession: 'New session',
|
||||
cwdLabel: 'Working directory',
|
||||
cwdPlaceholder: '/absolute/path/to/project',
|
||||
@@ -91,10 +91,10 @@ export default {
|
||||
namePlaceholder: 'e.g. Payments stack',
|
||||
colorLabel: 'Color',
|
||||
reposLabel: 'Repositories',
|
||||
empty: 'No group yet — create one above.',
|
||||
empty: 'No group yet. Create one above.',
|
||||
noRepos: 'No repository selected.',
|
||||
noReposInGroup: 'No repository in this group yet — add some below.',
|
||||
noReposRegistered: 'No repository registered yet — add some from the dashboard first.',
|
||||
noReposInGroup: 'No repository in this group yet. Add some below.',
|
||||
noReposRegistered: 'No repository registered yet. Add some from the dashboard first.',
|
||||
open: 'Open',
|
||||
edit: 'Edit repositories',
|
||||
editDone: 'Done',
|
||||
@@ -108,13 +108,13 @@ export default {
|
||||
viewList: 'Repos',
|
||||
viewGrid: 'Terminals',
|
||||
gridEmpty: 'No active session in this group.',
|
||||
gridCapped: 'Showing {shown} of {total} sessions — close some terminals to see the rest.',
|
||||
gridCapped: 'Showing {shown} of {total} sessions. Close some terminals to see the rest.',
|
||||
newFeature: 'New group session',
|
||||
spanBadge: 'group · {n} repo | group · {n} repos',
|
||||
},
|
||||
crossRepo: {
|
||||
title: 'New group session',
|
||||
intro: 'Launch one Claude session that spans every repo in this group — a single conversation across all of them.',
|
||||
intro: 'Launch one Claude session that spans every repo in this group: a single conversation across all of them.',
|
||||
modeLabel: 'Directories',
|
||||
modeFeature: 'New branch in each repo',
|
||||
modeMain: 'Main checkouts',
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
remove: 'Remove',
|
||||
pathLabel: 'Repository path',
|
||||
pathPlaceholder: '/absolute/path/to/repo',
|
||||
empty: 'No repository registered yet — add one above, or scan for repos.',
|
||||
empty: 'No repository registered yet. Add one above, or scan for repos.',
|
||||
invalid: 'unavailable',
|
||||
scan: 'Scan',
|
||||
scanResult: 'No new repository found | 1 new repository added | {n} new repositories added',
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
files: 'Files',
|
||||
changes: 'Changes',
|
||||
noChanges: 'No changes',
|
||||
changesTruncated: 'Too many changes — list truncated.',
|
||||
changesTruncated: 'Too many changes. List truncated.',
|
||||
terminal: 'Terminal',
|
||||
editor: 'Editor',
|
||||
diff: 'Diff',
|
||||
@@ -226,7 +226,7 @@ export default {
|
||||
overwrite: 'Overwrite',
|
||||
},
|
||||
diff: {
|
||||
binary: 'Binary file — no text diff.',
|
||||
binary: 'Binary file. No text diff.',
|
||||
tooLarge: 'Diff too large to display.',
|
||||
noChanges: 'No changes in this file.',
|
||||
},
|
||||
@@ -304,7 +304,7 @@ export default {
|
||||
observer: 'observer (read-only)',
|
||||
sessionEnded: 'Session ended',
|
||||
back: 'Sessions',
|
||||
notAttachable: 'This session runs outside Arboretum — fork it to interact, or resume it once it has stopped.',
|
||||
notAttachable: 'This session runs outside Arboretum. Fork it to interact, or resume it once it has stopped.',
|
||||
fullscreen: 'Fullscreen',
|
||||
exitFullscreen: 'Exit fullscreen',
|
||||
maximize: 'Maximize',
|
||||
@@ -316,7 +316,7 @@ export default {
|
||||
push: {
|
||||
enable: 'Enable notifications',
|
||||
disable: 'Notifications on',
|
||||
denied: 'Notification permission denied — enable it in your browser settings.',
|
||||
denied: 'Notification permission denied. Enable it in your browser settings.',
|
||||
unsupported: 'Push needs HTTPS (e.g. Tailscale Serve); on iOS, install the app to your home screen first.',
|
||||
},
|
||||
fs: {
|
||||
@@ -329,7 +329,7 @@ export default {
|
||||
repoBadge: 'repo',
|
||||
},
|
||||
ws: {
|
||||
reconnecting: 'Connection lost — reconnecting…',
|
||||
reconnecting: 'Connection lost. Reconnecting…',
|
||||
},
|
||||
nav: {
|
||||
worktrees: 'Worktrees',
|
||||
@@ -473,7 +473,7 @@ export default {
|
||||
preferencesHint: 'These apply to this browser only.',
|
||||
language: 'Language',
|
||||
notifications: 'Notifications',
|
||||
notificationsEnabled: 'Enabled — you will be notified when a session needs input.',
|
||||
notificationsEnabled: 'Enabled. You will be notified when a session needs input.',
|
||||
notificationsDisabled: 'Disabled.',
|
||||
enableNotifications: 'Enable notifications',
|
||||
disableNotifications: 'Disable notifications',
|
||||
@@ -488,7 +488,7 @@ export default {
|
||||
newTokenPlaceholder: 'e.g. phone, laptop',
|
||||
createToken: 'Create token',
|
||||
tokenCreated: 'Token created',
|
||||
copyTokenHint: 'Copy this token now — for security it will never be shown again.',
|
||||
copyTokenHint: 'Copy this token now. For security it will never be shown again.',
|
||||
copy: 'Copy',
|
||||
copied: 'Copied',
|
||||
current: 'current',
|
||||
@@ -498,7 +498,7 @@ export default {
|
||||
revoke: 'Revoke',
|
||||
confirmRevoke: 'Confirm revoke',
|
||||
tokenRevoked: 'Token revoked',
|
||||
lastTokenError: 'You cannot revoke the last active token — create another one first.',
|
||||
lastTokenError: 'You cannot revoke the last active token. Create another one first.',
|
||||
save: 'Save',
|
||||
saved: 'Saved',
|
||||
// Sécurité & conformité
|
||||
@@ -513,21 +513,21 @@ export default {
|
||||
discovery: 'Repository discovery',
|
||||
discoveryHint: 'Arboretum scans these folders for git repositories and registers them automatically. Hidden repos are kept and never re-added.',
|
||||
scanRoots: 'Scan folders',
|
||||
scanRootsEmpty: 'No folder configured — your home directory is used by default.',
|
||||
scanRootsEmpty: 'No folder configured. Your home directory is used by default.',
|
||||
addRoot: 'Add folder',
|
||||
removeRoot: 'Remove',
|
||||
scanInterval: 'Re-scan interval (minutes)',
|
||||
scanIntervalHint: '0 disables periodic scanning. Changes apply on the next daemon restart.',
|
||||
// Sessions (rétention / archivage auto)
|
||||
sessions: 'Sessions',
|
||||
sessionsHint: 'Finished sessions older than the retention window are automatically archived (hidden from the list by default). Nothing is deleted — archived sessions stay resumable and forkable.',
|
||||
sessionsHint: 'Finished sessions older than the retention window are automatically archived (hidden from the list by default). Nothing is deleted. Archived sessions stay resumable and forkable.',
|
||||
retentionDays: 'Auto-archive after (days)',
|
||||
retentionDaysHint: '0 never archives. Archived sessions are hidden by default; toggle "Show archived" in the sessions list to see them.',
|
||||
// Claude CLI
|
||||
claudeCli: 'Claude CLI',
|
||||
claudeCliHint: 'Where Arboretum finds the `claude` binary and its data. Handy when the daemon runs as a service with a minimal PATH that does not include ~/.local/bin.',
|
||||
claudeBinStatusOk: 'Detected',
|
||||
claudeBinStatusNotFound: 'Not found — sessions cannot start until this is resolved.',
|
||||
claudeBinStatusNotFound: 'Not found. Sessions cannot start until this is resolved.',
|
||||
claudeBinStatusBadPath: 'The configured path is not an executable file.',
|
||||
claudeBinSourceConfigured: 'configured',
|
||||
claudeBinSourcePath: 'PATH',
|
||||
@@ -539,7 +539,7 @@ export default {
|
||||
claudeHomePlaceholder: 'e.g. /home/you/.claude',
|
||||
// Serveur (lecture seule)
|
||||
server: 'Server',
|
||||
serverHint: 'Set at startup via CLI flags — changing them requires restarting the daemon.',
|
||||
serverHint: 'Set at startup via CLI flags. Changing them requires restarting the daemon.',
|
||||
version: 'Version',
|
||||
port: 'Port',
|
||||
bind: 'Bind address',
|
||||
|
||||
@@ -23,13 +23,13 @@ const fr: typeof en = {
|
||||
submit: 'Se connecter',
|
||||
submitting: 'Connexion…',
|
||||
invalidToken: 'Jeton invalide',
|
||||
rateLimited: 'Trop de tentatives — patientez avant de réessayer',
|
||||
rateLimited: 'Trop de tentatives. Patientez avant de réessayer',
|
||||
genericError: 'Échec de la connexion ({status})',
|
||||
},
|
||||
sessions: {
|
||||
title: 'Sessions',
|
||||
refresh: 'Rafraîchir',
|
||||
empty: 'Aucune session — démarrez-en une ci-dessous.',
|
||||
empty: 'Aucune session. Démarrez-en une ci-dessous.',
|
||||
newSession: 'Nouvelle session',
|
||||
cwdLabel: 'Répertoire de travail',
|
||||
cwdPlaceholder: '/chemin/absolu/du/projet',
|
||||
@@ -93,10 +93,10 @@ const fr: typeof en = {
|
||||
namePlaceholder: 'ex. Stack paiements',
|
||||
colorLabel: 'Couleur',
|
||||
reposLabel: 'Dépôts',
|
||||
empty: 'Aucun groupe — créez-en un ci-dessus.',
|
||||
empty: 'Aucun groupe. Créez-en un ci-dessus.',
|
||||
noRepos: 'Aucun dépôt sélectionné.',
|
||||
noReposInGroup: 'Aucun dépôt dans ce groupe — ajoutez-en ci-dessous.',
|
||||
noReposRegistered: 'Aucun dépôt enregistré — ajoutez-en d’abord depuis le tableau de bord.',
|
||||
noReposInGroup: 'Aucun dépôt dans ce groupe. Ajoutez-en ci-dessous.',
|
||||
noReposRegistered: 'Aucun dépôt enregistré. Ajoutez-en d’abord depuis le tableau de bord.',
|
||||
open: 'Ouvrir',
|
||||
edit: 'Modifier les dépôts',
|
||||
editDone: 'Terminé',
|
||||
@@ -110,13 +110,13 @@ const fr: typeof en = {
|
||||
viewList: 'Dépôts',
|
||||
viewGrid: 'Terminaux',
|
||||
gridEmpty: 'Aucune session active dans ce groupe.',
|
||||
gridCapped: '{shown} sessions affichées sur {total} — fermez des terminaux pour voir le reste.',
|
||||
gridCapped: '{shown} sessions affichées sur {total}. Fermez des terminaux pour voir le reste.',
|
||||
newFeature: 'Nouvelle session de groupe',
|
||||
spanBadge: 'groupe · {n} dépôt | groupe · {n} dépôts',
|
||||
},
|
||||
crossRepo: {
|
||||
title: 'Nouvelle session de groupe',
|
||||
intro: 'Lance UNE session Claude couvrant tous les dépôts du groupe — une seule conversation reliant l’ensemble.',
|
||||
intro: 'Lance UNE session Claude couvrant tous les dépôts du groupe : une seule conversation reliant l’ensemble.',
|
||||
modeLabel: 'Répertoires',
|
||||
modeFeature: 'Nouvelle branche dans chaque dépôt',
|
||||
modeMain: 'Checkouts principaux',
|
||||
@@ -142,7 +142,7 @@ const fr: typeof en = {
|
||||
remove: 'Retirer',
|
||||
pathLabel: 'Chemin du dépôt',
|
||||
pathPlaceholder: '/chemin/absolu/du/repo',
|
||||
empty: 'Aucun dépôt enregistré — ajoutez-en un ci-dessus, ou lancez un scan.',
|
||||
empty: 'Aucun dépôt enregistré. Ajoutez-en un ci-dessus, ou lancez un scan.',
|
||||
invalid: 'indisponible',
|
||||
scan: 'Scanner',
|
||||
scanResult: 'Aucun nouveau dépôt trouvé | 1 nouveau dépôt ajouté | {n} nouveaux dépôts ajoutés',
|
||||
@@ -211,7 +211,7 @@ const fr: typeof en = {
|
||||
files: 'Fichiers',
|
||||
changes: 'Changements',
|
||||
noChanges: 'Aucun changement',
|
||||
changesTruncated: 'Trop de changements — liste tronquée.',
|
||||
changesTruncated: 'Trop de changements. Liste tronquée.',
|
||||
terminal: 'Terminal',
|
||||
editor: 'Éditeur',
|
||||
diff: 'Diff',
|
||||
@@ -228,7 +228,7 @@ const fr: typeof en = {
|
||||
overwrite: 'Écraser',
|
||||
},
|
||||
diff: {
|
||||
binary: 'Fichier binaire — pas de diff texte.',
|
||||
binary: 'Fichier binaire. Pas de diff texte.',
|
||||
tooLarge: 'Diff trop volumineux à afficher.',
|
||||
noChanges: 'Aucun changement dans ce fichier.',
|
||||
},
|
||||
@@ -307,7 +307,7 @@ const fr: typeof en = {
|
||||
sessionEnded: 'Session terminée',
|
||||
back: 'Sessions',
|
||||
notAttachable:
|
||||
'Cette session tourne hors d’Arboretum — dupliquez-la pour interagir, ou reprenez-la une fois arrêtée.',
|
||||
'Cette session tourne hors d’Arboretum. Dupliquez-la pour interagir, ou reprenez-la une fois arrêtée.',
|
||||
fullscreen: 'Plein écran',
|
||||
exitFullscreen: 'Quitter le plein écran',
|
||||
maximize: 'Agrandir',
|
||||
@@ -319,7 +319,7 @@ const fr: typeof en = {
|
||||
push: {
|
||||
enable: 'Activer les notifications',
|
||||
disable: 'Notifications activées',
|
||||
denied: 'Permission de notification refusée — activez-la dans les réglages du navigateur.',
|
||||
denied: 'Permission de notification refusée. Activez-la dans les réglages du navigateur.',
|
||||
unsupported: 'Le push nécessite HTTPS (ex. Tailscale Serve) ; sur iOS, installez d’abord l’app sur l’écran d’accueil.',
|
||||
},
|
||||
fs: {
|
||||
@@ -332,7 +332,7 @@ const fr: typeof en = {
|
||||
repoBadge: 'dépôt',
|
||||
},
|
||||
ws: {
|
||||
reconnecting: 'Connexion perdue — reconnexion…',
|
||||
reconnecting: 'Connexion perdue. Reconnexion…',
|
||||
},
|
||||
nav: {
|
||||
worktrees: 'Worktrees',
|
||||
@@ -476,7 +476,7 @@ const fr: typeof en = {
|
||||
preferencesHint: 'Ces réglages ne s’appliquent qu’à ce navigateur.',
|
||||
language: 'Langue',
|
||||
notifications: 'Notifications',
|
||||
notificationsEnabled: 'Activées — vous serez notifié lorsqu’une session attend une réponse.',
|
||||
notificationsEnabled: 'Activées. Vous serez notifié lorsqu’une session attend une réponse.',
|
||||
notificationsDisabled: 'Désactivées.',
|
||||
enableNotifications: 'Activer les notifications',
|
||||
disableNotifications: 'Désactiver les notifications',
|
||||
@@ -491,7 +491,7 @@ const fr: typeof en = {
|
||||
newTokenPlaceholder: 'ex. téléphone, portable',
|
||||
createToken: 'Créer un jeton',
|
||||
tokenCreated: 'Jeton créé',
|
||||
copyTokenHint: 'Copiez ce jeton maintenant — par sécurité il ne sera plus jamais affiché.',
|
||||
copyTokenHint: 'Copiez ce jeton maintenant. Par sécurité il ne sera plus jamais affiché.',
|
||||
copy: 'Copier',
|
||||
copied: 'Copié',
|
||||
current: 'courant',
|
||||
@@ -501,7 +501,7 @@ const fr: typeof en = {
|
||||
revoke: 'Révoquer',
|
||||
confirmRevoke: 'Confirmer la révocation',
|
||||
tokenRevoked: 'Jeton révoqué',
|
||||
lastTokenError: 'Impossible de révoquer le dernier jeton actif — créez-en un autre d’abord.',
|
||||
lastTokenError: 'Impossible de révoquer le dernier jeton actif. Créez-en un autre d’abord.',
|
||||
save: 'Enregistrer',
|
||||
saved: 'Enregistré',
|
||||
// Sécurité & conformité
|
||||
@@ -516,21 +516,21 @@ const fr: typeof en = {
|
||||
discovery: 'Découverte des dépôts',
|
||||
discoveryHint: 'Arboretum scanne ces dossiers à la recherche de dépôts git et les enregistre automatiquement. Les dépôts masqués sont conservés et jamais ré-ajoutés.',
|
||||
scanRoots: 'Dossiers à scanner',
|
||||
scanRootsEmpty: 'Aucun dossier configuré — votre dossier personnel est utilisé par défaut.',
|
||||
scanRootsEmpty: 'Aucun dossier configuré. Votre dossier personnel est utilisé par défaut.',
|
||||
addRoot: 'Ajouter un dossier',
|
||||
removeRoot: 'Retirer',
|
||||
scanInterval: 'Intervalle de re-scan (minutes)',
|
||||
scanIntervalHint: '0 désactive le scan périodique. Les changements prennent effet au prochain redémarrage du daemon.',
|
||||
// Sessions (rétention / archivage auto)
|
||||
sessions: 'Sessions',
|
||||
sessionsHint: 'Les sessions terminées plus anciennes que la fenêtre de rétention sont archivées automatiquement (masquées de la liste par défaut). Rien n’est supprimé — une session archivée reste reprenable et forkable.',
|
||||
sessionsHint: 'Les sessions terminées plus anciennes que la fenêtre de rétention sont archivées automatiquement (masquées de la liste par défaut). Rien n’est supprimé. Une session archivée reste reprenable et forkable.',
|
||||
retentionDays: 'Auto-archiver après (jours)',
|
||||
retentionDaysHint: '0 n’archive jamais. Les sessions archivées sont masquées par défaut ; activez « Afficher les archivées » dans la liste pour les voir.',
|
||||
// Claude CLI
|
||||
claudeCli: 'CLI Claude',
|
||||
claudeCliHint: 'Où Arboretum trouve le binaire `claude` et ses données. Utile quand le daemon tourne en service avec un PATH minimal qui n’inclut pas ~/.local/bin.',
|
||||
claudeBinStatusOk: 'Détecté',
|
||||
claudeBinStatusNotFound: 'Introuvable — impossible de lancer une session tant que ce n’est pas résolu.',
|
||||
claudeBinStatusNotFound: 'Introuvable. Impossible de lancer une session tant que ce n’est pas résolu.',
|
||||
claudeBinStatusBadPath: 'Le chemin configuré n’est pas un fichier exécutable.',
|
||||
claudeBinSourceConfigured: 'configuré',
|
||||
claudeBinSourcePath: 'PATH',
|
||||
@@ -542,7 +542,7 @@ const fr: typeof en = {
|
||||
claudeHomePlaceholder: 'ex. /home/vous/.claude',
|
||||
// Serveur (lecture seule)
|
||||
server: 'Serveur',
|
||||
serverHint: 'Définis au démarrage via des flags CLI — les modifier nécessite de redémarrer le daemon.',
|
||||
serverHint: 'Définis au démarrage via des flags CLI. Les modifier nécessite de redémarrer le daemon.',
|
||||
version: 'Version',
|
||||
port: 'Port',
|
||||
bind: 'Adresse de liaison',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// (cf. composables/useMonaco) → chunk séparé, zéro impact sur le bundle initial.
|
||||
// Workers via Vite (?worker) : on ne fournit que le worker de base. La colorisation syntaxique
|
||||
// (Monarch) tourne sur le thread principal et ne dépend d'aucun worker ; les services de langage
|
||||
// avancés (TS/JSON) dégradent proprement — suffisant pour éditer + sauver dans le navigateur.
|
||||
// avancés (TS/JSON) dégradent proprement : suffisant pour éditer + sauver dans le navigateur.
|
||||
import * as monaco from 'monaco-editor';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Client WebSocket multiplexé — UNE seule connexion pour toute l'app.
|
||||
// Client WebSocket multiplexé : UNE seule connexion pour toute l'app.
|
||||
// Handshake hello/hello_ok, reconnexion avec backoff (ré-attache des terminaux
|
||||
// ouverts + re-sub), corrélation FIFO des 'attached', flow control par ACK
|
||||
// (cumul d'octets traités PAR CANAL, remis à zéro à chaque RESYNC).
|
||||
@@ -29,11 +29,11 @@ export interface TerminalSink {
|
||||
export type SessionEvent = Extract<ServerMessage, { type: 'session_update' | 'session_exit' | 'session_archived' }>;
|
||||
export type WorktreeEvent = Extract<ServerMessage, { type: 'repo_update' | 'repo_removed' | 'worktree_update' | 'worktree_removed' }>;
|
||||
export type GroupEvent = Extract<ServerMessage, { type: 'group_update' | 'group_removed' }>;
|
||||
/** P11 — un réglage a changé (diffusé au topic 'settings'). */
|
||||
/** P11 : un réglage a changé (diffusé au topic 'settings'). */
|
||||
export type SettingsUpdateEvent = Extract<ServerMessage, { type: 'settings_update' }>;
|
||||
/** P12 — progression/fin d'un clone (diffusée au topic 'clones'). */
|
||||
/** P12 : progression/fin d'un clone (diffusée au topic 'clones'). */
|
||||
export type CloneEvent = Extract<ServerMessage, { type: 'clone_update' }>;
|
||||
/** P7 — signal ciblé « le détail (changes/diff) d'un worktree regardé a changé ». */
|
||||
/** P7 : signal ciblé « le détail (changes/diff) d'un worktree regardé a changé ». */
|
||||
export type WorktreeChangesEvent = Extract<ServerMessage, { type: 'worktree_changes' }>;
|
||||
|
||||
export interface AttachOptions {
|
||||
@@ -48,7 +48,7 @@ const BACKOFF_MIN_MS = 500;
|
||||
const BACKOFF_MAX_MS = 10_000;
|
||||
|
||||
export class Attachment {
|
||||
/** canal courant — réassigné à chaque reconnexion, -1 tant que non attaché */
|
||||
/** canal courant : réassigné à chaque reconnexion, -1 tant que non attaché */
|
||||
channel = -1;
|
||||
controlling = false;
|
||||
closed = false;
|
||||
@@ -133,7 +133,7 @@ export class WsClient {
|
||||
private readonly groupListeners = new Set<(e: GroupEvent) => void>();
|
||||
private readonly settingsListeners = new Set<(e: SettingsUpdateEvent) => void>();
|
||||
private readonly cloneListeners = new Set<(e: CloneEvent) => void>();
|
||||
/** P7 — abonnements ciblés au détail d'un worktree (clé repoId\0path → listeners). */
|
||||
/** P7 : abonnements ciblés au détail d'un worktree (clé repoId\0path → listeners). */
|
||||
private readonly worktreeChangesListeners = new Map<string, Set<(e: WorktreeChangesEvent) => void>>();
|
||||
|
||||
connect(): void {
|
||||
@@ -248,7 +248,7 @@ export class WsClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* P7 — observe le détail (changes/diff) d'un worktree précis : envoie `watch`, route les
|
||||
* P7 : observe le détail (changes/diff) d'un worktree précis : envoie `watch`, route les
|
||||
* `worktree_changes` correspondants vers `listener`, et ré-arme automatiquement après reconnexion.
|
||||
* La désinscription envoie `unwatch` quand plus aucun listener ne regarde ce worktree.
|
||||
*/
|
||||
@@ -418,13 +418,13 @@ export class WsClient {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/** ACK immédiat du reliquat traité pour tous les terminaux ouverts — au retour en avant-plan,
|
||||
/** ACK immédiat du reliquat traité pour tous les terminaux ouverts : au retour en avant-plan,
|
||||
* pour rattraper sans attendre le débounce un serveur éventuellement en pause. */
|
||||
private flushAcks(): void {
|
||||
for (const att of this.attachments) this.flushAttachmentAck(att);
|
||||
}
|
||||
|
||||
/** ACK immédiat du reliquat traité d'UN attachment — n'ACK que du réellement traité (invariant
|
||||
/** ACK immédiat du reliquat traité d'UN attachment : n'ACK que du réellement traité (invariant
|
||||
* préservé). Factorisé : utilisé par flushAcks() et par le flush ciblé flushAck(). */
|
||||
private flushAttachmentAck(att: Attachment): void {
|
||||
if (att.closed || att.channel < 0) return;
|
||||
@@ -447,7 +447,7 @@ export class WsClient {
|
||||
this.backoffMs = BACKOFF_MIN_MS;
|
||||
this.status.value = 'open';
|
||||
this.sendSub();
|
||||
// P7 — ré-arme les watchers de worktree (clé repoId\0path) après reconnexion.
|
||||
// P7 : ré-arme les watchers de worktree (clé repoId\0path) après reconnexion.
|
||||
for (const key of this.worktreeChangesListeners.keys()) {
|
||||
const sep = key.indexOf('\0');
|
||||
this.sendControl({ type: 'watch', repoId: key.slice(0, sep), path: key.slice(sep + 1) });
|
||||
@@ -530,7 +530,7 @@ export class WsClient {
|
||||
}
|
||||
case 'error': {
|
||||
if (msg.channel !== undefined) {
|
||||
console.warn(`[ws] channel ${msg.channel}: ${msg.code} — ${msg.message}`);
|
||||
console.warn(`[ws] channel ${msg.channel}: ${msg.code}: ${msg.message}`);
|
||||
return;
|
||||
}
|
||||
// un échec d'attach est la seule erreur sans canal corrélable à une requête
|
||||
@@ -547,7 +547,7 @@ export class WsClient {
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.warn(`[ws] ${msg.code} — ${msg.message}`);
|
||||
console.warn(`[ws] ${msg.code}: ${msg.message}`);
|
||||
return;
|
||||
}
|
||||
case 'pong':
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAuthStore } from '../stores/auth';
|
||||
|
||||
// Type du layout choisi par App.vue : 'bare' (login, sans coquille),
|
||||
// 'shell' (sidebar + contenu centré), 'fullbleed' (terminal plein écran dans la coquille),
|
||||
// 'ide' (vue Workspace plein écran SANS coquille — gère son propre layout 3 panneaux).
|
||||
// 'ide' (vue Workspace plein écran SANS coquille : gère son propre layout 3 panneaux).
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
layout?: 'bare' | 'shell' | 'fullbleed' | 'ide';
|
||||
|
||||
@@ -138,8 +138,8 @@ export const useGroupsStore = defineStore('groups', () => {
|
||||
|
||||
/**
|
||||
* Lance une session de groupe reliant tous les repos (P6). En mode « feature » (branch fournie),
|
||||
* crée d'abord le même worktree de branche dans chaque repo — orchestration côté client tolérante
|
||||
* aux échecs partiels, feedback par repo — PUIS une seule session couvrant ces worktrees. En mode
|
||||
* crée d'abord le même worktree de branche dans chaque repo : orchestration côté client tolérante
|
||||
* aux échecs partiels, feedback par repo, PUIS une seule session couvrant ces worktrees. En mode
|
||||
* « checkouts principaux » (sans branch), la session couvre directement le worktree principal de chaque repo.
|
||||
*/
|
||||
async function createGroupFeature(
|
||||
|
||||
@@ -159,7 +159,7 @@ export const useSessionsStore = defineStore('sessions', () => {
|
||||
return res.session;
|
||||
}
|
||||
|
||||
// Fork d'une session (vivante ou morte) — ne corrompt jamais l'originale.
|
||||
// Fork d'une session (vivante ou morte) : ne corrompt jamais l'originale.
|
||||
async function forkSession(id: string): Promise<SessionSummary> {
|
||||
const res = await api.post<SessionResponse>(`/api/v1/sessions/${id}/fork`);
|
||||
upsert(res.session);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
loaded.value = true;
|
||||
}
|
||||
|
||||
// P11 — temps réel : un PATCH /settings d'un autre client/onglet rafraîchit le store ici.
|
||||
// P11, temps réel : un PATCH /settings d'un autre client/onglet rafraîchit le store ici.
|
||||
function startRealtime(): void {
|
||||
unsubscribe ??= wsClient.subscribeSettings((e) => applySettings(e.settings));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const session = computed(() => store.sessions.find((s) => s.id === sessionId.val
|
||||
// défaut true : une navigation directe (liste pas encore chargée) tente l'attache d'une session managée.
|
||||
const attachable = computed(() => session.value?.attachable ?? true);
|
||||
|
||||
// Vrai plein écran (API Fullscreen) — surtout utile sur mobile pour récupérer la barre d'URL.
|
||||
// Vrai plein écran (API Fullscreen) : surtout utile sur mobile pour récupérer la barre d'URL.
|
||||
// On synchronise `isFullscreen` sur l'événement natif : l'utilisateur peut sortir via Échap.
|
||||
const root = useTemplateRef<HTMLDivElement>('root');
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
@@ -474,7 +474,7 @@ async function deleteMyData(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// P11 — un settings_update WS (autre onglet/client) re-synchronise un draft SCALAIRE uniquement
|
||||
// P11 : un settings_update WS (autre onglet/client) re-synchronise un draft SCALAIRE uniquement
|
||||
// s'il n'a pas été modifié localement (le draft valait encore l'ancienne valeur du store) → on
|
||||
// n'écrase jamais une saisie en cours.
|
||||
watch(() => settings.scanIntervalMin, (n, o) => { if (intervalDraft.value === o) intervalDraft.value = n; });
|
||||
|
||||
@@ -37,7 +37,7 @@ const en: HelpSection[] = [
|
||||
{
|
||||
id: 'worktrees',
|
||||
title: 'Repositories & worktrees',
|
||||
blurb: 'Register your git repos, then spin up an isolated worktree per branch — each can auto-start a session.',
|
||||
blurb: 'Register your git repos, then spin up an isolated worktree per branch. Each can auto-start a session.',
|
||||
items: [
|
||||
{
|
||||
title: 'Add a repository',
|
||||
@@ -72,11 +72,11 @@ const en: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Hide noisy history',
|
||||
body: 'Old sessions started outside Arboretum can pile up. Hide one from its row, or use “Hide external history” to clear them all in one click. Hidden sessions stay resumable and survive a re-scan — toggle “Show hidden” to bring them back.',
|
||||
body: 'Old sessions started outside Arboretum can pile up. Hide one from its row, or use “Hide external history” to clear them all in one click. Hidden sessions stay resumable and survive a re-scan. Toggle “Show hidden” to bring them back.',
|
||||
},
|
||||
{
|
||||
title: 'Fullscreen terminal',
|
||||
body: 'The web terminal fills the whole screen, with the prompt pinned to the bottom and full scrollback above. Tap the fullscreen button in the header to go edge-to-edge — handy on phones.',
|
||||
body: 'The web terminal fills the whole screen, with the prompt pinned to the bottom and full scrollback above. Tap the fullscreen button in the header to go edge-to-edge. Handy on phones.',
|
||||
},
|
||||
{
|
||||
title: 'Resume & fork',
|
||||
@@ -107,7 +107,7 @@ const en: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Answer dialogs',
|
||||
body: 'When Claude asks for trust, a permission or a choice, pick an option (or Deny) right from the card — no terminal needed.',
|
||||
body: 'When Claude asks for trust, a permission or a choice, pick an option (or Deny) right from the card. No terminal needed.',
|
||||
},
|
||||
{
|
||||
title: 'Web Push notifications',
|
||||
@@ -130,7 +130,7 @@ const en: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Cross-repo feature',
|
||||
body: 'Create the same branch worktree across every repo of the group, then launch one Claude session that spans them all — one conversation, one shared context. Partial worktree failures can be retried per repo, and the new session opens straight into its terminal.',
|
||||
body: 'Create the same branch worktree across every repo of the group, then launch one Claude session that spans them all. One conversation, one shared context. Partial worktree failures can be retried per repo, and the new session opens straight into its terminal.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -168,7 +168,7 @@ const en: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Access tokens',
|
||||
body: 'Create a token per device and revoke any you no longer trust. A new token value is shown only once — copy it immediately. The last active token cannot be revoked, to avoid locking yourself out.',
|
||||
body: 'Create a token per device and revoke any you no longer trust. A new token value is shown only once. Copy it immediately. The last active token cannot be revoked, to avoid locking yourself out.',
|
||||
},
|
||||
{
|
||||
title: 'Server info',
|
||||
@@ -187,7 +187,7 @@ const en: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'VS Code extension',
|
||||
body: 'Prefer your editor? A native VS Code extension (not a webview) connects to the same daemon: a live tree of repos/worktrees/sessions, native terminals to attach to any session, a waiting counter with notifications, and git actions — right inside VS Code. It is distributed as a private VSIX; build it with “npm run build:vscode” then package with vsce, install via “Install from VSIX”, and sign in with a token.',
|
||||
body: 'Prefer your editor? A native VS Code extension (not a webview) connects to the same daemon: a live tree of repos/worktrees/sessions, native terminals to attach to any session, a waiting counter with notifications, and git actions. Right inside VS Code. It is distributed as a private VSIX; build it with “npm run build:vscode” then package with vsce, install via “Install from VSIX”, and sign in with a token.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -217,7 +217,7 @@ const fr: HelpSection[] = [
|
||||
{
|
||||
id: 'worktrees',
|
||||
title: 'Dépôts & worktrees',
|
||||
blurb: 'Enregistrez vos dépôts git, puis créez un worktree isolé par branche — chacun peut démarrer une session automatiquement.',
|
||||
blurb: 'Enregistrez vos dépôts git, puis créez un worktree isolé par branche. Chacun peut démarrer une session automatiquement.',
|
||||
items: [
|
||||
{
|
||||
title: 'Ajouter un dépôt',
|
||||
@@ -252,11 +252,11 @@ const fr: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Masquer l’historique',
|
||||
body: 'Les anciennes sessions lancées hors d’Arboretum peuvent s’accumuler. Masquez-en une depuis sa ligne, ou utilisez « Masquer l’historique externe » pour toutes les retirer en un clic. Les sessions masquées restent reprenables et survivent à un nouveau scan — activez « Afficher les masquées » pour les retrouver.',
|
||||
body: 'Les anciennes sessions lancées hors d’Arboretum peuvent s’accumuler. Masquez-en une depuis sa ligne, ou utilisez « Masquer l’historique externe » pour toutes les retirer en un clic. Les sessions masquées restent reprenables et survivent à un nouveau scan. Activez « Afficher les masquées » pour les retrouver.',
|
||||
},
|
||||
{
|
||||
title: 'Terminal plein écran',
|
||||
body: 'Le terminal web occupe tout l’écran, avec l’invite ancrée en bas et tout l’historique défilable au-dessus. Touchez le bouton plein écran dans l’en-tête pour passer bord à bord — pratique sur téléphone.',
|
||||
body: 'Le terminal web occupe tout l’écran, avec l’invite ancrée en bas et tout l’historique défilable au-dessus. Touchez le bouton plein écran dans l’en-tête pour passer bord à bord. Pratique sur téléphone.',
|
||||
},
|
||||
{
|
||||
title: 'Reprendre & forker',
|
||||
@@ -287,7 +287,7 @@ const fr: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Répondre aux dialogues',
|
||||
body: 'Quand Claude demande une confiance, une permission ou un choix, sélectionnez une option (ou Refuser) directement depuis la carte — sans terminal.',
|
||||
body: 'Quand Claude demande une confiance, une permission ou un choix, sélectionnez une option (ou Refuser) directement depuis la carte. Sans terminal.',
|
||||
},
|
||||
{
|
||||
title: 'Notifications Web Push',
|
||||
@@ -310,7 +310,7 @@ const fr: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Feature cross-repo',
|
||||
body: 'Créez le même worktree de branche dans chaque dépôt du groupe, puis lancez une seule session Claude qui les couvre tous — une conversation, un contexte partagé. Les échecs partiels de worktree sont rejouables dépôt par dépôt, et la session s’ouvre directement sur son terminal.',
|
||||
body: 'Créez le même worktree de branche dans chaque dépôt du groupe, puis lancez une seule session Claude qui les couvre tous. Une conversation, un contexte partagé. Les échecs partiels de worktree sont rejouables dépôt par dépôt, et la session s’ouvre directement sur son terminal.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -348,7 +348,7 @@ const fr: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Jetons d’accès',
|
||||
body: 'Créez un jeton par appareil et révoquez ceux en lesquels vous n’avez plus confiance. La valeur d’un nouveau jeton n’est affichée qu’une seule fois — copiez-la immédiatement. Le dernier jeton actif ne peut pas être révoqué, pour éviter de vous verrouiller dehors.',
|
||||
body: 'Créez un jeton par appareil et révoquez ceux en lesquels vous n’avez plus confiance. La valeur d’un nouveau jeton n’est affichée qu’une seule fois. Copiez-la immédiatement. Le dernier jeton actif ne peut pas être révoqué, pour éviter de vous verrouiller dehors.',
|
||||
},
|
||||
{
|
||||
title: 'Infos serveur',
|
||||
@@ -367,7 +367,7 @@ const fr: HelpSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Extension VS Code',
|
||||
body: 'Vous préférez votre éditeur ? Une extension VS Code native (pas un webview) se connecte au même daemon : un arbre en direct des repos/worktrees/sessions, des terminaux natifs pour s’attacher à n’importe quelle session, un compteur d’attente avec notifications, et les actions git — directement dans VS Code. Distribuée en VSIX privé : buildez avec « npm run build:vscode » puis packagez avec vsce, installez via « Installer à partir d’un VSIX », et connectez-vous avec un token.',
|
||||
body: 'Vous préférez votre éditeur ? Une extension VS Code native (pas un webview) se connecte au même daemon : un arbre en direct des repos/worktrees/sessions, des terminaux natifs pour s’attacher à n’importe quelle session, un compteur d’attente avec notifications, et les actions git. Directement dans VS Code. Distribuée en VSIX privé : buildez avec « npm run build:vscode » puis packagez avec vsce, installez via « Installer à partir d’un VSIX », et connectez-vous avec un token.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user