Skip to content

Feat/board customization#13

Merged
D4kooo merged 39 commits into
mainfrom
feat/board-customization
Jun 2, 2026
Merged

Feat/board customization#13
D4kooo merged 39 commits into
mainfrom
feat/board-customization

Conversation

@D4kooo
Copy link
Copy Markdown
Contributor

@D4kooo D4kooo commented Jun 2, 2026


Titre :
feat: fiabilité (coûts, citations, RAG, audit, a11y) + Board personnalisable par agent

Description :

Contexte

main est à #12 (passe a11y/theming/perf). Ce PR apporte deux blocs
cohérents construits par-dessus l'ancêtre commun : la fiabilisation de
l'app (coûts, sources, intégrité documentaire, audit, robustesse), puis la
personnalisation du Board. Les versions non-squashées d'onboarding/a11y
déjà présentes dans main se réconcilient via l'ancêtre commun.


Partie 1 — Fiabilité, sources & robustesse

  • Coûts & contrôle d'exécution : usage agrégé réel + message-metadata,
    Stop qui coupe vraiment l'appel LLM côté serveur (abortSignal),
    estimation coût/appels au point de dépense, agent_runs rattachés au
    message avec modelId par agent, quota mensuel visible.
  • Citations & auditabilité : citations Légifrance/Pappers cliquables
    (durcissement scheme http(s)), trail multi-agents persisté dans
    messages.parts (théâtre/badges/skills survivent au reload), export JSON
    du trail, pills « compétences appliquées ».
  • Intégrité RAG : purge des chunks de versions périmées, comptage scopé
    par utilisateur/projet, détection PDF scanné, transparence
    indexé/non-indexé + réindexation, import multi-fichiers, diff de versions
    de document (LCS maison).
  • Analyses tabulaires : export CSV (anti-injection de formule),
    ré-extraction ciblée, ajout de documents, format de colonne, timeout.
  • Configuration self-host : relabel honnête des connecteurs, tests de
    connexion providers/connecteurs, checklist de configuration, prix des
    modèles, auto-sync MCP, feedback des toggles, fallback OVH.
  • Le Bureau : agents Rédacteur + Légifrance, canvas honnête
    (handles masqués, drag séquentiel only), allowlist d'outils en multi-select,
    live panel conscient des tours + fallback synthèse (positions brutes
    si le synthétiseur échoue), vue-liste mobile.
  • Robustesse & conformité & a11y : filets loading/error/not-found,
    journal d'audit exploitable (filtres/pagination/export CSV+JSON), palette
    ⌘K accessible, focus-ring harmonisé, source de nav unique, primitives
    EmptyState/temps relatif partagées, contraste des micro-labels.
  • Vocabulaire : « Bureau » → Board, « Workflows » → Trames.

Partie 2 — Board personnalisable

  • RAG par agent : colonne pipeline_agents.rag_scope (null = hérite →
    zéro régression). resolveAgentRag passe un ToolScope déjà restreint à
    buildToolsForUser sans modifier buildToolsForUser ni search.ts. Règle
    de sécurité intersection, jamais union. Modes hérite / aucun / projet /
    dossiers / documents, section « Sources » dans la Sheet + badge sur le nœud.
  • Changement de rôle in-place (fini le « supprimer + recréer »).
  • Température par agent (slider 0–2 ou défaut provider).
  • Ordre d'exécution explicite : panneau liste numérotée (flèches ↑/↓
    clavier-accessibles) + badge « répond en dernier », dans les 3 modes.
  • Accès permanent à la Salle de délibération depuis chaque message.

Migration BDD

Après merge : npm run db:push (convention du repo). Ajoute deux colonnes
nullable sur pipeline_agents : rag_scope (jsonb) et temperature
(double precision). Aucune donnée à backfiller, zéro régression.

Vérification

tsc --noEmit ✓ · npm run lint 0 · vitest run 117/117 (dont 12 sur
le resolver RAG + le fallback synthèse) · npm run build ✓.

Réserve — validation live

Validé en live : RAG par agent (un agent scopé sur un dossier ne lit que ce
périmètre), citation de source. Restent à confirmer sur un vrai run
multi-tours : le rendu SSE du fallback synthèse et le libellé de tour qui avance.

