Skip to content

BRIC-18: Visual differentiation of thinking, content, and structured response#14

Merged
Kaiohz merged 7 commits into
mainfrom
BRIC-18/multiplexed-content-stream
May 5, 2026
Merged

BRIC-18: Visual differentiation of thinking, content, and structured response#14
Kaiohz merged 7 commits into
mainfrom
BRIC-18/multiplexed-content-stream

Conversation

@Kaiohz
Copy link
Copy Markdown
Contributor

@Kaiohz Kaiohz commented Apr 29, 2026

Jira

BRIC-18

Changes

  • Protocol: SSE client now parses JSON StreamEvent and routes thinking/content/message to separate states
  • Components: Added ThinkingBlock (collapsible, muted, monospace) and StructuredResponseCard (JSON card with copy button)
  • Rendering: Updated MessageList and ChatMessage to show thinking, content, and structured response in distinct visual sections
  • Store: useChatStore now tracks streamingThinking, streamingContent, and structuredResponse

Tests

  • Vite build passes
  • ESLint clean on changed files

@Kaiohz Kaiohz force-pushed the BRIC-18/multiplexed-content-stream branch from 8329f98 to 00edc20 Compare April 29, 2026 12:11
…ed response

- Add StreamEvent type with THINKING/CONTENT/MESSAGE
- Update Message type with thinking field
- Update chatApi to parse JSON SSE events
- Update useChatStore with streamingThinking and structuredResponse
- Add ThinkingBlock component (collapsible, muted, monospace)
- Add StructuredResponseCard component (JSON card, copy button)
- Update MessageList and ChatMessage to render all three sections
- Update useStreamChat to route typed events
@Kaiohz Kaiohz force-pushed the BRIC-18/multiplexed-content-stream branch from 00edc20 to 99d9ff2 Compare April 29, 2026 12:18
Copy link
Copy Markdown
Contributor Author

@Kaiohz Kaiohz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: BRIC-18

📊 Score: 8/10


✅ Points positifs

1. Architecture propre

  • Nouvelle entité domaine StreamEvent avec enum et interface bien typés
  • Séparation claire des couches: domain → infrastructure → application → components
  • Respect de l'architecture hexagonale existante

2. Gestion d'état propre avec Zustand

  • appendStreamEvent route intelligemment les events vers les bons états
  • Reset complet dans clearStream() pour tous les nouveaux états
  • Pattern cohérent avec l'existant

3. Composants bien conçus

  • ThinkingBlock: collapsible, accessible (aria-expanded), UX clean
  • StructuredResponseCard: bouton copy avec feedback visuel, gestion des erreurs JSON
  • Séparation des responsabilités, composants focalisés

4. Rétrocompatibilité

  • Fallback dans chatApi.ts pour traiter les données brutes comme content
  • Migration progressive du streaming

5. UX soignée

  • Thinking en monospace, fond muted (ne parasite pas le contenu)
  • Structured response avec icône data_object, clairement différencié
  • Auto-scroll mis à jour avec les nouvelles dépendances

🔧 Suggestions d'amélioration

1. Méthode non utilisée

// useChatStore.ts - ligne 24
appendStreamThinking: (chunk) => set(...),

Cette méthode n'est jamais appelée - le store utilise appendStreamEvent qui gère le routing en interne. Soit la supprimer, soit l'utiliser dans le hook.

2. Erreur silencieuse dans StructuredResponseCard

// StructuredResponseCard.tsx - lignes 28-30
} catch {
  /* ignore */
}

Même si c'est un cas rare, logguer l'erreur en console.warn aide au debugging.

3. Default case dans appendStreamEvent

default:
  return state;

Ajouter un console.warn('Unknown event type:', ev.type) pour détecter les nouveaux types d'events non gérés.

4. Extraction possible
Le parsing JSON dans appendStreamEvent (case "message") pourrait être extrait dans une fonction utilitaire parseStructuredResponse() pour:

  • Meilleure testabilité
  • Séparation des préoccupations

📋 Checklist avant merge

  • Types bien définis
  • Composants accessibles
  • State management cohérent
  • Backward compatibility
  • Tests unitaires pour appendStreamEvent ?
  • Nettoyer appendStreamThinking non utilisée

