Files
learnharness/components/plan-window.tsx
T
amosblomqvist 93033fac77 Plan placement (owner): the plan as marginalia — dock band's right flank + top-bar chip
Retires the fixed bottom-left plan window (which sat ON the sidebar and
bled into the session). Two placements replace it, per the owner's
lock-in:

- The plan chip in the top bar beside the phase chip — session-level
  structure beside session state; opens the full-DAG popup; present at
  every width; the access point.
- The plan marginalia: the BARE diagram (no card, no border, no shadow)
  in the dock band's right flank — marginalia on the lesson's page, not a
  floating widget. The band's reserved height (tall while a quiz is
  open) is its canvas; meet-fitted (PlanDiagram's new fill mode) so it
  fits both the flank's width and the band's height; live statuses +
  the yellow current-node glow; click opens the popup. Mounted only
  while a question owns the band — composer mode keeps the reading
  view pristine. Folding the dock fades + blurs it out of existence
  (300ms), returning to the bottom fades it back in. Rendered only when
  the flank holds a real diagram (>= 240px); narrower windows keep the
  chip alone.

Files: chat.tsx (chip, DockOverlay marginalia wrapper + flank measure,
PlanWindow removed), plan-window.tsx (PlanWindow → PlanMarginalia),
plan-block.tsx (PlanDiagram fill prop: h-full w-full meet-fit),
tests/plan-window.test.tsx (rewritten for the marginalia).
2026-09-14 11:23:28 +03:00

55 lines
2.0 KiB
TypeScript

"use client";
import { useId, useMemo } from "react";
import { derivedNodeStatus, derivePlanStatuses } from "@/src/core";
import {
PlanDiagram,
GRAPH_PAD,
latestTree,
TEXT_CAP_SCALE,
type PopupMessage,
} from "@/components/plan-block";
/**
* THE PLAN MARGINALIA: the plan's ambient presence in the session view —
* the successor of the old fixed corner card (the plan window, retired
* with the dock-band placement round). NO container: the bare diagram,
* meet-fitted into the dock band's right flank — the wrapper (DockOverlay)
* positions and fades it — so it reads as marginalia on the lesson's
* page, not a floating widget. Live by construction: statuses derive from
* the transcript on every render (derivePlanStatuses — calls, not
* results), so the glow moves the moment a tagged check streams in.
*
* Visibile only while a question owns the dock band (the reserved tail's
* height is the diagram's canvas — quiz open, tall band, full plan);
* folding the dock fades + blurs the whole thing out of existence. The
* top-bar plan chip carries access at every width; the popup is the study
* view. The viewBox pad clears the current node's blurred yellow halo
* (GLOW_REACH) at the extent edges.
*/
const MARGIN_PAD = GRAPH_PAD + 16;
export function PlanMarginalia({ messages }: { messages: readonly PopupMessage[] }) {
const markerId = useId().replace(/[^a-zA-Z0-9-]/g, "");
const tree = useMemo(() => latestTree(messages), [messages]);
const statuses = useMemo(
() => (tree ? derivePlanStatuses(tree, messages as Parameters<typeof derivePlanStatuses>[1]) : null),
[tree, messages],
);
if (!tree || tree.nodes.length === 0) return null; // no plan yet: nothing in the margin
const statusOf = (id: string) => (statuses ? derivedNodeStatus(statuses, id) : "upcoming");
return (
<PlanDiagram
nodes={tree.nodes}
edges={tree.edges}
statusOf={statusOf}
glowId={statuses?.current ?? null}
markerId={markerId}
pad={MARGIN_PAD}
textCapScale={TEXT_CAP_SCALE}
fill
/>
);
}