Skip to content

Release v1.2.0 - #39

Merged
Alex375 merged 125 commits into
mainfrom
dev
Jul 22, 2026
Merged

Release v1.2.0#39
Alex375 merged 125 commits into
mainfrom
dev

Conversation

@Alex375

@Alex375 Alex375 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Release v1.2.0. Voir les commits de dev depuis la dernière release.

Alex375 and others added 30 commits July 2, 2026 17:00
- Fix du « texte fantôme » sur les noms de conversation : l'auto-title
  remplaçait le texte en place, WebKit ne repeignait pas la boîte
  text-overflow:ellipsis (ancien + nouveau glyphes superposés, corrigé
  seulement par un resize). Le <span> du nom est désormais keyé par
  conv.name → remonté à chaque changement de titre → zone de peinture
  propre. Les titres de repos ne changent jamais, d'où l'absence de ghost.
- Titres de repos mis en valeur : 15px/700 + léger tracking, chevron de
  pli plus lumineux, plus d'espacement entre groupes ; noms de
  conversation posés au niveau de texte normal (--wf-tx), re-brillants au
  survol / actif / attention → hiérarchie repo > conversation nette.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…leaux

La colonne courte d'un tableau Markdown pouvait être écrasée à ~2 caractères,
en-tête cassé en plein mot. Cause : `.root` porte `overflow-wrap: anywhere`
(voulu pour wrapper les URLs/chemins longs en prose), propriété héritée par
les cellules `th`/`td`. Par spec CSS, `anywhere` (contrairement à `break-word`)
compte ses points de coupure dans le calcul du min-content → la largeur
minimale d'une cellule s'effondre à ~1 caractère, et `table-layout: auto`
écrase alors une colonne courte au profit d'une colonne à contenu long.

Fix sur `.root th`/`.root td` (couvre les 3 modes classic/warm/minimal, tous
montés sur `.root`) :
- `overflow-wrap: break-word; word-break: normal` : une colonne n'est jamais
  réduite sous la largeur de son mot le plus long, tout en laissant un token
  pathologiquement long (URL) se couper.
- `.root th { white-space: nowrap }` : l'en-tête reste toujours affiché en
  entier ; le wrapper scrolle horizontalement si besoin (aucune perte).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odèle

Un skill auto-invoqué par le modèle (outil Skill, ex. land→done) rendait
son body SKILL.md via une carte tool générique. On le rend désormais comme
un chip commande dédié « /done » (SkillChip + segment `skill`), réutilisant
l'affordance .cv-cmd, cohérent avec un slash-command tapé au composer.

Le body du SKILL.md porte isMeta:true et est déjà droppé (assembler L805 +
history L245) → jamais de fausse bulle user. On verrouille ce comportement
et on documente le wire (trou de spec).

- userText.tsx : parseSkillInvocation + SkillChip (dérive /done depuis input.skill)
- toolGroup.ts : segment/atome `skill` (casse le run, non compté)
- ConductorThread + SubAgentTranscript : branche skill (live + transcript)
- CSS .cv-skill (pill dans le flux assistant)
- docs/claude-code-protocol.md §3.7.1 : wire skill/slash-command
- fixture capture_skill.jsonl + tests régression (Rust) + tests front

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tations

Carte « Plan proposé » de premier plan (segment `plan` extrait du pli
clean-output), qui rend le plan markdown et héberge la décision :
accepter (→ bascule le mode en `auto`) ou refuser & réviser. L'utilisateur
peut surligner des passages et les commenter (CSS Custom Highlight API) ;
les retours sont renvoyés à l'agent dans le message de refus. Badge de
statut (accepté/refusé) dérivé du tool_result, persistant au reload.
Annotations + note générale persistées (localStorage), restaurées à l'undo
d'une suppression de conversation.

100% front : le wire ExitPlanMode passe déjà par le pipeline tool_use +
can_use_tool + tool_result. Aucun changement Rust.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	src/features/conversation/ConductorThread.tsx
#	src/features/conversation/toolGroup.ts
Corrige les 9 points remontés par la review multi-agents (rendu mode plan
+ skill chip).

