diff --git a/messages/de.json b/messages/de.json index aaa5c0714..7a8f945a7 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Daten, die der Coach sieht", "settingsClustersHint": "Wähle, welche Datencluster der Coach lesen darf. Mehr Daten bedeuten reichhaltigere Antworten und einen größeren Prompt.", "settingsClusterNoData": "Noch keine Daten", - "sourcesMemberCount": "{count} Werte" + "sourcesMemberCount": "{count} Werte", + "dictate": "Diktieren", + "dictateStop": "Diktat beenden", + "showConversations": "Unterhaltungen anzeigen", + "hideConversations": "Unterhaltungen ausblenden" }, "relativeJustNow": "gerade eben", "relativeMinutesAgoOne": "vor {count} Minute", diff --git a/messages/en.json b/messages/en.json index 7eb1d9c5b..6262c25cb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Data the Coach sees", "settingsClustersHint": "Pick which clusters of your data the Coach can read. More data means richer answers and a larger prompt.", "settingsClusterNoData": "No data yet", - "sourcesMemberCount": "{count} metrics" + "sourcesMemberCount": "{count} metrics", + "dictate": "Dictate", + "dictateStop": "Stop dictation", + "showConversations": "Show conversations", + "hideConversations": "Hide conversations" }, "relativeJustNow": "just now", "relativeMinutesAgoOne": "{count} minute ago", diff --git a/messages/es.json b/messages/es.json index 63c332c30..1c2de3bf1 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Datos que ve el Coach", "settingsClustersHint": "Elige qué grupos de datos puede leer el Coach. Más datos significan respuestas más ricas y un prompt mayor.", "settingsClusterNoData": "Aún no hay datos", - "sourcesMemberCount": "{count} métricas" + "sourcesMemberCount": "{count} métricas", + "dictate": "Dictar", + "dictateStop": "Detener el dictado", + "showConversations": "Mostrar conversaciones", + "hideConversations": "Ocultar conversaciones" }, "relativeJustNow": "ahora mismo", "relativeMinutesAgoOne": "hace {count} minuto", diff --git a/messages/fr.json b/messages/fr.json index 5758907bf..98cdb7079 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Données vues par le Coach", "settingsClustersHint": "Choisissez les groupes de données que le Coach peut lire. Plus de données, des réponses plus riches et un prompt plus grand.", "settingsClusterNoData": "Pas encore de données", - "sourcesMemberCount": "{count} mesures" + "sourcesMemberCount": "{count} mesures", + "dictate": "Dicter", + "dictateStop": "Arrêter la dictée", + "showConversations": "Afficher les conversations", + "hideConversations": "Masquer les conversations" }, "relativeJustNow": "à l’instant", "relativeMinutesAgoOne": "il y a {count} minute", diff --git a/messages/it.json b/messages/it.json index 688f8c7f9..837b62f0b 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Dati che il Coach vede", "settingsClustersHint": "Scegli quali gruppi di dati il Coach può leggere. Più dati significano risposte più ricche e un prompt più grande.", "settingsClusterNoData": "Ancora nessun dato", - "sourcesMemberCount": "{count} metriche" + "sourcesMemberCount": "{count} metriche", + "dictate": "Detta", + "dictateStop": "Interrompi dettatura", + "showConversations": "Mostra conversazioni", + "hideConversations": "Nascondi conversazioni" }, "relativeJustNow": "proprio ora", "relativeMinutesAgoOne": "{count} minuto fa", diff --git a/messages/pl.json b/messages/pl.json index 05addbcd7..ddc529b75 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2264,7 +2264,11 @@ "settingsClustersLabel": "Dane widziane przez Coacha", "settingsClustersHint": "Wybierz, które grupy danych Coach może czytać. Więcej danych to bogatsze odpowiedzi i większy prompt.", "settingsClusterNoData": "Brak danych", - "sourcesMemberCount": "{count} wskaźników" + "sourcesMemberCount": "{count} wskaźników", + "dictate": "Dyktuj", + "dictateStop": "Zatrzymaj dyktowanie", + "showConversations": "Pokaż rozmowy", + "hideConversations": "Ukryj rozmowy" }, "relativeJustNow": "przed chwilą", "relativeMinutesAgoOne": "{count} minutę temu", diff --git a/src/components/insights/coach-panel/__tests__/coach-drawer-mobile-trays.test.tsx b/src/components/insights/coach-panel/__tests__/coach-drawer-mobile-trays.test.tsx index d46c0e857..da352e3ac 100644 --- a/src/components/insights/coach-panel/__tests__/coach-drawer-mobile-trays.test.tsx +++ b/src/components/insights/coach-panel/__tests__/coach-drawer-mobile-trays.test.tsx @@ -58,19 +58,34 @@ describe(" — rail-tray strip", () => { expect(html).not.toContain('data-slot="coach-drawer-sources"'); }); - it("mounts an inline history column on lg+ when historyRail is passed (page surface)", () => { + it("mounts a collapsible inline history column on lg+ when historyRail is passed (page surface)", () => { + // v1.18.7 W-coach C-UI — the inline rail is now collapsible and + // collapsed by default; the page surface passes `onToggleHistory`. + // The sub-lg tray trigger keeps `lg:hidden` (the bottom tray covers + // small viewports); a separate lg+ toggle (`coach-history-toggle`) + // shows the rail on desktop. const html = render( - history} />, + history} + onToggleHistory={() => undefined} + />, ); expect(html).toMatch( /]*data-slot="coach-drawer-history"[^>]*lg:flex/, ); expect(html).toContain("history"); - // The strip button collapses on lg+ where the inline column shows. + // Sub-lg tray trigger collapses on lg+ (the inline rail covers it). const historyTrigger = html.match( /]*data-slot="coach-drawer-history-tray-trigger"[^>]*>/, ); expect(historyTrigger?.[0]).toContain("lg:hidden"); + // lg+ inline-rail toggle is present and labelled as a show control. + const railToggle = html.match( + /]*data-slot="coach-history-toggle"[^>]*>/, + ); + expect(railToggle).not.toBeNull(); + expect(railToggle?.[0]).toContain('aria-expanded="false"'); }); it("renders the message thread + composer slots without a disclaimer line", () => { diff --git a/src/components/insights/coach-panel/__tests__/coach-input.test.tsx b/src/components/insights/coach-panel/__tests__/coach-input.test.tsx index 5fb65b000..b59b383c0 100644 --- a/src/components/insights/coach-panel/__tests__/coach-input.test.tsx +++ b/src/components/insights/coach-panel/__tests__/coach-input.test.tsx @@ -11,21 +11,31 @@ function render(node: React.ReactNode, locale: "en" | "de" = "en") { } describe("", () => { - it("mounts the textarea + send button slots (mic dropped in W5)", () => { + it("mounts the textarea + send button slots", () => { // v1.4.22 B4 — the disclaimer moved to the sources rail; the // composer no longer renders its own paragraph below the input. - // v1.4.25 W5 — the non-functional mic icon was removed. const html = render( {}} onSubmit={() => {}} />, ); expect(html).toContain('data-slot="coach-input"'); expect(html).toContain('data-slot="coach-input-textarea"'); expect(html).toContain('data-slot="coach-input-send"'); - expect(html).not.toContain('data-slot="coach-input-mic"'); expect(html).not.toContain('data-slot="coach-input-disclaimer"'); expect(html).not.toContain("Coach replies are generated"); }); + it("does not render the mic in SSR markup (Web Speech API is client-only)", () => { + // v1.18.7 W-coach C-UI — voice dictation returns, but the mic only + // mounts client-side once `SpeechRecognition` is confirmed present + // (a post-hydration effect flips support on). The SSR / unsupported + // path renders no button so the affordance is never a dead control. + const html = render( + {}} onSubmit={() => {}} />, + ); + expect(html).not.toContain('data-slot="coach-input-mic"'); + expect(html).not.toContain("Voice input arrives with the iOS app"); + }); + it("renders the localised placeholder without the retired shortcut hint", () => { // v1.16.1 — the Enter/Shift+Enter hint (prose footer, later an // Info-popover) is gone entirely; the behaviour itself is @@ -48,18 +58,6 @@ describe("", () => { expect(html).toContain("Frag mich etwas zu deinen Daten"); }); - it("no longer renders a mic button (W5 removed the placeholder)", () => { - // v1.4.25 W5 — the mic icon used to be rendered + disabled with a - // "voice arrives with iOS" tooltip. The maintainer flagged it as a click- - // trap: nothing happened on tap. The composer now drops the icon - // entirely; voice input remains a v1.5 iOS feature. - const html = render( - {}} onSubmit={() => {}} />, - ); - expect(html).not.toMatch(/data-slot="coach-input-mic"/); - expect(html).not.toContain("Voice input arrives with the iOS app"); - }); - it("disables the send button when the value is empty", () => { const html = render( {}} onSubmit={() => {}} />, diff --git a/src/components/insights/coach-panel/coach-conversation.tsx b/src/components/insights/coach-panel/coach-conversation.tsx index 5b229441f..bcda26d32 100644 --- a/src/components/insights/coach-panel/coach-conversation.tsx +++ b/src/components/insights/coach-panel/coach-conversation.tsx @@ -126,6 +126,10 @@ export function CoachConversation({ >(null); const [historyTrayOpen, setHistoryTrayOpen] = useState(false); const [sourcesTrayOpen, setSourcesTrayOpen] = useState(false); + // v1.18.7 (W-coach C-UI) — page surface only: the inline conversation + // rail is collapsed by default for a calm, prompt-first surface. The + // rail-tray-strip toggle (lg+) opens it; the rail heading closes it. + const [historyRailOpen, setHistoryRailOpen] = useState(false); const [inputValue, setInputValue] = useResettableValue(prefill ?? ""); // v1.16.4 — self-context backflow: `pendingAdopt` raises a quiet // offer to fold a clarifying-question answer back into the @@ -359,6 +363,12 @@ export function CoachConversation({ setHistoryRailOpen((open) => !open) + : undefined + } historyRail={ surface === "page" ? ( void; onHistoryClick: () => void; onOpenSourcesTray: () => void; } @@ -46,49 +68,75 @@ export function CoachDrawerBody({ thread, composer, historyRail, + historyOpen = false, + onToggleHistory, onHistoryClick, onOpenSourcesTray, }: CoachDrawerBodyProps) { const { t } = useTranslations(); + // The page surface (inline `historyRail`) carries the collapsible + // grid; without a rail the body is a single column (drawer surface). + const railExpanded = !!historyRail && historyOpen; return (
- {/* Inline history column — page surface, lg+ only. */} + {/* Inline history column — page surface, lg+ only, collapsible. */} {historyRail && ( )} @@ -96,41 +144,72 @@ export function CoachDrawerBody({ {/* Centre — message thread. */}
{/* v1.4.27 R3d MB2 — rail-tray triggers lifted out of the absolute overlay into a sub-header strip so the buttons sit at a 44 px tap target and never overlay the first message bubble. - v1.18.1 (W-COACH-UI C4) — fixed `h-14` so the strip's - bottom border aligns with the history rail heading divider - in the adjacent column (one continuous line, no restless - two-height split). */} + v1.18.7 — fixed `h-14` so the strip's bottom border aligns + with the history rail heading divider in the adjacent column. + On the page surface the left control is the rail TOGGLE + (clean show/hide) on lg+, falling back to the tray trigger + below lg; the drawer keeps the plain conversations button. */}
` (px-4) all share the same horizontal divider - // line at sm+ rather than stepping in/out by a pixel. className="border-border/70 flex h-14 shrink-0 items-center justify-between gap-2 border-b px-3 sm:px-4" > - + {/* Left control(s). On the page surface (inline rail) the + affordance forks by breakpoint: below lg a tray trigger + opens the bottom sheet; on lg+ a clean toggle shows/hides + the inline rail (and the lg+ toggle hides once the rail is + expanded, since the rail heading then owns the close + control). Without an inline rail (drawer surface) the + single button hands off via `onHistoryClick`. */} + {historyRail && onToggleHistory ? ( + <> + {/* Sub-lg: open the bottom tray. */} + + {/* lg+: toggle the inline rail (hidden while expanded). */} + + + ) : ( + + )} {/* Sources toggle — the rail is hidden by default on every viewport and opens as the right-edge tray. */} + )}