fix(frontend,harness): phase-stepper wiring, central tail, activity-panel markdown
- WorkflowBar: center the phase pills (add justify-center).
- tht-gate.js: the gate was sending the descriptive phase name (e.g.
"chiarimento") instead of the workflow.yaml id ("F1") in the widget's
`phase` field, so the frontend's F1..F8 match never hit and pills never
lit up. Rename phaseName -> phaseId, return p.id.
- CentralStatus: expand the live "working" tail from a single truncated
line to up to 5 lines (line-clamp-5 safety net for unbroken paragraphs).
- ModelActivityPanel: render the streamed tail through react-markdown +
remark-gfm instead of raw per-line <p> tags, so emphasis/headings render
and blank-line paragraph breaks (previously stripped) are preserved;
tail-cut now operates on paragraphs instead of physical lines.
Updated tests accordingly (WorkflowBar/CentralStatus/ModelActivityPanel/
AppShell.session-mgmt); full frontend suite (98/98) + harness gate node
tests (34/34) + tsc -b pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,25 +1,33 @@
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useSessionStore } from "../store/sessionStore";
|
||||
import { Button } from "../components/ui/button";
|
||||
|
||||
const TAIL_LINES = 5;
|
||||
const TAIL_PARAGRAPHS = 5;
|
||||
|
||||
/** Left drawer showing "what's happening": the tail of the model's streamed output.
|
||||
* Collapsed it shows the last few lines (refreshing as the stream grows); the expand
|
||||
* toggle reveals the full stream. Opened on demand from the rotating activity icon. */
|
||||
/** Left drawer showing "what's happening": the tail of the model's streamed markdown
|
||||
* output, rendered as markdown (not raw text) so headings/emphasis/lists come through
|
||||
* and paragraphs stay visually separated. Collapsed it shows the last few paragraphs
|
||||
* (refreshing as the stream grows); the expand toggle reveals the full stream. Opened
|
||||
* on demand from the rotating activity icon. */
|
||||
export function ModelActivityPanel({ onClose }: { onClose: () => void }) {
|
||||
const transcript = useSessionStore((s) => s.transcript);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const lines = transcript
|
||||
// Blank lines are markdown's paragraph separator — split on them (not on every "\n",
|
||||
// which would also break single soft-wrapped lines apart) so each block below is a
|
||||
// real paragraph and the tail cut lands on paragraph boundaries.
|
||||
const paragraphs = transcript
|
||||
.map((e) => e.text)
|
||||
.join("\n")
|
||||
.split("\n")
|
||||
.map((l) => l.trimEnd())
|
||||
.filter((l) => l.trim() !== "");
|
||||
const shown = expanded ? lines : lines.slice(-TAIL_LINES);
|
||||
const canExpand = lines.length > TAIL_LINES;
|
||||
.join("\n\n")
|
||||
.split(/\n{2,}/)
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p !== "");
|
||||
const shown = expanded ? paragraphs : paragraphs.slice(-TAIL_PARAGRAPHS);
|
||||
const canExpand = paragraphs.length > TAIL_PARAGRAPHS;
|
||||
const source = shown.join("\n\n");
|
||||
|
||||
return (
|
||||
<aside className="flex w-[380px] shrink-0 flex-col border-r border-border bg-sidebar">
|
||||
@@ -46,10 +54,8 @@ export function ModelActivityPanel({ onClose }: { onClose: () => void }) {
|
||||
{shown.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No activity yet.</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1 font-mono text-xs leading-relaxed text-foreground/90">
|
||||
{shown.map((l, i) => (
|
||||
<p key={i} className="whitespace-pre-wrap break-words">{l}</p>
|
||||
))}
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none text-foreground/90">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user