🎯 Verdict

Fondations solides, architecture respectée. Quelques nettoyages mineurs (méthode inutilisée, error handling) mais rien de bloquant pour un draft. La feature apporte une vraie valeur UX pour différencier thinking/content/structured response.

Prêt pour review finale après adressage des suggestions mineures.

@Kaiohz Kaiohz marked this pull request as ready for review April 29, 2026 12:20
Copy link
Copy Markdown
Contributor Author

@Kaiohz Kaiohz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review - BRIC-18

✅ Points Positifs

Architecture & Structure

  • Séparation propre des responsabilités: nouveaux composants ThinkingBlock et StructuredResponseCard bien isolés
  • Types TypeScript dans domain/entities/chat/streamEvent.ts - clean et typé
  • Intégration propre dans le store Zustand avec appendStreamEvent qui dispatch selon le type
  • Backward compatibility dans chatApi.ts avec fallback pour les anciens clients SSE

UX/UI

  • ThinkingBlock collapsible avec icône lightbulb - bonne UX pour le contenu secondaire
  • StructuredResponseCard avec bouton copy et état "Copied" - bon UX pattern
  • Styles cohérents avec le design system Material Design 3

Code Quality

  • Imports propres, composants bien typés
  • Gestion des états de stream avec les 3 types d'événements (thinking, content, message)
  • Parsing JSON robuste avec try/catch

⚠️ Points à Améliorer

1. Typage du StructuredResponseCard

// Actuel
interface StructuredResponseCardProps {
  data: unknown;
}

// Suggestion: définir un type plus spécifique
interface StructuredResponseCardProps {
  data: Record<string, unknown> | null;
}

2. Accessibilité du ThinkingBlock
Le bouton a aria-expanded mais manque aria-controls pour lier au contenu collapsible:

<button
  type="button"
  onClick={() => setIsExpanded((prev) => !prev)}
  aria-expanded={isExpanded}
  aria-controls="thinking-content"  // Ajouter
  // ...
>
// ...
<div id="thinking-content" ...>  // Ajouter l'id

3. Dépendances non utilisées?
package-lock.json ajoute @radix-ui/react-accordion, @radix-ui/react-switch, et js-yaml mais je ne vois pas leur utilisation directe dans les composants modifiés. À vérifier s'ils sont nécessaires ou s'ils proviennent d'un autre fichier non inclus dans cette PR.

4. Gestion d'erreur silencieuse dans appendStreamEvent

// Actuel
case "message":
  try {
    const msg = JSON.parse(ev.data);
    return { structuredResponse: msg.structured_response ?? null };
  } catch {
    return state;  // Silencieux - pourrait logger pour debug
  }

Suggestion: ajouter un console.warn en dev pour les parsing errors.

5. État initial du ThinkingBlock
Le bloc est fermé par défaut (isExpanded = false). Si le thinking est important pour l'UX, envisager de l'afficher ouvert pendant le streaming et fermé une fois terminé.


🧪 Tests

⚠️ Absence de tests unitaires pour les nouveaux composants. Suggestion:

  • Test pour ThinkingBlock: vérifier le toggle expand/collapse
  • Test pour StructuredResponseCard: vérifier le bouton copy et l'affichage JSON
  • Test pour appendStreamEvent: vérifier le dispatch des différents types d'événements

📊 Score: 7/10

Critère Note Commentaire
Architecture 8/10 Propre, bien séparée
Typage 7/10 unknown pourrait être plus spécifique
Accessibilité 6/10 Manque aria-controls
Tests 5/10 Pas de tests unitaires
Code Quality 8/10 Clean, bien structuré

🔄 Recommandations

  1. Merge OK avec les points ci-dessus comme tech debt à adresser dans un follow-up
  2. Créer des tickets pour les améliorations d'accessibilité et de tests
  3. Vérifier les dépendances inutilisées dans package.json

Bonne implémentation globale ! 🚀

@Kaiohz
Copy link
Copy Markdown
Contributor Author

Kaiohz commented Apr 29, 2026

🤖 Review automatique de PR

📊 Score de qualité: 7.5/10