Comportement
- PlanCard: `clearOwnSelection` scope la purge de sélection à la carte — ne
  wipe plus une sélection étrangère ailleurs à l'écran (clearDraft/commitDraft)
- planStatus: corrige la contradiction doc/code (isError ⟺ permission refusée
  pour ExitPlanMode — comportement juste, doc rendue honnête)
- CleanBlocks: un plan enterré dans le fold (historique rechargé) est désormais
  rendu en clair (renderFoldedWork découpe le fold à chaque plan)

Perf
- PlanCard: sélecteur ciblé usePendingPermission — plus de re-render à chaque
  permission de la session
- PlanCard: rAF-throttle du handler scroll/resize du composer

Cleanup / dédup
- resultText.ts: flatten de tool_result partagé (planStatus + ToolSection)
- util/id.ts: uid partagé (conversationsStore + planAnnotations)
- store/persist.ts: loadJson/saveJson partagés (planAnnotations + workFold)
- planAnnotations: withList/withNote → setNested générique

+ tests persist/resultText. tsc vert, 638 tests OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt du wire live)

Un skill auto-invoqué par le modèle (outil Skill) réaffichait le body du
SKILL.md comme une fausse bulle user, EN PLUS du SkillChip. Le fix précédent
(2247ebd6) droppait la ligne `isMeta:true`, ce qui marche au reload mais PAS
en live.

Cause racine confirmée par capture live (nouveau probe
`control.rs::live_capture_skill_body_replay`) : le CLI n'ajoute `isMeta:true`
QUE dans le transcript persisté ; sur le stdout LIVE la ligne du body est nue
(`isMeta`/`isReplay`/`sourceToolUseID` tous absents). La garde `is_meta` ne se
déclenche donc jamais en live → bug live-only (disparaissait au redémarrage).

Fix socle-only (assembler) : on arme `skill_invocation_pending` sur un tool_use
Skill (`ingest_assistant`), on droppe dans `ingest_user` la ligne texte qui
ouvre sur le prefix boilerplate « Base directory for this skill: » tant que
c'est armé, et on désarme en fin de tour (`ingest_result`). Double garde (flag
armé ET prefix) → jamais de faux-drop d'un vrai message. Front inchangé.

- protocol.rs : champ UserMsg.source_tool_use_id (présent on-disk, absent live)
- fixture capture_skill_live.jsonl (vraie forme live, sans isMeta) + 2 tests
  (drop live + non-régression « ne pas avaler le tour suivant »)
- docs/claude-code-protocol.md §3.7.1 : écart live-vs-disk sur isMeta documenté
- probe live #[ignore] conservé (instrument de re-capture, comme les autres)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…plein écran

- Registre central de bindings (ACTION_BINDINGS + matchChord) dans shortcuts.ts,
  source de vérité unique partagée entre le handler global et la page Réglages.
- Nouveaux raccourcis globaux : ⌘B éditeur, ⌘J terminal, ⌘⇧G Git, ⌘L clean output,
  ⌘E extensions, ⌘N nouvelle conv, ⌘⌥↑/↓ nav conversations, ⌘⇧O historique
  (AZERTY-safe : lettres via e.key, flèches via e.code).
- Onglet « Raccourcis » dans les Réglages : récap complet (existant + nouveau) rendu
  depuis le même catalogue SHORTCUT_GROUPS.
- Fix « Échap sort du plein écran macOS » : garde globale en phase capture qui
  preventDefault Échap pour tous les cas (sauf Monaco/xterm) ; les popovers imbriqués
  (transcript, task output, workflow, badge tâches de fond) passent en stopPropagation
  pour préserver « une touche = une couche », Extensions + reply modal ne gatent plus
  sur defaultPrevented.
- Tests : matchChord + intégrité du registre/catalogue (22 nouveaux).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mise à jour

- Suppression : confirmation avant de supprimer une conversation ACTIVE (tour en vol
  ou tâches de fond) ; friction-free (1 clic, ⌘Z) préservé pour les convs inactives.
- Page MAJ : notes rendues en Markdown, blabla Gatekeeper masqué in-app via le marqueur
  `<!-- gh-only -->`, hero version, pop-up de redémarrage TOUJOURS affichée (renforcée
  si des sessions tournent).
