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:
2026-07-02 15:53:31 +02:00
co-authored by Claude Sonnet 5
parent 7c417d4cf1
commit 023c69d752
6 changed files with 86 additions and 48 deletions
+8 -5
View File
@@ -172,10 +172,13 @@ function phaseMeta(ctx) {
_phaseMetaCache = meta;
return meta;
}
function phaseName(ctx, num) {
// Returns the workflow.yaml phase id (e.g. "F1"), not the descriptive name — this is
// what feeds the widget's `phase` field, which the frontend WorkflowBar matches against
// its own "F1".."F8" ids to light up the stepper pills.
function phaseId(ctx, num) {
const meta = phaseMeta(ctx);
const p = meta.phases.find((x) => x.num === num);
return p ? p.name : "?";
return p ? p.id : "?";
}
function currentPhase(ctx, session) {
const out = tht(ctx, ["phase", "show", "--session", session]);
@@ -433,7 +436,7 @@ export default function (pi) {
async execute(_id, params, _signal, _onUpdate, ctx) {
lockActive = true;
const { session, title, options: opts, intro, advance } = params;
const phase = phaseName(ctx, currentPhase(ctx, session));
const phase = phaseId(ctx, currentPhase(ctx, session));
const recommended = opts.find((o) => o.recommended)?.id ?? null;
const widget = buildSelectRequest({
@@ -505,7 +508,7 @@ export default function (pi) {
async execute(_id, params, _signal, _onUpdate, ctx) {
lockActive = true;
const { session, title, options: opts, advance } = params;
const phase = phaseName(ctx, currentPhase(ctx, session));
const phase = phaseId(ctx, currentPhase(ctx, session));
const toAdd = [];
const widget = buildMultiselectRequest({
id: `u${Date.now()}`,
@@ -571,7 +574,7 @@ export default function (pi) {
async execute(_id, params, _signal, _onUpdate, ctx) {
lockActive = true;
const { session, kind, title, artifact } = params;
const phase = phaseName(ctx, currentPhase(ctx, session));
const phase = phaseId(ctx, currentPhase(ctx, session));
const widget = buildArtifactGate({
id: `u${Date.now()}`,
phase,