From ffc458e4778c8c3cc977c70a104bc28e201bf427 Mon Sep 17 00:00:00 2001 From: Blomios Date: Wed, 8 Jul 2026 08:38:16 +0200 Subject: [PATCH] docs(memory): notes projet du sprint UI rework et des tickets #7/#14/#16/#17/#18/#25/#28 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capitalise les notes durables produites pendant le sprint UI rework et les tickets livrés depuis. Commit séparé du code : `.ideai/memory/` est le store durable versionné, il ne doit jamais être mélangé aux commits de feature. Co-Authored-By: Claude Opus 4.8 --- .ideai/memory/MEMORY.md | 14 ++++ .../floatingwindow-focus-trap-mount-only.md | 11 ++++ ...cket14-local-lan-openai-adapter-scoping.md | 64 +++++++++++++++++++ ...16-menus-floating-delivered-devfrontend.md | 29 +++++++++ .../ticket17-focus-trap-fix-merged-develop.md | 9 +++ .../ticket18-picker-delivered-devfrontend.md | 28 ++++++++ ...et25-assistant-sandbox-eacces-rootcause.md | 11 ++++ .../memory/ticket28-f1-frontend-delivered.md | 17 +++++ .../ticket28-firstrun-detect-hang-scoping.md | 19 ++++++ ...-f2-delegated-limit-no-agentratelimited.md | 16 +++++ ...ework-sprint-delivered-tickets-21-22-23.md | 19 ++++++ .../ui-rework-sprint-scoping-contracts.md | 23 +++++++ ...-rework-sprint-tickets-21-22-23-scoping.md | 39 +++++++++++ 13 files changed, 299 insertions(+) create mode 100644 .ideai/memory/floatingwindow-focus-trap-mount-only.md create mode 100644 .ideai/memory/ticket14-local-lan-openai-adapter-scoping.md create mode 100644 .ideai/memory/ticket16-menus-floating-delivered-devfrontend.md create mode 100644 .ideai/memory/ticket17-focus-trap-fix-merged-develop.md create mode 100644 .ideai/memory/ticket18-picker-delivered-devfrontend.md create mode 100644 .ideai/memory/ticket25-assistant-sandbox-eacces-rootcause.md create mode 100644 .ideai/memory/ticket28-f1-frontend-delivered.md create mode 100644 .ideai/memory/ticket28-firstrun-detect-hang-scoping.md create mode 100644 .ideai/memory/ticket7-f2-delegated-limit-no-agentratelimited.md create mode 100644 .ideai/memory/ui-rework-sprint-delivered-tickets-21-22-23.md create mode 100644 .ideai/memory/ui-rework-sprint-scoping-contracts.md create mode 100644 .ideai/memory/ui-rework-sprint-tickets-21-22-23-scoping.md diff --git a/.ideai/memory/MEMORY.md b/.ideai/memory/MEMORY.md index 8adb35b..d886c0c 100644 --- a/.ideai/memory/MEMORY.md +++ b/.ideai/memory/MEMORY.md @@ -59,3 +59,17 @@ - [checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending](checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending.md) — memory note checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending - [workstate-background-tasks-projection-fix](workstate-background-tasks-projection-fix.md) — Décision d'archi pour rendre visibles Cancel/Retry dans le panneau Work — extension du read-model GetProjectWorkState, contrat DTO et borne de livraison. - [tickets-t3-frontend-validation-verdict](tickets-t3-frontend-validation-verdict.md) — Verdict de validation frontend du fix T3 (projection backgroundTasks par agent) sur feature/background-tasks-first-class — vert, avec 3 désalignements de contrat mineurs. +- [ticket4-restart-on-develop-ebd992e](ticket4-restart-on-develop-ebd992e.md) — Cadrage du redémarrage des annonces inter-agent sur la base pré-canonique develop ebd992e — ce qui existe, l'écart live, la réutilisabilité du backup et le découpage B/F. +- [ticket4-overlay-composition-leafview](ticket4-overlay-composition-leafview.md) — Règle de composition des deux voiles plein-cellule de LeafView (write-portal injection PTY vs F3 annonces busy) — exclusion mutuelle avec priorité F3, arbitrée pour éviter le double voile. +- [ticket7-f2-delegated-limit-no-agentratelimited](ticket7-f2-delegated-limit-no-agentratelimited.md) — Écart backend ticket #7/F2 : le rendez-vous délégué n'émet pas AgentRateLimited pour la cible limitée. +- [ui-rework-sprint-scoping-contracts](ui-rework-sprint-scoping-contracts.md) — Frontières hexagonales et contrats figés du sprint UI rework — les 4 tickets sont frontend-purs, la popup TicketPicker #18, l'échelle z-index et le curseur opaque. +- [ticket18-picker-delivered-devfrontend](ticket18-picker-delivered-devfrontend.md) — memory note ticket18-picker-delivered-devfrontend +- [ticket16-menus-floating-delivered-devfrontend](ticket16-menus-floating-delivered-devfrontend.md) — memory note ticket16-menus-floating-delivered-devfrontend +- [floatingwindow-focus-trap-mount-only](floatingwindow-focus-trap-mount-only.md) — Le focus-trap de FloatingWindow ne doit dépendre que du mount ; sinon un onClose instable vole le focus à chaque frappe. +- [ticket17-focus-trap-fix-merged-develop](ticket17-focus-trap-fix-merged-develop.md) — Correctif de la perte de focus dans l'édition de ticket (#17) livré sur develop via feature branch dédiée. +- [ui-rework-sprint-tickets-21-22-23-scoping](ui-rework-sprint-tickets-21-22-23-scoping.md) — memory note ui-rework-sprint-tickets-21-22-23-scoping +- [ui-rework-sprint-delivered-tickets-21-22-23](ui-rework-sprint-delivered-tickets-21-22-23.md) — memory note ui-rework-sprint-delivered-tickets-21-22-23 +- [ticket25-assistant-sandbox-eacces-rootcause](ticket25-assistant-sandbox-eacces-rootcause.md) — Root cause et contrat de fix du EACCES (os error 13) au spawn de la CLI d'un assistant de ticket — le preset SandboxPlan::project_read_only handle la classe exec Landlock. +- [ticket14-local-lan-openai-adapter-scoping](ticket14-local-lan-openai-adapter-scoping.md) — memory note ticket14-local-lan-openai-adapter-scoping +- [ticket28-firstrun-detect-hang-scoping](ticket28-firstrun-detect-hang-scoping.md) — Cadrage hexagonal du bug bloquant first-run (#28) — panic block_on imbriqué dans detect + découplage busy/détection, contrats et point de vérité QA. +- [ticket28-f1-frontend-delivered](ticket28-f1-frontend-delivered.md) — Lot F1 du ticket #28 livré sur feature/ticket28-firstrun-detect-hang — invariant busy/detectProfiles, flag detecting, timeout UI, test de non-régression. diff --git a/.ideai/memory/floatingwindow-focus-trap-mount-only.md b/.ideai/memory/floatingwindow-focus-trap-mount-only.md new file mode 100644 index 0000000..6958c1c --- /dev/null +++ b/.ideai/memory/floatingwindow-focus-trap-mount-only.md @@ -0,0 +1,11 @@ +--- +name: floatingwindow-focus-trap-mount-only +description: Le focus-trap de FloatingWindow ne doit dépendre que du mount ; sinon un onClose instable vole le focus à chaque frappe. +metadata: + type: reference +--- +Le primitif `frontend/src/shared/ui/FloatingWindow.tsx` a un `useEffect` focus-trap qui appelle `(first ?? node)?.focus()`. Sa dépendance doit rester `[]` (mount-only), et `onClose` doit être lu via un `onCloseRef` maintenu à jour. + +**Pourquoi** : les appelants (ex. `TicketDetail.requestClose`) passent souvent un `onClose` recréé à chaque render. Si l'effet dépend de `[onClose]`, chaque frappe dans un input contrôlé de la fenêtre → re-render → nouvelle identité `onClose` → l'effet se ré-arme → focus renvoyé au premier focusable → l'utilisateur ne peut taper qu'UNE lettre à la fois (bug #17, corrigé 2026-07-07). + +**Comment l'appliquer** : ne jamais remettre `onClose` (ni une autre prop changeante) dans les deps de cet effet ; router les callbacks via une ref. Régression couverte par le test « #17 focus-loss bug » dans FloatingWindow.test.tsx. Voir [[ticket16-menus-floating-delivered-devfrontend]]. \ No newline at end of file diff --git a/.ideai/memory/ticket14-local-lan-openai-adapter-scoping.md b/.ideai/memory/ticket14-local-lan-openai-adapter-scoping.md new file mode 100644 index 0000000..72824a9 --- /dev/null +++ b/.ideai/memory/ticket14-local-lan-openai-adapter-scoping.md @@ -0,0 +1,64 @@ +--- +name: ticket14-local-lan-openai-adapter-scoping +description: memory note ticket14-local-lan-openai-adapter-scoping +metadata: + type: project +--- +# Ticket #14 — cadrage figé : adapter IA local/LAN OpenAI-compatible canonique + +Périmètre figé (review agent-user faite) : canal = **adapter HTTP natif** parlant une API compatible OpenAI/Ollama ; usage **transparent** vs Claude/Codex (mêmes cellules, conversation headless canonique, historique, reprise, délégation, vivacité) ; **PUREMENT ADDITIF** (zéro régression Claude Code / Codex CLI) ; **parité tool-calling/MCP** dès ce ticket avec **dégradation propre** si le modèle n'expose pas les outils. + +## 0. Asymétrie structurelle (commande TOUT le design) +| | Claude/Codex (existant) | Modèle local HTTP (#14) | +|---|---|---| +| Nature | binaire CLI spawné | serveur HTTP appelé | +| Boucle agentique | conduite par la CLI | conduite par **IdeA** | +| Rôle MCP d'IdeA | serveur ; CLI = client | pas de client ⇒ IdeA = orchestrateur d'outils | +| Contexte .md | la CLI lit son convention file | injecté en **system message** par l'adapter | +| Conversation | id moteur (session_id/thread_id) | API **stateless** ⇒ transcript possédé par IdeA | +Conséquence : le nouvel adapter **n'utilise PAS** `session/process.rs` (run_turn/spawn/drain). Il ne partage avec claude.rs/codex.rs que les **types de port** (`AgentSession`, `ReplyEvent`). Cette absence de code partagé mutable EST la garantie d'isolation. + +## 1. Cartographie hexagonale +**Domaine (crates/domain)** +- Variante `StructuredAdapter::OpenAiCompatible` (profile.rs:244). `provider_key()` ⇒ `"openai-compatible"` (contrat persistance providers.json). Sérialise camelCase `"openAiCompatible"`. +- Nouveau VO validé (parse-don't-validate) `HttpChatConfig { endpoint:String (http/https non vide), model:String (non vide), api_key_env:Option (nom de var env valide, JAMAIS la clé), request_timeout_ms:Option, connect_timeout_ms:Option, max_tool_iterations:Option (garde-fou boucle, défaut ~16) }`. Le domaine ne résout jamais la clé (pas d'I/O env), il porte le **nom** de variable. +- Rattachement `AgentProfile.chat_http: Option` avec `#[serde(default, skip_serializing_if="Option::is_none")]` (miroir exact de `mcp`/`liveness`/`rate_limit_pattern`, profile.rs:570-604) ⇒ **zéro régression de sérialisation** (profils Claude/Codex bit-identiques). Test round-trip obligatoire (miroir profile_without_mcp_round_trips_identically). + +**Nouveau port tool-calling (le seam clé — le pont MCP .mcp.json/config.toml + bridge `idea mcp-server` NE s'applique pas, pas de client CLI)** +```rust +pub struct ToolSpec { name:String, description:String, input_schema:serde_json::Value } +#[async_trait] pub trait ToolInvoker: Send+Sync { + fn tools(&self) -> Vec; + async fn call(&self, name:&str, args_json:&str) -> Result; +} +``` +Implémenté en **app-tauri** en déléguant à la **même** `OrchestratorService::dispatch` (orchestrator/service.rs:1082) que le endpoint MCP ⇒ parité délégation/rendez-vous (idea_ask_agent/idea_reply), gating permissions/sandbox conservé. Injecté dans la factory via `with_tool_invoker(Arc)` (jumeau de `with_sandbox_enforcer`). `None` ⇒ tool-calling désactivé proprement (chat nu). + +**Infra** : nouveau fichier `crates/infrastructure/src/session/openai_compat.rs` (jumeau structurel de codex.rs SANS process). Client HTTP **reqwest + rustls-tls** (jamais native-tls — spike AppImage §13-2, confiné au crate infra). Parsing isolé pur `parse_chat_delta`/`parse_completion` (miroir de parse_event) — seul endroit qui connaît le schéma OpenAI (choices[].message, tool_calls[], SSE data:). + +**Routing — seul contact avec l'existant** : `StructuredSessionFactory::start` (factory.rs:111) = **UN bras de match ajouté** ; bras Claude/Codex INTOUCHÉS ; `supports()` reste `profile.is_selectable()` (=structured_adapter.is_some(), profile.rs:857) ⇒ sélectionnable sans changer le prédicat. + +## 2. Contrat de session headless +`send(prompt)` : 1) transcript.push(user) ; 2) émettre Heartbeat ; 3) POST {endpoint}/chat/completions {model, messages:transcript, tools?:invoker.tools(), stream:true} ; 4) si tool_calls ⇒ pour chaque: ToolActivity{label=name} + result=invoker.call(name,args) + push(assistant tool_call)+push(tool result), reboucler (borné max_tool_iterations) ; 5) sinon streamer chunks ⇒ TextDelta (+tap→Announcement, cf claude.rs:337) ; 6) réponse complète ⇒ push(assistant) + **Final{content}** ; 7) persister transcript run dir. Contrat de flux universel respecté (un seul Final terminal) ⇒ send_blocking/drain_with_readiness marchent tels quels. + +**Erreurs (jamais de corps HTTP brut propagé)** : endpoint injoignable 1er contact/probe ⇒ `AgentSessionError::Start` (UI erreur, IdeA non bloqué) ; réseau/coupure en tour ⇒ `Io` ; JSON illisible/schéma ⇒ `Decode` (diagnostic court) ; timeout ⇒ `Timeout` via send_blocking, session non tuée ; modèle absent (404/400) ⇒ `Start` dédié. + +**Reprise sans mélange d'ids (invariant du ticket)** : /chat/completions **stateless** ⇒ AUCUN id provider ⇒ `conversation_id()` retourne **None** (rien à mélanger). État = transcript provider-shaped (roles+tool_calls) **possédé par IdeA**, persisté dans le **run dir stable** `.ideai/run//chat-transcript.json` (clé = agent, jamais id provider). Reprise = ré-instanciation factory ⇒ rechargement transcript ; `seed_conversation_id` (factory.rs:63) **ignoré** par cet adapter (documenter). DISTINCT du conversation-log LS6 (log.jsonl, dérivé ReplyEvent, surface humaine, inchangé) — deux artefacts, aucun mélange. + +**Contexte** : `start` reçoit `ctx:&PreparedContext` ; `ctx.content` (ports.rs:120) = Markdown rendu ⇒ injecté comme premier message **system** (le serveur HTTP ne lit pas de convention file). + +## 3. Seam tool-calling / dégradation +Parité : ToolInvoker câblé ⇒ modèle local voit idea_* via la même OrchestratorService (délégation/rendez-vous/contexte/mémoire/tickets identiques, gating conservé). Dégradation 3 niveaux : (1) ToolInvoker None ⇒ chat nu ; (2) profil opte sans outils ⇒ idem ; (3) endpoint rejette param `tools` ⇒ détecter, retirer tools, rejouer chat nu, 1 diagnostic — l'agent converse toujours, sans déléguer. Garde-fou max_tool_iterations (modèles locaux moins fiables) ⇒ au plafond, Final + note. + +## 4. Lots B/F +Backend : **B1** domaine (variante+provider_key, VO HttpChatConfig, champ chat_http, port ToolInvoker/ToolSpec/ToolInvocationError ; tests sérialisation zéro-régression, round-trip, invariants ; zéro I/O). **B2** adapter infra openai_compat (reqwest/rustls, parse_* purs, mapping ReplyEvent+erreurs, résolution clé env ; tests fixtures OpenAI/Ollama, erreurs via wiremock, pas de fuite payload). **B3** boucle outils bornée + persistance/rechargement transcript run-dir (reprise) + dégradation tools non supporté (tests ToolInvoker fake, reprise=rechargement, plafond). **B4** routing (bras match) + composition root (with_tool_invoker, impl ToolInvoker→OrchestratorService) + profil de référence "Ollama / OpenAI-compatible local model" éditable (tests factory route, supports true, probe indispo→Start). +Frontend : **F1** types TS HttpChatConfig + champ chatHttp? + adapter "openAiCompatible". **F2** wizard/settings : sélectionnable comme Claude/Codex, form endpoint/model/apiKeyEnv/timeouts, validation miroir backend (first-run/profile.ts isValidEnvVar, URL, non-vide), apiKeyEnv = nom de var jamais clé. **F3** cellule = chemin chat structuré existant (dérivé de is_selectable, "gratuit" si DTO respecté) + erreur "endpoint indisponible" propre sans bloquer UI (tests RTL gateways mock). +Frontière B↔F : **DTO AgentProfile** (déjà le pont IPC) porte structuredAdapter:"openAiCompatible" + chatHttp{endpoint,model,apiKeyEnv?,requestTimeoutMs?,connectTimeoutMs?,maxToolIterations?}. Sélectionnabilité + rendu chat dérivés du DTO existant, pas de nouveau canal ni commande Tauri au cœur (le seed du profil de référence peut passer par le CRUD profils existant — à confirmer B4/F3). + +## 5. Vigilance régression / invariants +1. Zéro régression sérialisation (skip_serializing_if=Option::is_none ; round-trip Claude/Codex bit-identique — BLOQUANT). 2. Isolation code : nouvel adapter = fichier neuf ; INTERDIT de toucher claude.rs/codex.rs/process.rs ; seul diff existant = 1 bras match factory.rs + champs optionnels domaine. 3. Contrat flux : exactement un Final terminal ; Heartbeat/ToolActivity/TextDelta/RateLimited non terminaux (conformance.rs doit couvrir le nouvel adapter). 4. Frontière domaine : reqwest UNIQUEMENT infra ; aucun type HTTP en domaine/application ; ToolInvoker port pur. 5. Nouvelle dépendance AppImage : reqwest rustls-tls (pas OpenSSL, spike §13-2). 6. Non-mélange ids : conversation_id()=None ; transcript clé-agent ; distinct de LS6. 7. Sandbox : pas de process spawné ⇒ SandboxEnforcer OS = no-op pour le tour (pas une régression) mais les outils via ToolInvoker gardent le gating orchestrateur. + +## 6. Décisions produit remontées à Main (NON tranchées par Architect) +1. Surface d'outils v1 : parité totale immédiate vs sous-ensemble curaté (fiabilité tool-calling des modèles locaux). Reco : parité + garde-fou max_tool_iterations. +2. Streaming SSE dès v1 (reco, parité observabilité live) vs non-streaming (repli si endpoint ne stream pas). +3. Nom canonique variante : `OpenAiCompatible` (reco, décrit le protocole ; provider_key figé) vs `LocalChat` (formulation ticket). \ No newline at end of file diff --git a/.ideai/memory/ticket16-menus-floating-delivered-devfrontend.md b/.ideai/memory/ticket16-menus-floating-delivered-devfrontend.md new file mode 100644 index 0000000..f111d7e --- /dev/null +++ b/.ideai/memory/ticket16-menus-floating-delivered-devfrontend.md @@ -0,0 +1,29 @@ +--- +name: ticket16-menus-floating-delivered-devfrontend +description: memory note ticket16-menus-floating-delivered-devfrontend +metadata: + type: project +--- +# Lot A / #16 — MenuBar + FloatingWindow livré (DevFrontend) + +Branche `feature/ui-menus-floating` (base develop 0218e32), non commité, avant merge Git. `vitest` **vert** : 54 fichiers / 527 tests. `tsc --noEmit` exit 0. + +## UX pass validée utilisateur (barre unique + sélecteur projet) — APPLIQUÉE +- **Barre unique** : plus de MenuBar séparée dans App.tsx. Une seule MenuBar dans `ProjectsView` regroupant `[ Projet: ▾ ] File ▾ View ▾ Settings ▾`. +- **Sélecteur de projet permanent** : premier menu de la barre, label = projet actif, items = tous les projets connus (`vm.projects`) → `selectProject(id)` (active l'onglet si ouvert, sinon `openProject`). Accessible même sous projet actif, sans ouvrir File→Projects…. +- **Settings → AI Profiles** déplacé dans la barre unique : toggle `showSettings` LOCAL à ProjectsView ; le main area swap vers `` pendant que la barre reste visible (permet de refermer). App.tsx ne gère plus showSettings ni ProfilesSettings (juste firstRun ? wizard : ProjectsView). + +## Fichiers (état final) +- **Créés (lot A initial)** : `shared/ui/FloatingWindow.tsx`, `shared/ui/MenuBar.tsx`, `shared/ui/FloatingWindow.test.tsx`. +- **Modifiés** : `shared/index.ts`, `shared/ui/zIndex.ts` (réutilisé), `app/App.tsx` (barre + showSettings + ProfilesSettings retirés), `features/projects/ProjectsView.tsx` (barre unique : projectMenu+File+View+Settings, showSettings local, ProfilesSettings dans main), `features/projects/projects.test.tsx` (+2 tests : sélecteur projet, toggle AI Profiles), `features/projects/ProjectsView.ls7.test.tsx`. + +## Invariants conservés +- FloatingWindow (focus-trap+Échap+backdrop, z=floatingWindow 50), MenuBar dropdowns z=menuDropdown 40, toasts z=toast 70, zIndex canonique réutilisé. +- TOUS les panneaux atteignables via **View** (context, work, tickets, agents, templates, skills, perms, memory, git) + File→Projects…. Critère d'acceptation respecté. +- Non touché : `shouldShowWritePortalVeil`, exclusion write-portal↔F3, overlays in-cell, TicketPicker (#18). + +## Notes +- ProfilesSettings rendu DANS le main de ProjectsView (barre + onglets projet restent visibles au-dessus) au lieu du swap plein-écran App précédent — meilleure continuité, barre unique toujours accessible. +- Pas de run live Tauri (couverture RTL mock-driven). DoD vitest satisfaite. + +QA/utilisateur revalident avant merge Git. \ No newline at end of file diff --git a/.ideai/memory/ticket17-focus-trap-fix-merged-develop.md b/.ideai/memory/ticket17-focus-trap-fix-merged-develop.md new file mode 100644 index 0000000..44a6746 --- /dev/null +++ b/.ideai/memory/ticket17-focus-trap-fix-merged-develop.md @@ -0,0 +1,9 @@ +--- +name: ticket17-focus-trap-fix-merged-develop +description: Correctif de la perte de focus dans l'édition de ticket (#17) livré sur develop via feature branch dédiée. +metadata: + type: reference +--- +Bug #17 : saisie une lettre à la fois dans le formulaire d'édition de ticket, cause = focus-trap `FloatingWindow` avec `useEffect` dépendant de `[onClose]` (closure recréée à chaque render). Fix DevFrontend : `onCloseRef` + effet focus-trap mount-only (`[]`). + +Topologie Git : branche `feature/ticket17-focus-trap-fix` depuis `develop`, commit `4cccd40` (2 fichiers frontend seulement), merge `--no-ff` → `develop` = `2f8467e`, branche supprimée. Vert : tsc clean, 531/531 + test de régression. Voir [[ticket16-menus-floating-delivered-devfrontend]], [[ui-rework-sprint-scoping-contracts]]. \ No newline at end of file diff --git a/.ideai/memory/ticket18-picker-delivered-devfrontend.md b/.ideai/memory/ticket18-picker-delivered-devfrontend.md new file mode 100644 index 0000000..c3a944d --- /dev/null +++ b/.ideai/memory/ticket18-picker-delivered-devfrontend.md @@ -0,0 +1,28 @@ +--- +name: ticket18-picker-delivered-devfrontend +description: memory note ticket18-picker-delivered-devfrontend +metadata: + type: project +--- +# Lot B / #18 — TicketPicker livré (DevFrontend) + +Branche `feature/ticket-picker-popup` (base develop d97abb1). `vitest` **vert** : 53 fichiers / 515 tests, dont 8 nouveaux (`TicketPicker.test.tsx`) + 29 `tickets.test.tsx` inchangés (valide la non-régression du listing après extraction). `tsc --noEmit` clean. + +## Fichiers +- **Créés** : `shared/ui/zIndex.ts`, `features/tickets/TicketFacetsBar.tsx`, `features/tickets/useTicketSearch.ts`, `features/tickets/TicketPicker.tsx`, `features/tickets/TicketPicker.test.tsx`. +- **Modifiés** : `shared/index.ts` (export zIndex), `features/tickets/index.ts` (barrel), `features/tickets/TicketsPanel.tsx` (consomme `TicketFacetsBar`). + +## Écarts de contrat à trancher (signalés à Main) +1. **Emplacement TicketPicker** : placé sous `features/tickets/`, PAS `shared/ui`, car il dépend du domaine ticket + gateways DI (shared/ui = design system pur). Conforme à tous les autres overlays (SprintManager, MemoryEditor…). Seul `zIndex.ts` (niveau design-system) est dans shared/ui. +2. **`TicketPickerResult.id`/`number`** : `ticket_list`/`TicketSummary` ne portent NI `id` NI `number`. Résolus par un `ticket_read` unique **à la sélection** (`useTicketSearch.resolve`). `number` serait dérivable du ref mais `id` exige la lecture. → contrat `{ref,id,number,title,status,priority}` respecté sans nouveau endpoint. Écart backend candidat : ajouter id/number au summary (dette basse). +3. **`zIndex.ts` provisoire** : lot A (#16) introduit le canonique en parallèle. Valeurs alignées sur l'échelle figée (menuDropdown=40, floatingWindow=50, floatingWindowNested=60, toast=70). **À réconcilier au merge** (garder un seul module). Picker monté à `floatingWindowNested=60`. + +## Notes d'impl +- G3-bis respecté : `cursor` traité en token opaque (jamais construit/incrémenté ; relayé verbatim via `loadMore`). +- `excludeRefs` filtré client-side après réception de page. +- `useTicketSearch` léger (pas de groupement sprint, pas de souscription events, debounce 200ms) — ne réutilise pas `useTickets`. +- `TicketFacetsBar` = recherche + facettes statut/priorité (labels aria préservés : `search tickets`, `filter status …`, `filter priority …`, `clear filters`). Le select assignee reste dans `TicketsPanel` (hors périmètre picker). +- Focus-trap inline (Tab cycling + Escape + restore focus) car aucun util partagé n'existe ; G5 respecté (montage niveau chrome par le consommateur #17/#19). +- `selectionMode:"multi"` câblé de façon extensible (footer réservé) mais non actif en V1. + +QA repasse derrière. Consommateurs #17 (link) et #19 (sprint) importent `TicketPicker` depuis `@/features/tickets`. \ No newline at end of file diff --git a/.ideai/memory/ticket25-assistant-sandbox-eacces-rootcause.md b/.ideai/memory/ticket25-assistant-sandbox-eacces-rootcause.md new file mode 100644 index 0000000..b3b0f90 --- /dev/null +++ b/.ideai/memory/ticket25-assistant-sandbox-eacces-rootcause.md @@ -0,0 +1,11 @@ +--- +name: ticket25-assistant-sandbox-eacces-rootcause +description: Root cause et contrat de fix du EACCES (os error 13) au spawn de la CLI d'un assistant de ticket — le preset SandboxPlan::project_read_only handle la classe exec Landlock. +metadata: + type: reference +--- +**Symptôme** : ouvrir un assistant de ticket (Codex ou Claude), premier message ⇒ `process error: agent session start failed: : Permission non accordée (os error 13)`. Indépendant de la CLI. + +**Root cause** : `OpenTicketAssistant::execute` (`application/src/ticket_assistant.rs:97`) fabrique un plan bespoke via `SandboxPlan::project_read_only` (`domain/src/sandbox.rs:153`) = grant **RO** sur `project_root` + RW sur le run_dir, posture `Deny`. Dans l'adapter Landlock (`infrastructure/src/sandbox/landlock.rs:70`), un grant RO *handle* la classe read, et `AccessFs::from_read(V1)` **inclut `AccessFs::Execute`**. Le binaire CLI vit hors du project root ⇒ `execve` refusé ⇒ EACCES au `spawn()` sandboxé (`process.rs:248`). Le chemin workspace n'a pas le bug car son plan vient de `compile_sandbox_plan` sous posture `Allow`/write-only ⇒ l'adapter no-op ou ne handle que write, read/exec restent ouverts. Piège déjà documenté dans l'adapter (« enrichment is the launch-path's concern LP4-2 »). + +**Fix (backend-pur, zéro port/DTO)** : remplacer le preset par un **write-fence** — ne jamais handle read/exec (pas de grant RO), handle write seul, RW-grant = run_dir + state/home CLI + temp, posture Deny ⇒ écriture projet refusée, binaire/libs/home lisibles-exécutables. Fallback sûr : passer `None` à `factory.start` pour la surface ticket (garde policy MCP + projection advisory). Garde-fous fonctionnels réels = policy MCP `idea_ticket_read/update*` + LP3, pas l'OS-sandbox. Lié à [[ui-rework-sprint-scoping-contracts]]. \ No newline at end of file diff --git a/.ideai/memory/ticket28-f1-frontend-delivered.md b/.ideai/memory/ticket28-f1-frontend-delivered.md new file mode 100644 index 0000000..9881171 --- /dev/null +++ b/.ideai/memory/ticket28-f1-frontend-delivered.md @@ -0,0 +1,17 @@ +--- +name: ticket28-f1-frontend-delivered +description: Lot F1 du ticket #28 livré sur feature/ticket28-firstrun-detect-hang — invariant busy/detectProfiles, flag detecting, timeout UI, test de non-régression. +metadata: + type: reference +--- +Lot **F1** (frontend) du bug #28 implémenté sur `feature/ticket28-firstrun-detect-hang` (non committé, Git s'en charge). + +**Invariant posé** : dans `frontend/src/features/first-run/useFirstRun.ts`, le retour de `busy` à `false` ne dépend d'aucun appel à `detectProfiles`. `busy` n'entoure plus que `firstRunState()` (reload) et `configureProfiles()` (finish). Toute future régression qui remet un `await profile.detectProfiles(...)` sur un chemin gouvernant `busy` re-gèle le wizard. + +**Mécanisme** : `runDetection(candidates, { preselect, reportError })` factorise auto-détection et bouton manuel. Le flag `detecting` est relâché par un `setTimeout(DETECT_TIMEOUT_MS = 3_000)`, **jamais** par la promesse — car le `finally` d'un `await` sur une promesse jamais settled (panic Rust ⇒ pas de réponse IPC) n'est jamais atteint. Un compteur `detectRun` invalide les rounds périmés (re-clic, unmount). + +**Piège UI à retenir** : `