diff --git a/components/plan-block.tsx b/components/plan-block.tsx
index 4b94910..e6a5c4c 100644
--- a/components/plan-block.tsx
+++ b/components/plan-block.tsx
@@ -136,6 +136,14 @@ export function wrapTitle(title: string, maxChars = 20, maxLines = 2): string[]
* geometry (half bleeds outside the extent) and the glow halo reaches −4 —
* without this pad the outermost node borders clip at the svg edge. */
export const GRAPH_PAD = 8;
+/** The current node's yellow glow: a fill blurred so it bleeds over the
+ * node's edges (owner). A Gaussian's visible spread is ≈3σ, so the glow
+ * reaches GLOW_MARGIN + 3·GLOW_BLUR past the node — any surface that
+ * shows the glow must pad its viewBox by GLOW_REACH or the blur clips
+ * at the frame edge (owner: extend the container padding to fit it). */
+export const GLOW_MARGIN = 8;
+export const GLOW_BLUR = 5;
+export const GLOW_REACH = GLOW_MARGIN + 3 * GLOW_BLUR;
/** Node titles render at text-xs (12.5px) in svg units. The in-chat block
* scales up to fill the column but NEVER past the chat prose size
* (--lh-prose-size, 16.5px) — the width cap that makes that hold. */
@@ -253,11 +261,25 @@ export function PlanDiagram({
line ends AT the notch — the tip beyond it is solid fill. */}
+ {/* Same head, grayed — arrows into not-yet-visited nodes are
+ grayed out like the nodes themselves (owner, round 6). */}
+
+
+
{/* The current node's highlight: a faint yellow fill BLURRED so it
bleeds out over the node's edges (owner). Generous filter region
so the blur never clips at its own bounds. */}
-
+
{edges.map((e) => {
@@ -268,13 +290,16 @@ export function PlanDiagram({
// node border while the line stops at the notch mouth.
const y2 = (layout.y.get(e.to) ?? 0) - HEAD_TIP_OVERHANG;
const mid = (y1 + y2) / 2;
+ // An arrow into a not-yet-visited node is grayed out like the
+ // node (owner, round 6); the in-chat block stays all-white.
+ const dim = !monochrome && statusOf(e.to) === "upcoming";
return (
${e.to}`}
d={`M ${x1} ${y1} C ${x1} ${mid}, ${x2} ${mid}, ${x2} ${y2}`}
- className="fill-none stroke-white"
+ className={dim ? "fill-none stroke-border" : "fill-none stroke-white"}
strokeWidth={1.5}
- markerEnd={`url(#${markerId})`}
+ markerEnd={`url(#${markerId}${dim ? "-dim" : ""})`}
/>
);
})}
@@ -291,13 +316,13 @@ export function PlanDiagram({
>
{glow && (
// The current node's highlight: faint yellow, blurred so it
- // bleeds over the edges (owner, plan-graph round 4).
+ // bleeds over the edges — static, no pulse (owner, round 6).
)}
@@ -391,6 +416,9 @@ export function latestTree(messages: readonly PopupMessage[]): PlanTree | null {
* wide graphs; the fit itself is the floor now. */
const ZOOM_MAX = 4;
const FIT_PAD = 16;
+/** The popup shows the glow, so its frame pads by the glow's reach —
+ * the in-chat block (monochrome, no glow) stays tight at GRAPH_PAD. */
+const POPUP_PAD = GLOW_REACH;
const FIT_ZOOM_MAX = 1.5;
interface GraphView {
@@ -459,10 +487,11 @@ export function PlanPopup({ messages, onClose }: { messages: readonly PopupMessa
}, []);
const layout = useMemo(() => (tree ? blockLayout(tree.nodes, tree.edges) : null), [tree]);
- // The fit box is the PADDED ink box — the diagram renders with GRAPH_PAD
- // so strokes/glow never clip, and the fit must account for it.
+ // The fit box is the PADDED ink box — the diagram renders with POPUP_PAD
+ // (= the glow's reach) so the blurred glow never clips at the frame, and
+ // the fit must account for that padding.
const box = useMemo(
- () => (layout ? { w: layout.width + 2 * GRAPH_PAD, h: layout.height + 2 * GRAPH_PAD } : { w: 0, h: 0 }),
+ () => (layout ? { w: layout.width + 2 * POPUP_PAD, h: layout.height + 2 * POPUP_PAD } : { w: 0, h: 0 }),
[layout],
);
const fitZoom = useMemo(() => fitZoomFor(box, size), [box, size]);
@@ -595,7 +624,7 @@ export function PlanPopup({ messages, onClose }: { messages: readonly PopupMessa
statusOf={statusOf}
glowId={statuses?.current ?? null}
markerId={markerId}
- pad={GRAPH_PAD}
+ pad={POPUP_PAD}
naturalSize
/>
diff --git a/tests/render-plan-block.test.tsx b/tests/render-plan-block.test.tsx
index 7734240..0043a4d 100644
--- a/tests/render-plan-block.test.tsx
+++ b/tests/render-plan-block.test.tsx
@@ -139,9 +139,12 @@ describe("plan popup (live derivation)", () => {
assert.match(html, /data-testid="plan-node-b" data-status="current"/);
assert.match(html, /data-testid="plan-node-c" data-status="upcoming"/);
// The current node is the glow target: a faint yellow fill, blurred
- // so it bleeds over the edges (owner, plan-graph round 4).
- assert.match(html, /fill-yellow-400\/25 animate-pulse/);
+ // so it bleeds over the edges — static, no pulse (owner, round 6).
+ assert.match(html, /fill-yellow-400\/25/);
assert.match(html, /feGaussianBlur/);
+ assert.doesNotMatch(html, /animate-pulse/);
+ // Arrows into not-yet-visited nodes are grayed out (owner, round 6).
+ assert.match(html, /marker-end="url\(#.*-dim\)"/);
assert.match(html, /live — statuses derive from your check outcomes/);
});