- Pipeline notes : CHANGELOG.md lu par release.yml, note d'install déplacée dans
  .github/release-install-note.md, skill /release étape 3b ; inAppReleaseNotes + test.
- Correctifs revue adversariale : garde sur le compteur brut de tâches de fond
  (kind review/error masquait backgrounding) ; Échap ne ferme qu'une couche
  (ConfirmDialog preventDefault + SettingsPanel !defaultPrevented) ; :global(.wf-ico) ;
  auto-close du confirm stale ; props CSS mortes retirées.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…message, to-do)

- clic sur le corps d'une carte → ouvre la conversation en modale (le titre
  reste l'entrée plein écran) ; bouton « Ouvrir » retiré
- résumé du dernier message → pop-up avec le message complet
- pips de to-do → pop-up avec la to-do list détaillée (TodoList réutilisé)
- CardPopover porté (document.body) + collision-detection : jamais tronqué
  par un bord d'écran (flip haut/bas + clamp horizontal)
- garde clic-corps contre le bubbling des events de portal (contains)

Revue adversariale (7 dims × 3 lentilles) : 2 fixes CSS LOW confirmés appliqués
(hover n'écrase plus l'accent .att/.rev ; line-height du bouton lastmsg).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n message

Le bouton « + » du composeur ouvre un file picker natif (multi-sélection) :
- images (png/jpeg/gif/webp) → bloc image base64 sur le wire stream-json
  (vérifié accepté par claude 2.1.187) + vignette optimiste dans la bulle ;
- tout autre fichier → mention de chemin insérée dans le message ;
- collage d'image (Cmd+V) → attachement direct.

Socle : send_message(session, text, images: Vec<ImageAttachment>),
transport::user_message_with_images (texte puis blocs image), SessionCommand::SendUser.
Bindings tauri-specta regénérés. Store d'attachements en mémoire par conversation.

Corrige aussi les 6 findings de la revue adversariale :
- nettoyage des attachements sur suppression conv/repo + wipe-all (fuite mémoire) ;
- plafond de taille au collage (16 Mio, aligné sur fs::MAX_FILE_BYTES) ;
- consommation de l'uuid d'écho pour un tour image-seule (fuite bornée) ;
- verrou d'envoi tant qu'un collage se lit (race collage→message suivant) ;
- rendu des images dans InlineUserMarker (clean-output) ;
- conv à 1er message image-seule listée/indexée + placeholder [image] au reload.

tsc + vitest (650) + cargo test --lib (215) + build : verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	src-tauri/src/supervisor/assembler.rs
…squage task-notifications, refonte Réglages

Front-only, 4 features :

1. Fleet readout — ligne de statut adaptative des compteurs d'agents par stage
   (Running · Review · Need Attention · Idle). Bandeau large en haut du Flight Deck
   (remplace l'AttentionBar) + encadré compact en bas de la sidebar (3 stages fusionnés :
   Attention = Review + Need Attention). Zéros masqués, activité d'abord, « Fleet rests »
   au repos. 2 toggles séparés (display store). Nouveau tally tallyFleet/useFleetCounts
   + fleetSegments/mergedFleetSegments dans agent/fleet.ts ; readoutBucket dans status.ts.

2. Alerte « terminé + tâche de fond » — quand un tour finit alors qu'une tâche de fond
   tourne, accent violet sur le review/question (anneau .wf-dot.bgring + chip « N en fond »)
   pour le distinguer d'un vrai fini. Réglage alertOnBackgroundWait (défaut ON) : OFF →
   passe direct en backgrounding, sans alerte ni notif OS (gate notifyTransition sur le
   statut résultant backgrounding). bg? porté par review/needInput/error ; backgroundCount().
   L'agrégat flotte reçoit désormais les vraies tâches de fond par conv (runningCountsByConv).

3. Masquage des <task-notification> — bruit au reload/import historique. Gate unique dans
   SpecialMessageCard (pref showTaskNotifications, défaut OFF) ; code de rendu conservé.

4. Refonte de la page Réglages — onglets à accent coral, groupes en cartes arrondies,
   en-têtes de page, hero « À propos ». Kit réutilisable SettingsKit (PageHead/SettingsGroup/
   ToggleRow). CSS mort nettoyé.