D4kooo added 30 commits May 29, 2026 12:53
Composant réutilisable <ModuleHelp> : petit bouton (i) près du titre d'un
module ouvrant un popover (aide contextuelle courte + lien « En savoir
plus » vers la doc publique). URL surchargeable via NEXT_PUBLIC_DOCS_URL.

Posé sur Dashboard (prise en main), Settings → Providers, Settings →
Connecteurs, Documents et l'écran « nouvelle conversation » du chat.

Ajoute deux guides utilisateur : prise en main (onboarding 5 étapes) et
travailler par projet. Le bouton de la page Projets suivra (fichier en
cours de refonte).
Systematic audit-driven fixes across 7 module groups (UI surface),
verified with tsc + lint + production build (all green).

Criticals:
- Chat: scope the streaming live-region (was re-announcing every token)
- Board: gate infinite edge animation + node pulse behind reduced-motion;
  add text/icon status badges (was color-only)
- Settings: drop invalid CutoutCard role="button" wrapping nested
  interactives; add explicit "Configurer" trigger + hoist delete dialog

Systemic:
- aria-current on active nav (sidebar / settings / admin)
- Spinner primitive: role=status on wrapper, sr-only label, motion-safe
- form helper text wired via aria-describedby
- citation highlight -> --highlight token (was hardcoded yellow)
- list/table semantics: ul/li, scope, caption, role=progressbar, dl metrics
- reduced-motion gating on spring/infinite animations
- dead "Bientot" controls removed; emoji -> @tabler icons
- print footer contrast; admin stats no longer hidden on mobile

Bugs:
- broken oklch(oklch()) vignette gradient (rendered nothing) -> color-mix
- hardcoded black MiniMap maskColor -> token (dark-mode correct)

Perf:
- React.memo on assistant markdown rows via stable onOpenDoc, so historical
  messages stop re-rendering/re-linkifying on every streamed token
Choix du dossier de stockage à la création d'un projet ; en chat-projet le RAG ne voit que les documents du sous-arbre du dossier + l'historique des conversations du projet. Table message_chunks (embeddings 1024d, index hnsw), projects.folderId, lib/projects/scope.

Note: le câblage chat/orchestrateur/outils qui consomme ce scope (route.ts, orchestrator, tools.ts, upload) est intriqué avec le sprint prod-ready et committé avec les phases P1/P3 sur ces fichiers partagés.
…y P1)

- R1 usage réel agrégé par message (somme tous agents) + message-metadata émise → pill coût, page usage, quota et URL ?id= fonctionnels
- R2 abortSignal req.signal → streamText : « Stop » coupe le pipeline serveur ; withRetry ignore AbortError ; pas de sauvegarde de réponse partielle
- H8 estimation coût/appels au point de dépense (composer + CTA Essayer), helper estimateCalls/estimateRunCost centralisé, mode-bar refactorée
- H9 agent_runs rattachés au message (messageId) avec modelId/providerType par agent
- H22 quota mensuel visible au membre (page usage + dashboard) via getMonthlySpendCents partagé avec l'enforcement
- tests cost-estimate + non-retry AbortError (vitest 77 → 91)
- R9 $count scopé userId/projet : plus de fuite cross-tenant de search_documents
- R7 purge des chunks des versions périmées au remplacement : l'IA ne cite plus de texte obsolète
- H17 détection PDF scanné → extractionStatus=failed + message OCR clair
- R6 correction des copies « RAG arrive en v0.3 » (le RAG est en production)
…eady P3 R6)

- badge par document : indexé (N segments) / non indexé / clé Mistral manquante
- action « Réindexer » par document (recovery après ajout de clé ou échec)
- bouton « Réindexer tout » dans l'en-tête
- helper réutilisable reindexDocument (ownership, idempotent)
…t projet réel, cascade (P3 🟠)

- H16 bouton Importer multi-fichiers + rejets drag-drop (type/taille) surfacés au lieu d'être ignorés silencieusement
- H18 « Déplacer vers projet » déplace réellement le document dans le dossier du projet (entre dans le périmètre RAG), plus seulement le projectId d'affichage
- H20 le dialog de suppression de dossier avertit de la cascade des sous-dossiers (compte récursif)
…ady P2 R3)

Les résultats legifrance_search / pappers_search / pappers_get s'affichent en cartes citation cliquables (titre + source + lien externe, rel=noopener) au lieu d'une pill grise « Terminé » qui jetait les URLs. Re-rendu à l'identique au reload (les tool-results sont déjà persistés). Tient la promesse « Louis cite ses sources ».

