Files
HealthLog/src/components/ui/waiting-text.tsx
T
Marc-André Bombeck dd72634791 Rebuild the Coach thinking row as one calm line
While a turn runs the Coach now shows a single row: a brain icon, the
current step in the answer's text size with a shimmer passing through it
and fading dots, and a chevron once there are steps to open. There is no
running clock any more, and the interim table preview with its sparkline
no longer renders under the row; interim tables stay in state until the
answer's own results show them.

When the answer is done the row reads "Thought process · N steps" with
proper plurals, and "Answer needed" while a clarifying question waits.
It never opens by itself, and a row opened during the run closes when
the turn ends. Opened, the steps form a vertical list with an icon per
kind of work on a thin connector line, and a reasoning round shows its
summary under its title.

The shimmer and the dots are a `text-shimmer` utility and a reusable
WaitingText component, both still under reduced motion.

The demo's write block now carries `meta.errorCode: "demo.readOnly"`,
listed in the error-code catalogue, and the Coach answers it with an
honest line instead of the provider-outage copy.

Removes the interim preview component, the elapsed-seconds and summary
helpers, and their message keys in all seven locales.
2026-10-07 16:55:25 +02:00

51 lines
1.6 KiB
TypeScript

import { cn } from "@/lib/utils";
/** A trailing ellipsis, three dots or the single character. */
const TRAILING_ELLIPSIS = /(?:\.\.\.|…)\s*$/u;
/** The stagger of the three dots, one class each (no inline style). */
const DOT_DELAYS = [
"[animation-delay:0s]",
"[animation-delay:0.2s]",
"[animation-delay:0.4s]",
] as const;
/**
* Text that is still waiting on something: a trailing "..." or "…" becomes
* three dots that fade in turn (`waiting-dot` in `globals.css`), so the line
* reads as alive without a spinner beside it. Text without a trailing
* ellipsis renders as it is. Under reduced motion the dots stand still.
*
* `textClassName` styles the words alone, never the dots: a text effect
* clipped to the glyphs (`text-shimmer`) does not reliably reach a child
* that animates its own opacity, so the dots keep the surrounding colour.
*
* The dots are ordinary full stops, so the text reads and copies as
* "Thinking...". A surface that announces its status elsewhere hides this
* text from assistive technology itself.
*/
export function WaitingText({
text,
textClassName,
}: {
text: string;
textClassName?: string;
}) {
const match = TRAILING_ELLIPSIS.exec(text);
const words = match ? text.slice(0, match.index) : text;
return (
<>
<span className={textClassName}>{words}</span>
{match ? (
<span data-slot="waiting-dots">
{DOT_DELAYS.map((delay) => (
<span key={delay} className={cn("waiting-dot", delay)}>
.
</span>
))}
</span>
) : null}
</>
);
}