✅ Points positifs

  1. Architecture hexagonale respectée

    • Nouvelles entités dans domain/entities/chat/streamEvent.ts
    • Ports mis à jour (chatPort.ts)
    • Infrastructure adaptée (chatApi.ts)
    • Composants UI bien séparés (ThinkingBlock, StructuredResponseCard)
  2. Types TypeScript solides

    • StreamEventType enum pour typer les events
    • StreamEvent interface bien définie
    • Validation runtime avec isValidStreamEvent()
  3. Accessibilité

    • aria-expanded, aria-controls, aria-label présents dans ThinkingBlock
    • aria-live="polite" pour le feedback de copie
    • Boutons avec type="button" explicite
  4. Tests unitaires

    • Nouveaux tests pour appendStreamEvent (thinking, content, message, unknown)
    • Coverage des cas edge
  5. Gestion d'erreur

    • Fallback pour backward compatibility si JSON invalide
    • Validation des events avant traitement
    • Error events gérés avec toast notification
  6. Cleanup

    • Timer cleanup dans StructuredResponseCard via useEffect
    • AbortController cleanup dans useStreamChat

⚠️ Points à améliorer

  1. Dépendance non utilisée

    • @radix-ui/react-accordion est ajouté mais ThinkingBlock n'utilise pas le composant Radix Accordion
    • Le pattern manuel (button + conditional render) fonctionne mais l'accordion installé n'est pas exploité
    • Suggestion: Soit retirer la dépendance, soit migrer vers Radix Accordion/Collapsible pour une meilleure accessibilité
  2. Gestion du default case

    default:
      return state;

    Le default case ne log aucun warning. Un console.warn serait utile pour debug les events inconnus.

  3. Validation des events côté store
    Dans appendStreamEvent:

    if (!ev.type || typeof ev.data !== "string") {
      console.warn("[ChatStore] Invalid stream event:", ev);
      return state;
    }

    C'est bien, mais la validation est dupliquée entre isValidStreamEvent (chatApi) et ce check (store). Pourrait être factorisé.

  4. Timer cleanup edge case
    Dans StructuredResponseCard, le timer est cleanup au unmount, mais si handleCopy est appelé juste avant unmount, le timer pourrait encore s'exécuter. Le timerRef.current = null après clearTimeout dans le cleanup useEffect serait plus propre.

  5. Test avec type assertion

    useChatStore.getState().appendStreamEvent({ type: "unknown" as "content", data: "x" });

    Ce workaround TypeScript ne teste pas vraiment le cas d'un event avec type: "unknown" au runtime (ce qui serait impossible à cause des types). Un test plus réaliste serait de tester avec un event qui a un type valide mais non géré.


🔧 Suggestions d'amélioration

1. Migrer ThinkingBlock vers Radix Collapsible:

import * as Collapsible from "@radix-ui/react-collapsible";
// Utilise le Collapsible installé au lieu du pattern manuel

2. Améliorer le default case:

default:
  console.warn("[ChatStore] Unhandled stream event type:", ev.type);
  return state;

3. Factoriser la validation:
Créer une fonction partagée entre chatApi et store pour valider les events.

4. Timer cleanup robuste:

useEffect(() => {
  return () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  };
}, []);

📈 Verdict

PR solide qui implémente correctement la différenciation visuelle thinking/content/structured response. L'architecture est propre et les tests couvrent les nouveaux cas.

Recommandation: Merge avec les améliorations suggérées en follow-up (cleanup des dépendances inutilisées, amélioration du default case).


🤖 Review générée automatiquement par SoluBot

@Kaiohz
Copy link
Copy Markdown
Contributor Author

Kaiohz commented Apr 29, 2026

Code Review: BRIC-18

📊 Score: 8/10

Une PR solide avec une architecture propre et une bonne séparation des préoccupations. Quelques points à améliorer mais rien de bloquant pour le merge.


✅ Points positifs

  1. Architecture hexagonale respectée - Les entités domain, ports et infrastructure sont bien séparées
  2. Type safety - Bon usage de TypeScript avec enums et interfaces
  3. Accessibilité - ThinkingBlock a les attributs aria appropriés (aria-expanded, aria-controls, aria-live)
  4. Tests mis à jour - Les tests unitaires du store couvrent les nouveaux cas
  5. Error handling - Gestion propre des erreurs avec état d'erreur dans le store et UI dédiée
  6. Fallback robuste - L'API fallback sur CONTENT pour les events inconnus assure la rétrocompatibilité

