Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 120 additions & 4 deletions CLAUDE.md

Large diffs are not rendered by default.

72 changes: 68 additions & 4 deletions css/canvas.css
Original file line number Diff line number Diff line change
Expand Up @@ -1543,12 +1543,29 @@
cursor: grabbing;
}

/* ── the boundary tab ──
A DASHED OUTLINE MEANS "NOT PART OF THE MACHINE" — see the note at the top
of css/views.css. A port is derived from the wiring on every rebuild and
reaches no serializer, so it keeps the dash; what changed is everything the
dash was being asked to carry on its own.

It was a transparent 96px pill with one line of 9px mono in it, and it
failed two ways at once. The label ran clean out of both ends, because
`ADDR_L_leaf_14 -> ADDR_L_count_14` is not 96px wide in any font. And with no
fill it was drawn *through* by every edge and edge label behind it.

So: sized to its text (js/view-graph.js), filled so it occludes, and two rows
so the role and the target each get one. Direction is the ROLE — `ENTRY`,
`FROM`, or the block's own word for an exit — which says it in language
rather than in a glyph, and does not need to be read at any particular size
or angle. Colour is left to mean the one thing left: whether the block
declared this crossing at all. */
.pn-body {
fill: transparent;
fill: var(--bg2);
stroke: var(--accent);
stroke-width: 1.2;
stroke-dasharray: 5 3;
opacity: 0.75;
stroke-width: 1;
stroke-dasharray: 4 3;
opacity: 0.9;
}

.pn.is-out .pn-body {
Expand All @@ -1559,6 +1576,10 @@
opacity: 1;
}

.pn:hover .pn-body {
fill: var(--bg3, var(--bg2));
}

/* A hand-placed tab is drawn solid: the dash says "derived, placed for you",
so a port the reader has put somewhere deliberately should stop claiming to
be. It is the same thing `t.curve` does to an auto-routed edge. */
Expand All @@ -1567,6 +1588,49 @@
opacity: 1;
}

/* ── a crossing the block did not declare ──
`entry` and `exits` are what a block promises about itself; every other edge
across the same boundary is a wire into or out of the middle of a
sub-machine. The block still works — this is a finding, not a fault, which is
why it is `--orange` and not `--red` — but it is not *reusable*: a copy of it
placed elsewhere would have that wire hanging off a state its definition
never mentioned. Worth seeing on the canvas rather than in a panel nobody
opens, and worth seeing at a glance among forty tabs.

Written after .is-out so it wins on order rather than on !important. */
.pn.is-stray .pn-body,
.pn.is-out.is-stray .pn-body {
stroke: var(--orange);
}

.pn.is-stray .pn-role {
fill: var(--orange);
}

/* A declared way in or out that nothing is wired to yet. Quieter than either,
because it is the one tab that describes an intention rather than an edge. */
.pn.is-empty .pn-body {
opacity: 0.5;
}

.pn.is-empty .pn-label {
font-style: italic;
}

/* The two rows follow the app's own rule: the role is a small-caps label, the
target is an identifier. LANGUAGE / MTM, ALPHABET Σ / 2, FINGERPRINT /
0 OF 257 — the tab was the one piece of canvas chrome that did not say what
kind of thing it was naming. */
.pn-role {
font-family: var(--sans);
font-size: 7px;
font-weight: 700;
letter-spacing: .09em;
fill: var(--text3);
text-anchor: middle;
pointer-events: none;
}