Sécurité (défense en profondeur) : les URLs proviennent d'API externes (PISTE/Pappers) → validation du schéma http(s) via safeHttpUrl avant tout href ; fallback non-cliquable sinon (jamais de javascript:/data: dans un href).
…3a/H3b/H5)

- H3a persiste les data parts du trail (agent-event/output/retry + skills) dans messages.parts → le théâtre, les badges d'étapes et les compétences survivent au reload ; output capé à 12k/agent
- H3b getConversationAuditTrail (ownership) : runs groupés par message (messageId rattaché via P1)
- H5 export du trail en JSON (par message : agents, rôles, modèles, tokens, latence, statut, erreurs) depuis le menu de conversation
Consomme la part data-skills-detected (live + persistée via H3a) et affiche des pills « Compétences appliquées : X » au-dessus du composer. Mapping slug→libellé fourni par page.tsx (getEnabledSkills). Rien affiché si aucune skill détectée.
…cées, notif de fin (P4)

- H14 export CSV (route GET ownership-checkée, BOM UTF-8 + séparateur ; + échappement, Content-Disposition attachment) + item « Exporter en CSV » dans le menu
- H15-e le format de colonne (date/money/boolean/liste) est désormais injecté dans la consigne d'extraction
- H15-f les lignes « running » abandonnées (after() interrompu) au-delà de 5 min sont requalifiées et redeviennent relançables
- H15-d toast « Extraction terminée » (transition running→fini, une seule fois, sans faux positif au chargement) + annonce aria-live
…nts (P4 H15-a/b/c)

- H15-a rerunReviewRow : ré-extraire une ligne (toutes colonnes) depuis la grille (desktop + mobile)
- H15-b rerunReviewColumn : « Ré-extraire » dans le popover colonne après édition du prompt ; extractRow merge désormais (préserve les autres colonnes)
- H15-c addReviewDocuments + dialog : ajouter des documents à une analyse existante (docs indexables pas déjà présents), promesse « en ajouter plus tard » tenue
- R4 PISTE n'annonce comme « débloqué » que Légifrance ; Judilibre/JADE/INPI/BODACC déclassés en « à venir » (carte + dialog)
- H23 la bibliothèque de modèles affiche le prix entrée/sortie par M de tokens (« prix inconnu » à défaut, jamais de faux gratuit)
…elf-host (P5 R9'/R5)

- R9' checklist de mise en route STATEFUL (provider→modèle→connecteur opt.→chat), reflète l'état réel ; remplace la bannière basée sur le nombre de conversations
- R5 (providers) « Tester la connexion » est activé dès qu'un baseUrl est configuré (Scaleway/OVH/Albert/OpenAI-compatible self-host), plus de menu grisé muet — l'action passait déjà le baseUrl de la clé
Action testConnectorKey + fonctions testPisteConnection (OAuth) / testPappersConnection (requête minimale). Item « Tester la connexion » dans la carte connecteur + toast (connecté / auth refusée / non configuré / injoignable) + badge « Dernier test » persistant (lastTestStatus). Comble le trou : un cabinet entrait ses identifiants PISTE sans aucun feedback jusqu'ici.
…/H24)

