From 654b55f26a22ad1e0ad11319e475b575e43b060b Mon Sep 17 00:00:00 2001 From: Glenn Gore Date: Tue, 8 Sep 2026 18:04:33 +0200 Subject: [PATCH] fix(persona): the lines go behind the boxes, and one thing looks pressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things a person testing the map reported, in the order they hit them. **The edges were drawn over the cards.** The SVG is positioned and the bands were not, so it won a stacking contest nobody entered it into: every curve ran across the boxes it connects, through the type and the value. The bands are positioned now with a higher `zIndex`, and an edge that disappears under a card reads as going behind it — which is what it does. **Everything a selection reached looked selected.** Both the pressed card and the ones it reaches wore a coloured border, so clicking a persona outlined its face, its attributes and its context in the same treatment as the row that was clicked: a dozen outlined boxes and no way to tell which one was the question. "I clicked on the persona DID — but how can you tell?" The two states stop competing. A reached card is a wash with the ordinary border; only the selection carries a ring, drawn as a hairline of the page's own ground and then the accent so it lifts off a band that is already tinted. One outline on the screen, and it is always the thing you pressed. The persona row inside a context gets the same rule — it is the one people click most, being where "who am I here" is asked. **A withheld value lost its own sentence.** "Singapore · with your agent" does not fit a 222px card and truncated to "Singapore · with y…", dropping the half that says what to do about it. The label steps aside while the value is absent: it is the holder's own note, the type above already names the attribute, and on a card holding nothing the sentence that matters is the one about where the value is. It returns the moment the value does. 367 extension tests pass; `tsc -b` and `npm run build` clean. Signed-off-by: Glenn Gore --- .../src/manager/panes/persona-map.tsx | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/extension/src/manager/panes/persona-map.tsx b/packages/extension/src/manager/panes/persona-map.tsx index 622d2b9..898d450 100644 --- a/packages/extension/src/manager/panes/persona-map.tsx +++ b/packages/extension/src/manager/panes/persona-map.tsx @@ -236,15 +236,28 @@ type Mood = "plain" | "self" | "down" | "up" | "dim"; */ function cardStyle(mood: Mood, extra?: React.CSSProperties, stripe?: string): React.CSSProperties { const lit = mood === "down" || mood === "up" ? FLOW_COLOUR[mood] : null; + // **The selection outranks everything it reaches, visually.** + // + // Both used to wear a coloured border, so selecting a persona lit its face, + // its attributes and its context in the same treatment as the row that had + // been clicked — a dozen outlined boxes and no way to tell which one was the + // question. "I clicked on the persona DID, but how can you tell?" + // + // So the two states stop competing: a reached card is a *wash* with the + // ordinary border, and only the selection carries a ring. One outline on the + // screen, and it is always the thing you pressed. const ring = mood === "self" - ? { border: `2px solid ${c.accent}`, background: c.surface } + ? { border: `2px solid ${c.accent}`, background: c.accentSoft } : lit - ? { border: `1px solid ${lit.edge}`, background: lit.wash } + ? { border: `1px solid ${c.line}`, background: lit.wash } : { border: `1px solid ${c.line}`, background: c.surface }; const shadows = [ stripe ? `inset 3px 0 0 ${stripe}` : null, - mood === "self" ? `0 0 0 4px ${c.accentSoft}` : null, + // Two rings rather than one: a hairline of the page's own ground, then the + // accent. It reads as lifted off the band at any zoom, where a single soft + // halo disappears against a card that is already tinted. + mood === "self" ? `0 0 0 2px ${c.ground}, 0 0 0 5px ${c.accent}` : null, ].filter(Boolean); return { borderRadius: "var(--w-r-md)", @@ -553,8 +566,15 @@ export function IdentityMap({
{ if (e.target === e.currentTarget) setSelection(null); }}> + {/* Behind the cards, not over them. + The SVG is positioned and the bands are not, so the SVG won a + stacking contest nobody entered it into: every edge was drawn + *across* the boxes it connects, through the type and the value. The + bands become positioned with a higher `zIndex` below, which is the + whole fix — an edge that disappears under a card reads as going + behind it, which is what it does. */} {/* ── Attributes ── */} -
+
{grouped.map(({ family, members }) => { @@ -612,7 +632,16 @@ export function IdentityMap({ own — and a *Show* that scrolled out of a card clipped to one line would be a control nobody could press. */}
- {f.label && ( + {/* The label steps aside when the agent sent no + value. "Singapore · with your agent" does not + fit a 222px card and truncated to "Singapore · + with y…", losing the half that says what to do + about it. The label is the holder's own note and + the type above already names the attribute, so + on a card holding nothing the sentence that + matters is the one about where the value is. It + returns the moment the value does. */} + {f.label && f.value !== undefined && ( {f.label} · )} {/* ── Faces ── */} -
+
{graph.faces.map((face) => { @@ -694,7 +723,7 @@ export function IdentityMap({
{/* ── The line ── */} -
+
Copies go down. Nothing reads up. @@ -702,7 +731,7 @@ export function IdentityMap({
{/* ── Contexts ── */} -
+
{ const key = personaKey(ctx.id, p.did); const pFlow = flowOf(reach, "persona", key); + // Same hierarchy as the cards: reached is a wash, and + // only the selection wears a ring. This row is the one + // a person clicks most — it is what "who am I in this + // context" is asked of — so it is the one that most + // needs to look pressed. const wash = pFlow === "self" ? c.accentSoft : pFlow ? FLOW_COLOUR[pFlow].wash : c.raised; - const edge = pFlow === "self" ? c.accent : pFlow ? FLOW_COLOUR[pFlow].edge : c.line; + const edge = pFlow === "self" ? c.accent : c.line; + const ring = pFlow === "self" ? `0 0 0 2px ${c.ground}, 0 0 0 5px ${c.accent}` : undefined; const linked = showLinks && p.faceId !== null && linkedFaces.has(p.faceId); return (
{personaLabel(p.did)}