.pn-label {
font-family: var(--mono);
font-size: 9px;
Expand Down
7 changes: 6 additions & 1 deletion css/modals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5070,10 +5070,15 @@ button:focus-visible {
padding: 3px 5px 3px 10px;
}

/* Locked is a constraint this machine has, not something absent — so it is the
app's disabled treatment (dimmed, no pointer) rather than a dashed edge,
which is reserved for what is not part of the machine. See the rule at the
top of css/views.css. */
.wiz-chips .chip.is-locked {
color: var(--text2);
padding-right: 10px;
border-style: dashed;
opacity: .62;
cursor: not-allowed;
}

.wiz-chip-sym {
Expand Down
77 changes: 73 additions & 4 deletions css/panels.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,14 @@
}

/* Fill alone carries the chip — the extra ring on top of it was what made
these read as heavy buttons rather than counts. */
.lp-section-count {
these read as heavy buttons rather than counts.

Both panels, from one rule. The right panel's Trace count was written into
the markup with a class nothing styled, so it drew as a bare "0" beside a
pill on every other section — the same drift .panel-header and .panel-tab
were pulled together to stop. */
.lp-section-count,
.rp-section-count {
min-width: 16px;
height: 16px;
padding: 0 5px;
Expand All @@ -107,7 +113,8 @@
}

/* A zero count is not news — mute it so populated sections stand out. */
.lp-section-count[data-empty="1"] {
.lp-section-count[data-empty="1"],
.rp-section-count[data-empty="1"] {
background: var(--surface3, var(--bg3));
color: var(--text3);
}
Expand Down Expand Up @@ -281,21 +288,48 @@
gap: 4px;
}

/* A <button>, not the <span onclick> it was. Same lesson rowActions() in
js/render.js learned for the list rows: a control that deletes something has
to be reachable with Tab and firable with Enter, and a span is neither — so
of everything in the panel, Σ, Γ and Δ were the three lists whose only
destructive control no keyboard could reach and no screen reader could name.

The 9px glyph is also a 9px target. It keeps its size — a chip is 20px tall
and a bigger mark would crowd the symbol beside it — and the *button* around
it is padded out to something a pointer can actually land on. */
.chip .x {
cursor: pointer;
color: var(--text3);
display: inline-flex;
transition: color .12s;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
margin-right: -3px;
padding: 0;
border: none;
border-radius: 999px;
background: transparent;
font: inherit;
transition: color .12s, background .12s;
}

.chip .x svg {
width: 9px;
height: 9px;
fill: currentColor;
flex-shrink: 0;
}

.chip .x:hover {
color: var(--red);
background: var(--red-soft);
}

.chip .x:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
color: var(--red);
}

/* States and Transitions behave identically: both cap at the same height and
Expand Down Expand Up @@ -931,6 +965,10 @@ body.panel-resizing .rpanel {
display: flex;
flex-direction: column;
flex: 1;
/* Reaching the end of the panel must not start scrolling whatever is behind
it. The mobile sheet already asked for this; the desktop rail is the same
scroller and was chaining out of the panel into the page. */
overscroll-behavior: contain;
/* Without this a flex column refuses to shrink past its content, so the
transitions section (flex:1, min-height:0) never actually got a bounded
height and grew the panel instead of scrolling inside itself. */
Expand All @@ -956,6 +994,9 @@ body.panel-resizing .rpanel {

.lpanel-search-inp:focus {
border-color: var(--accent);
/* .inp, .sel and .algo-search-inp all draw this ring; these two were the
fields in the panel that changed only their border colour on focus. */
box-shadow: 0 0 0 2px var(--focus-ring);
}

/* Right Panel */
Expand All @@ -979,6 +1020,8 @@ body.panel-resizing .rpanel {
flex: 1;
display: flex;
flex-direction: column;
/* Same as .lpanel-content. */
overscroll-behavior: contain;
/* Same reason as .lpanel-content: a flex column won't shrink below its
content without this, so overflow-y never engages. */
min-height: 0;
Expand Down Expand Up @@ -1077,6 +1120,16 @@ body.panel-resizing .rpanel {
background: var(--surface);
}

/* The same ring the left panel's header has. These headers were plain
`<div onclick>`s — no role, no tabindex, no key handler — so of the two
sidebars only one could be collapsed from a keyboard, and the four right
panel sections had no focus state to give because nothing could focus
them. See the note in index.html. */
.rp-section-header:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2px var(--focus-ring);
}

.rp-section-title {
font-family: var(--mono);
font-size: var(--sec-label-size);
Expand Down Expand Up @@ -1125,6 +1178,16 @@ body.panel-resizing .rpanel {
transform: rotate(-90deg);
}

/* The class hides the body, the way `.lp-collapsible.collapsed` already does.
Only the inline `display` written by setRPSectionCollapsed was doing this,
which meant the markup had to ship a section's collapsed state twice — as a
class on the section and as a style attribute on its body — and a section
declared collapsed in the registry but not in the markup flashed open on
every boot until the pass caught up. */
.rp-collapsible.collapsed .rp-section-body {
display: none;
}

.rp-section-body {
padding: 4px 14px 14px;
}
Expand Down Expand Up @@ -1758,7 +1821,13 @@ body.panel-resizing .rpanel {
display: flex;
flex-direction: column;
gap: 2px;
/* The same cap .slist and .tlist take, and for the same reason: a machine
built out of blocks has as many rows here as it has states there. Without
a height to overflow, the `overflow-y` below never engaged at all — the
list simply grew and pushed the Library heading under it off the panel. */
max-height: var(--lp-list-max-h);
overflow-y: auto;
overscroll-behavior: contain;
}

/* A <button>, not a <div onclick>. The States Q and Transitions δ rows learned
Expand Down
Loading
Loading