- H26 OVH ne renvoie plus une erreur 501 dans la bibliothèque : retourne la liste curée locale (plus de cul-de-sac), hint « liste curée »
- H24 un serveur MCP est synchronisé automatiquement à la création (best-effort, n'échoue pas la création) + la carte affiche la liste nominative des outils découverts, pas seulement le compte
…alette Cmd+K (P7 R8/H27)

- R8 ajoute les filets manquants : skeleton de chargement (motion-safe), error boundary brandée avec reset, 404 FR brandée, global-error (use client + html/body, styles inline). Plus d'écran Next brut.
- H27 DialogTitle/Description déplacés DANS DialogContent → la palette Cmd+K a un nom accessible (aria-labelledby) et ne déclenche plus le warning Radix
… /board dans la palette (P7 VOCAB/H29)

- Décision #8 : libellés UI seulement (sidebar, palette, titres de page, menus composer, panel live, copies models). Routes /board et /workflows inchangées (pas de lien cassé).
- H29 (partiel) : Board ajouté au groupe Navigation de la palette Cmd+K.
Décision #6 : DraftingAgent (génère/édite actes & mémoires) et LegifranceAgent (sourcing Légifrance) sont implémentés (prompts + allowlist d'outils) et enregistrés dans AGENT_REGISTRY. Les rôles drafting/legifrance sont désormais proposables dans l'add-dialog (plus de retombée silencieuse sur DefaultAgent). Test du registre mis à jour.
…équentiel only (P7 H7)

Les poignées de connexion (qui ne connectent rien, nodesConnectable=false) sont rendues invisibles/non-interactives (conservées pour l'ancrage des edges). Le drag ne ré-ordonne plus qu'en mode séquentiel — en council/parallel la position n'a aucun effet sur l'exécution, le drag y était trompeur. Reste (H7) : vue-liste verticale sur mobile (décision #5).
… export CSV/JSON (P7 H21)

- labels d'action centralisés (lib/audit/labels) et réutilisés sur la page audit ET la fiche utilisateur (plus de slug brut)
- filtres action + plage de dates + réinitialisation, pagination (50/page), total réel (plus de cap dur à 200)
- colonne meta (IP/user-agent/motif…) désormais rendue
- export CSV (BOM + anti-injection) et JSON via /api/admin/audit/export (requireAdmin catché → 403, pas 500 ; filtres respectés)
…le=status) (P7 H28/A11Y)

- harmonise les focus-ring des inputs/boutons custom sur ring-3 (cohérent avec les primitives shadcn) — 9 occurrences
- le feedback transitoire de la fiche utilisateur admin passe en role=status aria-live=polite (annoncé aux lecteurs d'écran)
Remplace le champ texte libre (où une typo donnait un agent sans outil, silencieusement) par un sélecteur : « Tous les outils » (null) vs « Sélection » (cases à cocher des outils réellement disponibles — connecteurs actifs + génération docs + RAG + MCP synchronisés, calculés côté serveur). Un outil d'une allowlist héritée mais indisponible est listé et signalé « indisponible ». « Aucun » = sélection vide.
- src/lib/format/time.ts : formatRelativeFr unique (avant : deux copies
  divergentes dashboard/admin), nullLabel paramétrable.
- src/components/empty-state.tsx : EmptyState réutilisable (carte
  pointillée + titre + corps + action), uniformise les traitements ad hoc.
- Câblage : dashboard, admin/users, admin/audit, workflows ; suppression
  des fonctions/JSX locaux dupliqués.
Les labels à text-[9px] cumulaient une taille minuscule ET un
affaiblissement de contraste (opacity-70 sur du muted-foreground hérité,
text-foreground/50, text-destructive/70) → sous le seuil WCAG AA 4.5:1.

- admin/users : StatCell + « Ce mois » → 10px, muted-foreground franc
  (suppression du double-mute opacity-70).
- chat/model-picker : label souveraineté → 10px, foreground/70.
- chat/chat-shell : labels Avant/Après du diff → 10px, couleur pleine.
Les switches actif/inactif (connecteur, provider, MCP) appelaient l'action
sans await ni vérification : un échec serveur (ligne disparue, erreur DB)
laissait le toggle muet — l'utilisateur croyait avoir activé une intégration
qui restait inactive.

- Les trois toggle*Active renvoient désormais ActionResult (erreur si
  introuvable, try/catch sur l'update) au lieu de void.
- Côté client : await + toast.error(message) en cas d'échec. Le Switch
  étant piloté par l'état serveur, il ne bascule que sur succès+revalidate
  (pas de faux positif optimiste à annuler).
La barre latérale et la palette de commandes maintenaient deux listes
parallèles des mêmes destinations — chaque renommage VOCAB (« Bureau » →
« Board », « Workflows » → « Trames »…) devait être répliqué et finissait
par diverger en libellé, ordre et icône.

- src/lib/navigation.ts : PRIMARY_NAV (source unique) + type NavItem.
- sidebar-content : consomme PRIMARY_NAV (suppression du tableau local et
  des 7 imports d'icônes devenus inutiles).
- command-palette : PAGES = [...PRIMARY_NAV, ...SETTINGS_PAGES] ; les
  réglages granulaires propres à la palette restent locaux.
- mobile-nav réutilisait déjà SidebarContent — couvert sans changement.
Diff ligne-à-ligne du texte extrait entre une version antérieure et la
version courante, accessible depuis l'historique d'un document.

- src/lib/diff/line-diff.ts : LCS maison (sans dépendance) avec trim
  préfixe/suffixe commun, plafond DP (bloc remplacé au-delà), repli des
  plages identiques en marqueurs « gap ». 12 tests unitaires.
- getDocumentVersionDiff : action sécurisée (même propriétaire + même
  famille de versions), payload borné (MAX_DIFF_OPS).
- version-diff-dialog : bouton « Comparer » par version antérieure +
  rendu vert/rouge avec compteur +/− et bannière de troncature.
Trois manques du panneau live d'un conseil multi-agents :

1. Fallback synthèse — si le synthétiseur échoue, l'orchestrateur émettait
   emitError + throw : l'utilisateur perdait TOUTE la délibération. Désormais
   on sert les positions brutes (avec avertissement « non arbitrées / non
   vérifiées ») via de vraies parts texte — la seule voie effectivement
   rendue ET persistée par route.ts (data-final-text n'est consommé nulle
   part). Couvre council ET parallel. Test unitaire : synthé qui throw →
   pas de throw, texte de repli non vide contenant les positions.

2. Conscience des tours — le panneau affiche « Tour N/M » en council
   multi-tours (round déjà présent dans les events, désormais consommé).

3. Retry reflété — une carte d'agent relancé passe en « nouvelle
   tentative N… » dans le panneau flottant (déjà le cas dans le badge).

⚠️ Vérifié statiquement (tsc/lint/tests/build + test orchestrateur du
fallback). Le rendu SSE live (texte de repli, libellé de tour qui avance
en cours de run) reste à confirmer sur un run réel.
D4kooo added 9 commits June 1, 2026 21:52
Le canvas React Flow est inutilisable au doigt sous 640px. On ressuscite
PipelineBoard (vue verticale : agent terminal mis en avant, collaborateurs
empilés en une colonne) en fallback mobile, et on réserve le canvas au
desktop (sm+).

- pipeline-board : props enabledModels + availableTools transmises à
  l'AgentEditSheet (édition d'agent fonctionnelle sur mobile aussi),
  team en colonne unique.
- board/[id]/page : swap responsive (hidden sm:block / sm:hidden) +
  AddAgentDialog au positionnement responsive (statique sous la liste en
  mobile, flottant sur le canvas en desktop).
Chaque agent d'une pipeline peut désormais avoir sa propre portée
documentaire RAG, indépendante du périmètre de la conversation.

- schéma : colonne pipeline_agents.rag_scope (jsonb, null = inherit →
  zéro régression) + type AgentRagScope (inherit/none/project/folders/
  documents ; folders/documents câblés en Lot 1b).
- resolveAgentRag (agents/rag-scope.ts) : résout la portée d'un agent à
  partir de sa ragScope et du périmètre conversation, SANS toucher
  buildToolsForUser ni search.ts (on leur passe un ToolScope déjà
  restreint). Invariant : intersection avec le périmètre conversation,
  jamais extension. Mode none → documentIds vidés + masquage des outils
  de lecture documentaire (couvre aussi la conversation globale).
- câblage base.ts + default.ts ; mapping repository ; action
  updatePipelineAgent (zod discriminatedUnion).
- UI : section « Sources documentaires (RAG) » dans agent-edit-sheet
  (toggle Périmètre conversation / Aucun document).
- 7 tests resolver (inherit/project/none, projet vs global, omit tools).

Migration : via db:push (convention du repo) — colonne rag_scope nullable.

Vérifié : tsc, lint, 112 tests, build prod OK.
- resolveAgentRag : modes folders/documents par INTERSECTION stricte avec
  le périmètre projet de la conversation (jamais d'extension). documents =
  intersection pure (0 requête) ; folders = getDocsInFolders (sous-arbres
  récursifs, filtré userId) puis intersection. Hors conversation projet :
  repli global sûr (borné par le filtre userId de search.ts).
- getDocsInFolders + getAgentSourceOptions dans projects/scope.ts (dossiers
  en ordre DFS avec profondeur, documents avec flag indexé via chunks).
- UI : section « Sources » à 4 modes (Select) — Périmètre conversation /
  Aucun / Dossiers choisis (cases indentées) / Documents choisis (cases +
  badge « non indexé »). Texte d'aide rappelant l'intersection projet.
- Plomberie availableFolders/availableDocuments : page → PipelineWorkflow
  + PipelineBoard (mobile) → AgentEditSheet.
- 5 tests resolver supplémentaires (intersection, doc hors projet, global).

Vérifié : tsc, lint, 117 tests, build prod OK.
Le nœud d'agent signale sa portée RAG quand elle est restreinte :
« RAG désactivé » (none), « lit : N dossiers » (folders), « lit : N docs »
(documents). Aucun badge pour le cas par défaut (hérite du périmètre) afin
de garder le canvas lisible. → Lot 1 (RAG par agent) complet.

Vérifié : tsc, lint, 117 tests, build prod OK.
Avant, le rôle d'un agent était figé (« supprimer + recréer » pour en
changer). Désormais un Select de rôle dans la Sheet permet de basculer
Assistant/Recherche/Légifrance/Citateur/Rédacteur/Relecteur/Maestro sans
détruire l'agent (label, modèle, prompt, outils, portée RAG conservés).

- agent-role-meta : export AGENT_ROLES (liste ordonnée).
- updatePipelineAgent : accepte role (z.enum des rôles connus).
- Sheet : Select de rôle en tête, méta (icône/pitch/header) réactive au
  rôle choisi, avertissement quand le rôle change (prompt factory + outils
  par défaut affectés, prompt perso conservé).

Vérifié : tsc, lint, 117 tests, build prod OK.
Chaque agent peut fixer sa propre température d'échantillonnage (ex.
Relecteur factuel à 0.2, Rédacteur créatif à 0.8), ou hériter du défaut
du provider.

- schéma : colonne pipeline_agents.temperature (double precision, null =
  défaut provider). AgentDefinition.temperature + mapping repo.
- streamText (base.ts + default.ts) : temperature: def.temperature ??
  undefined (undefined = défaut provider, comportement inchangé).
- updatePipelineAgent : temperature (z.number 0–2, nullable).
- UI Sheet : toggle Défaut / Personnalisée + slider natif 0–2 (libellés
  précis/créatif).

Migration : via db:push (colonne nullable). Vérifié : tsc, lint, 117
tests, build prod OK.
L'ordre ne se réglait que par drag horizontal sur le canvas, en mode
sequential uniquement, et l'agent terminal n'était jamais désigné
clairement (juste « le dernier par position »).

- execution-order-panel : liste numérotée réordonnable via flèches
  haut/bas (clavier-accessible), indépendante de la géométrie du canvas,
  affichée dans les TROIS modes. Badge « répond en dernier » sur l'agent
  terminal. Texte expliquant la sémantique du mode (chaîne / conseil /
  parallèle). Persiste via reorderPipelineAgents ; resync optimiste.
- Le drag canvas (sequential) reste disponible en complément.

Vérifié : tsc, lint, 117 tests, build prod OK.
…essage

Le théâtre n'était ouvrable que depuis le panneau live transitoire : une
fois la réponse finie (ou au message suivant), le panneau disparaissait et
la délibération devenait inaccessible — alors que les données sont
persistées dans les parts du message.

- Le théâtre est désormais piloté par theatreMessageId (l'id du message à
  afficher) → ouvrable pour N'IMPORTE quel message multi-agents passé, pas
  seulement le dernier. theatreTurns calculé pour le message ciblé.
- Bouton « Voir le débat » permanent sous le bandeau d'agents de chaque
  message multi-agents terminé (en plus du panneau live pendant le run).

Vérifié : tsc, lint, 117 tests, build prod OK.
…tomization

Conflits résolus en faveur de notre branche (sur-ensemble) : elle contient
déjà le travail onboarding/a11y que dataring a squashé (#4/#12), plus le
sprint et la personnalisation du Board. Aucune fonctionnalité dataring perdue
(les lignes divergentes étaient les versions antérieures de lignes que nos
commits ont fait évoluer : focus-ring ring-2→ring-3, copie « v0.3 » corrigée,
FolderRow/auto-refresh enrichis).

Vérifié post-merge : tsc, lint, 117 tests, build prod OK.
@D4kooo D4kooo merged commit a47210a into main Jun 2, 2026
5 checks passed
@D4kooo D4kooo deleted the feat/board-customization branch June 2, 2026 10:15
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.

1 participant