tsc 0 · vitest 674/674.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La PR #29 (v0.23.0) a été squash-mergée dans main, cassant le lien
d'historique. dev contient déjà tout le contenu de main → stratégie
'ours' pour rétablir la base de merge sans rien changer à l'arbre de dev.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause racine des conflits de la PR de release : la PR #29 (v0.23.0) a été
squash-mergée dans main, cassant le lien d'historique avec dev (branche
permanente). La base de merge est restée figée → tout le changeset v0.23.0
est entré en conflit avec lui-même sur la release suivante.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review multi-agents adversariale (8 finders + vérif finding-par-finding).

Medium — régression Échap (introduite par le même PR)
- SettingsPanel: retire le gate `!e.defaultPrevented` (revenu à `!busy`). Le
  garde Échap app-wide en phase capture (App.tsx) preventDefault TOUJOURS →
  gater dessus rendait la modale Réglages infermable au clavier.
- ConfirmDialog: Échap passe de preventDefault à stopPropagation → aligné sur
  la convention « couche imbriquée = stopPropagation », un Échap ne ferme
  qu'une couche (le confirm interne, pas aussi le panneau Réglages).

Low
- ConductorComposer.addPaths: encadre les lectures d'images du sélecteur « + »
  dans le même send-lock `attaching` que le collage → un pick-then-Enter rapide
  ne part plus sans l'image (qui voyageait sur le message suivant).
- ConductorComposer.addPaths: accumule les échecs d'attachement multiples en un
  seul message (dédupliqué) au lieu de n'afficher que le dernier.
- PlanCard: ajoute stopPropagation sur l'Échap du composeur de commentaire de
  plan → n'annule que le brouillon, ne ferme plus aussi la reply-modal du
  Flight Deck (« une touche = une couche »).
- fleet.ts: corrige 2 références @link mortes (FleetAttention/useFleetAttention
  renommés → useFleetLanes ; suppression du lien vers le type disparu).

tsc vert · vitest 674/674. Front-only, aucun changement Rust.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #30 à nouveau squash-mergée dans main → on ramène le lien d'historique
dans dev (branche permanente). Arbres identiques (git diff dev main vide),
donc -s ours est lossless : rétablit la base de merge pour que la prochaine
release ne reparte pas en conflit. Cf. étape 9b du skill /release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chaque /build-app crée une identité macOS isolée com.tosse.desktop.<slug>
(App Support, Caches, Preferences, WebKit…) que /land ne nettoyait jamais
→ accumulation d'identités mortes dans ~/Library au fil des features.

- /land : nouvelle étape 7b qui purge com.tosse.desktop.<slug> au moment
  de supprimer le worktree. Cible le slug EXACT (jamais un glob, qui
  attraperait la prod com.tosse.desktop) + garde-fou refusant prod/dev.
  Ligne optionnelle ajoutée au tableau récapitulatif (étape 9).
- /build-app : note croisée renvoyant vers /land étape 7b.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reprend l'aperçu du Flight Deck (message court en clair, ou résumé Haiku)
dans la vue Conversation : bandeau flottant épinglé en haut du fil, clic →
scroll jusqu'au dernier message user. Réglage showLastMessagePreview (défaut ON).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UserText rend le texte dans un span `.cv-user-text` (white-space: pre-wrap)
au lieu d'un fragment nu — sinon HTML collapse les `\n` et les sauts de ligne
du message envoyé disparaissent à l'affichage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Alex375 and others added 29 commits July 13, 2026 16:02
Revue de la release v1.0.0 (dev→main). Un Medium, un Low-Medium et 5 Low.