⚠️ Points à améliorer

1. Validation de type runtime au lieu de compile-time

Le type StreamEvent permet n'importe quelle string pour type. Utiliser un discriminated union serait plus restrictif.

Suggestion:

type StreamEvent =
  | { type: StreamEventType.THINKING; data: string }
  | { type: StreamEventType.CONTENT; data: string }
  | { type: StreamEventType.MESSAGE; data: string }
  | { type: StreamEventType.ERROR; data: string };

2. Console.warn en production

Plusieurs console.warn dans le code store et API qui devraient utiliser un système de logging configurable.

3. ThinkingBlock pas de limite de hauteur

Un thinking très long va prendre tout l'écran. Suggérer un max-height + overflow-y-auto:

<pre className="... max-h-64 overflow-y-auto">
  {text}
</pre>

4. StructuredResponseCard - empty catch blocks

} catch {
  /* ignore */
}

Même si c'est pour le clipboard, un log en dev serait utile.

5. Props inutilisées

  • key={message.timestamp} sur ThinkingBlock dans ChatMessage - les clés devraient être gérées par le parent qui map
  • className prop sur StructuredResponseCard mais jamais utilisée

🎯 Recommandations

  1. Quick win: Ajouter max-height sur ThinkingBlock
  2. MVP acceptable: Les autres points peuvent être adressés dans une PR suivante
  3. Bon pattern à garder: La séparation thinking/content/message est propre et extensible

🔧 Tests automatiques

  • Vite build passes
  • ESLint clean on changed files

Verdict

Approve avec suggestions - Le code est fonctionnel et bien structuré. Les améliorations suggérées sont des polish plutôt que des bloqueurs.

Fonce sur le merge si tu veux, mais considère les height limits sur ThinkingBlock pour l'UX.

Handle "structured" stream events by parsing JSON data into state,
with fallback logging on parse failure.
Copy link
Copy Markdown
Contributor Author

@Kaiohz Kaiohz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Code Review

✅ Points positifs

  • Architecture hexagonale respectée : Nouvelle entité StreamEvent dans domain/entities, mise à jour du port chatPort, et implémentation dans chatApi
  • Accessibilité : Utilisation correcte de aria-expanded, aria-controls, aria-live sur les nouveaux composants
  • Backward compatibility : Fallback intelligent qui traite les données non-JSON comme CONTENT
  • Tests unitaires : Couverture des nouveaux cas appendStreamEvent dans useChatStore.test.ts
  • Séparation des responsabilités : Composants ThinkingBlock et StructuredResponseCard bien isolés

⚠️ Points à améliorer

1. Timer leak potentiel dans StructuredResponseCard

// L148: Le timer n'est pas nettoyé si le composant unmount pendant le setTimeout
useEffect(() => {
  return () => {
    if (timerRef.current) clearTimeout(timerRef.current);
  };
}, []);

Le cleanup nettoie le ref mais le timer peut encore être actif. Correction suggérée :

useEffect(() => {
  return () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  };
}, []);

const handleCopy = async () => {
  // ...
  if (timerRef.current) clearTimeout(timerRef.current);
  timerRef.current = setTimeout(() => {
    timerRef.current = null;
    setCopied(false);
  }, 1500);
};

2. Incohérence styling dans ThinkingBlock

// L30: Utilisation de inline style au lieu de Tailwind
style={{ transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)' }

Suggestion :

className={cn(
  "material-symbols-outlined text-base text-secondary-brand transition-transform",
  isExpanded && 'rotate-180'
)}

📊 Score : 8/10

Code propre, bien structuré, qui suit les patterns existants du projet. Les points d'amélioration sont mineurs (cleanup timer, cohérence styling).


✅ Prêt à merger après correction mineure du timer leak.

🤖 Review par SoluBot

@Kaiohz Kaiohz merged commit 211359e into main May 5, 2026
1 check passed
@Kaiohz Kaiohz deleted the BRIC-18/multiplexed-content-stream branch May 5, 2026 19:30
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