- Medium (kit.tsx Menu) : l'Échap d'un Menu ne faisait pas stopPropagation →
  dans la modale Flight Deck (et le gestionnaire d'Extensions) un seul Échap
  fermait le menu ET la couche parente. Consomme l'Échap au niveau du Menu
  (comme les popovers drill-in) → « une touche = une couche ».
- Low-Medium (codex/session.rs) : un sous-agent Codex encore « running » n'était
  soldé qu'à la fin de session → faux-vert / compteur flotte gonflé après un
  interrupt. Solde les sous-agents live à la fin de tour (in-turn), sans
  rétrograder un statut déjà terminal. Test ajouté.
- Low (binaryAvailable.ts) : un rejet IPC transitoire figeait un `false`
  permanent (barre « CLI not found » collée toute la session). Laisse l'état
  inconnu (null) + re-probe au prochain mount.
- Low (StreamCard.tsx) : le dialogue de suppression disait « stop the Claude
  session » même pour une conv Codex → suit désormais le backend.
- Low (AccountsSection.tsx) : un échec de login Codex pouvait se ré-afficher à
  la réouverture du panneau → le listener in-panel consomme le stash.
- Low (useAccounts.ts) : la sonde de statut Claude tournait toujours, même sur
  une machine Codex-only sans binaire `claude` → gate sur la disponibilité.
- Low (accounts/mod.rs) : le reader stdout du login Claude était droppé à la fin
  de login_start (read-end fermé → risque SIGPIPE) → gardé vivant dans ActiveLogin.

cargo test 364 ok · tsc clean · vitest 794 ok · bindings inchangés

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anthropic retire la limite de session 5h au profit d'une limite
hebdomadaire unique. /api/oauth/usage renvoie ENCORE `five_hour`
(utilization ~1%) mais un nouveau tableau `limits` déclare
`{kind:"session", is_active:false}` → on l'affichait à tort à ~0%.

parse_usage croise désormais chaque fenêtre avec `limits` et masque
celle dont la limite est inactive (helper pur window_is_active, match
par kind : session→5h, weekly_all/weekly→7d). Rétrocompat : `limits`
absent (ancienne forme) → comportement inchangé. Aucun type IPC touché
(front masquait déjà une fenêtre null). Parité avec Codex.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dex)

Codex référence ses fichiers en liens Markdown `[label](/abs/path:line)`
(là où Claude utilise du code inline en backticks). Ces liens tombaient
sur le renderer `a` brut de StreamMarkdown, qui produisait une ancre morte :
le href est un chemin de système de fichiers, pas une URL web, et la webview
le résolvait en `tauri://localhost/...` — clic sans effet.

- Nouveau composant `MentionLink` (FileMention.tsx) : un href de type chemin
  ouvre le fichier dans l'éditeur latéral à la bonne ligne (revealInEditor,
  gating d'existence comme les mentions en prose) ; une vraie URL web garde
  l'ouverture externe ; chemin non résolu / inert → texte simple.
- `ClickableFile` accepte l'élément `"a"`.
- `preservePathUrls` (urlTransform) : préserve un href de type chemin que le
  sanitizer de react-markdown v9 viderait sinon (bare `foo.py:42`).
- Tests verrouillant le routage (exemple réel, note.txt, URL, javascript:).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…+ modes Light/Hard

Service Rust `power/` pilotant `caffeinate -i -w <pid>` (self-terminant,
teardown no-orphan), IPC `set_awake`, bouton toolbar coffee, store
localStorage, politique Light (suit l'activité flotte + tâches de fond) /
Hard (permanent), section Réglages → Général avec explication des modes.

Correctifs revue adversariale :
- Light inclut les tâches de fond sous état "attention" (fix bug qui laissait
  dormir le Mac pendant qu'un sous-agent tournait)
- enfant caffeinate auto-terminé par `-w <pid>` (anti-orphelin sur crash/SIGKILL)
- échec de spawn surfacé via appErrors (plus d'erreur silencieuse)
- liveness `try_wait` (enfant mort re-spawné)
- test CI non-ignoré + stabilité de PID
- brique partagée OptionCardRail (dédup du sélecteur de mode markdown/caffeinate)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sation (watch fs unique)

Le watch fs OS est unique et suit la conversation active. Un fichier ouvert
(ex. aperçu Markdown) dans une conversation mise en arrière-plan restait figé :
le watch ne signale les changements qu'à partir de son démarrage, donc les
éditions faites par l'agent pendant qu'on était ailleurs n'émettaient aucun
event. Au retour, le watch se re-pointait mais ne relisait jamais les buffers
déjà ouverts → aperçu périmé jusqu'à fermeture/réouverture.

- resyncOpenBuffers : rattrapage des onglets TEXTE ouverts quand le watch se
  (re)pointe (effet [enabled, cwd, convId] dans useFsWatch, après watchDir).
  Onglets image/PDF sautés (perf : évite de re-shipper jusqu'à 16 Mio de base64
  à chaque switch) ; pas de re-listing d'arbo (évite une fausse bannière quand
  un dossier chargé a été supprimé de l'extérieur).
- reloadTab : helper partagé extrait de onExternalChange. Garde `dirty` remontée
  AVANT la branche binaire/trop-gros → un buffer sale dont le fichier disque
  devient binaire n'est plus écrasé silencieusement (perte de données).
- onExternalChange : délègue le refresh d'arbo à refreshDir (dédup).
- Tests : rattrapage propre/sale au resync, skip image/PDF, protection binaire.
  Sentinelles mock __binary__/__toolarge__.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…thinking time

Replace the generic "Thinking…" activity label with fancier gerunds (4 difficulty
tiers) that climb as the discussion's cumulative thinking time grows; anchor "Thinking"
for the first 40s, word re-draws every 40s of thinking and on each new turn. Measure =
time the "Thinking…" spinner is shown, accrued incrementally with a per-sample cap
(sleep-safe), via a single global ticker. Light vertical carousel (RollText) on change.
Only the generic thinking state is touched; backend-agnostic (Claude + Codex).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or banners, lighter thinking ticker)

Addresses the code-review findings on the release changes:

- power/CaffeinateHost: add a 30s heartbeat that re-asserts the keep-awake hold,
  so an externally-killed `caffeinate` child (killall / OS reap) is respawned by
  the idempotent Rust hold() even while `desired` stays true (Hard mode, or Light
  during a long run) — closing a self-heal gap where the Mac silently slept.
  Serialize the `set_awake` IPC writes (promise chain, last-intent-wins) so a
  later-issued release always beats an in-flight periodic hold: Tauri does not
  guarantee cross-invoke ordering, so without this a stale setAwake(true) could
  reach the Rust mutex after a near-simultaneous setAwake(false) and strand the
  Mac awake with no further heartbeat to correct it.

- editor/editorStore: translate the 4 filesystem-error banner strings to English
  (July-2026 English convention). No test/consumer depends on the text (asserts
  only buffer.error truthiness and error counts).

- thinking spinner ticker: sample every 1000ms instead of 500ms. The playful word
  only escalates on 40s tier/rotation boundaries, so second-resolution is ample
  and it halves the store churn during otherwise-quiet generic thinking. The 2000ms
  per-sample accrual cap is unchanged (still >= one interval, so no delta is clipped).

Verified: tsc --noEmit clean, 825/825 front tests pass, adversarial multi-agent
review of the diff clean after the serialization hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ckable-paths pref

Follow-up to the first pass (which existence-gated the links): real Codex file
links still rendered as plain text. Two root causes, both fixed here.

1) Authoritative routing (no existence gate). A Markdown file link is a deliberate
   reference (Codex writes `[name](/abs/path:line)`), so — like a tool-card file_path
   chip and unlike a path guessed from prose — it is now clickable WITHOUT a
   `pathExists` probe (which failed for just-created / temp / outside-cwd paths). The
   routing decision is a pure, unit-tested function `routeMarkdownLink`.

2) Decoupled from the `clickableFileMentions` pref. That "Clickable file paths
   (Read/Write tools)" setting is scoped to the tool-card chips; a file link the
   model wrote in its conversation must be clickable regardless of it. The provider
   now exposes `hostInert` (no-editor-host only, e.g. the reply modal) separately
   from `inert` (which still folds the pref for inline-code mentions + chips), and
   MentionLink uses `hostInert`.

Real-DOM render test (react-dom/server) proves: absolute link clickable, non-existent
link still clickable, pref OFF still clickable, web URL stays external, no-editor-host
→ plain text. tsc 0, vitest 837.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ctive

Suite de f8d1755. Le tableau `limits[]` de /api/oauth/usage porte un
flag `is_active` qui n'indique PAS l'existence d'une limite mais quelle
limite est actuellement « binding » — et il BASCULE dans le temps :
- 2026-07-15 : session(5h) is_active:false / weekly_all(7d) is_active:true
- 2026-07-20 : l'exact inverse (session:true / weekly_all:false)

Filtrer sur is_active (f8d1755) ne montrait donc jamais qu'UNE fenêtre à
la fois : après bascule du flag, c'est la hebdo qui disparaissait
(l'utilisateur ne voyait plus que la 5h). Or les deux objets top-level
five_hour + seven_day sont TOUJOURS présents : les deux plafonds
coexistent.

parse_usage revient au critère de PRÉSENCE (affiche five_hour et/ou
seven_day selon présence de l'objet), le helper window_is_active est
supprimé. Une limite réellement retirée par Anthropic drop son objet →
absente → masquée naturellement. Aucun type IPC touché, front inchangé.
Test is_active remplacé par un test de non-régression (les deux fenêtres
affichées quelle que soit l'orientation du flag).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Panneau/gestion des artefacts générés par l'outil Artifact (pages
hébergées claude.ai/code/artifact/<uuid>), en dérivation front pure
(zéro Rust/IPC/SQLite) depuis le flux de messages :

- Carte inline `ArtifactCard` à chaque publication (favicon, titre,
  état pending/failed/degraded, ouvre le viewer in-app).
- Chip composer « Artefacts (N) » + popover portal groupé par version.
- Sélecteur `useArtifacts` (main-thread only, groupé par file_path,
  last-known-good, drop des publications échouées, read-only).
- Segment `artifact` dans toolGroup (garde file_path : list/url-update
  retombent en run normal), peel hors du pli clean-output.
- Carte-lien `ArtifactRefCard` : une URL d'artefact écrite par Claude
  dans sa prose rend une pilule cliquable (wire MentionLink).
- Viewer in-app `ArtifactViewer` dans le panneau latéral : HTML en
  iframe sandboxée, Markdown via StreamMarkdown, fallback navigateur si
  le fichier temp a disparu. Région `artifactView` en mémoire.
- Icône dédiée `artifact`, états d'erreur explicites (zéro erreur
  silencieuse), tests (isArtifactUrl/artifactKind, selectArtifacts,
  groupBlocks). tsc + 864 vitest + build verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface le /goal natif de Claude Code (feature session, DISK-ONLY dans le
transcript) : icône target cliquable sur la carte Flight Deck + chip dans le
composer, tous deux ouvrant la même popover (condition + dernière raison +
clear). Interception par tail-scan du transcript (load_active_goal), réhydraté
au load/reload + seed par carte (survit au quit/relaunch) + refetch aux fronts
de tour (gaté sur goalSeen pour ne rien coûter aux convs sans goal). Le clear
est silencieux (pas de bulle), garde de clear pilotée par le cycle de vie de la
mutation (in-flight + grâce, rollback onError). Les commandes /goal du composer
partent en silencieux (live == reload). Placement des popovers portalés rendu
width-aware et clampé au viewport.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	src/features/conversation/ConductorComposer.tsx
Revue de code de la release v1.2.0 : 8 points + nits, tous corrigés,
puis revue adversariale des correctifs eux-mêmes (7 findings confirmés).

Perf
- load_active_goal : pré-filtre par sous-chaîne avant tout parse JSON.
  Le scan tournait une fois par carte Flight Deck au montage et à chaque
  front de tour, en parsant chaque ligne d'un transcript de plusieurs Mo.

/goal — fuites de plomberie
- noteFirstMessage n'est plus appelé pour une commande /goal : une conv
  fraîche dont le 1er message était /goal héritait de ce nom.
- setReminder / noteActivity gatés sur !silent : un /goal consommait un
  rappel d'attention non lu et faisait remonter la conv en tête de liste.
- is_goal_command_noise : table explicite de préfixes au lieu du test
  large « Goal », qui pouvait faire disparaître la sortie d'une autre
  commande. Formes lues verbatim dans le handler /goal du binaire
  (« Goal active: … » est la vraie forme du statut, pas « Goal: … »).
  « Goal condition is limited to N characters » reste VISIBLE : c'est un
  set en échec, et l'envoi étant silencieux le masquer priverait
  l'utilisateur de tout retour.

/goal — robustesse
- scheduleGoalRefresh : échelle de relectures bornée après un envoi
  silencieux. Sans elle, /goal <condition> ne produisant pas forcément de
  front de tour, le chip pouvait n'apparaître qu'au message suivant.
- Une lecture vide pendant l'échelle ne désarme plus goalSeen (le write
  disque n'a pas encore atterri), et « jamais lu » est traité comme
  « pas de goal » : sinon l'échelle s'arrêtait sur sa 1re lecture.
- Garde de séquence dans refreshActiveGoal : 3 appelants concurrents,
  une lecture ancienne pouvait écraser une récente.

Artefacts — sécurité
- L'aperçu in-app injecte désormais une CSP dans le srcDoc. La CSP de
  l'app étant nulle et le sandbox ne donnant qu'une origine nulle, un
  artefact prévisualisé était plus permissif que la même page sur
  claude.ai (réseau sortant possible).
- Injection à la position la plus précoce, jamais par recherche d'un
  <head> textuel : un <head dans un commentaire ou une template literal
  gagnait la course et la CSP n'était alors PAS appliquée (vérifié en
  navigateur). Doctype préservé en tête (anti quirks mode).

Artefacts — divers
- ArtifactViewer : échecs distingués (absent / trop gros / binaire /
  erreur de lecture) au lieu d'un « indisponible » unique et muet.
- Garde Codex retiré d'ArtifactCard : il faisait disparaître l'appel
  d'outil au lieu de le dégrader.
- Purge du cache module des artefacts sur les chemins de suppression.
- Popover du chip repositionnée au scroll/resize.
- RegExp d'URL d'artefact hissée hors du chemin chaud du rendu Markdown.
- Toggles de la région latérale : un appui = un effet visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'endpoint /api/oauth/usage expose une 3e sorte de plafond : une
allocation hebdomadaire scopée à UN modèle (aujourd'hui Fable). Elle n'a
aucune clé top-level — tous les candidats (seven_day_opus, tangelo,
nimbus_quill…) sont null et aucun seven_day_fable n'existe — et vit
uniquement dans limits[] sous kind:"weekly_scoped" avec sa valeur sous
`percent` (entier) et un resets_at parfois null.

- PlanUsage gagne `scoped: Vec<ScopedUsageWindow>` (serde(default), donc
  un push Codex qui l'ignore désérialise toujours).
- Qualification sur la PRÉSENCE d'un scope nommé, jamais sur is_active
  (ce flag ne marque que la limite courante et bascule dans le temps —
  cf. mémoire usage-endpoint-limits-is-active).
- Libellé lu depuis scope.model.display_name : data-driven, jamais codé
  en dur, donc un modèle renommé ou ajouté suit sans changement de code.
- Une entrée sans nom ou sans pourcentage est écartée plutôt qu'affichée
  en ligne anonyme à 0 %.
- Affiché dans le popover d'usage après les deux fenêtres globales.
- Codex n'a pas de notion équivalente : liste vide.

Tests : 4 tests Rust dont une capture live du 2026-07-22, 1 test front.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…de console

- .wf-pop-usage-label remplace le sélecteur positionnel
  `.wf-pop-usage-top > span:first-child` : le libellé d'un plafond scopé
  est data-driven (il porte le nom du modèle), donc c'est le seul qui
  puisse être assez long pour pousser le pourcentage hors du popover — il
  DOIT garder son ellipse. Une règle `:first-child` cesserait de
  s'appliquer si la ligne gagnait un élément en tête, silencieusement, et
  la casse ne se verrait que sur un nom long.
- *.log ignoré : dumps console/réseau capturés en vérifiant un
  comportement dans le navigateur (ici, la preuve que la CSP des
  artefacts bloque bien une requête externe). Jetables par nature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La section ## v1.2.0 alimente à la fois le corps de la release GitHub
(release.yml) et les notes affichées in-app : sans cette puce, l'ajout
des plafonds scopés (Fable) serait livré sans apparaître nulle part.

Les correctifs de revue de la PR #39 n'ont volontairement pas d'entrée :
ils corrigent des fonctionnalités de cette même v1.2.0, pas encore
publiée — du point de vue de l'utilisateur il n'y a rien à « corriger »
par rapport à la version précédente.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Alex375
Alex375 merged commit f5ef00b into main Jul 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants