@issuegraph/viewer 0.3.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clusters.d.ts +116 -0
- package/dist/clusters.d.ts.map +1 -1
- package/dist/clusters.js +85 -2
- package/dist/clusters.js.map +1 -1
- package/dist/document.d.ts +228 -5
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +204 -3
- package/dist/document.js.map +1 -1
- package/dist/element.d.ts +11 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js.map +1 -1
- package/dist/index.d.ts +17 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -4
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +79 -50
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +371 -170
- package/dist/layout.js.map +1 -1
- package/dist/marks.d.ts +105 -0
- package/dist/marks.d.ts.map +1 -0
- package/dist/marks.js +430 -0
- package/dist/marks.js.map +1 -0
- package/dist/mount.d.ts +13 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +69 -1
- package/dist/mount.js.map +1 -1
- package/dist/navigation.d.ts +14 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +51 -0
- package/dist/navigation.js.map +1 -1
- package/dist/parts.d.ts +308 -2
- package/dist/parts.d.ts.map +1 -1
- package/dist/parts.js +845 -65
- package/dist/parts.js.map +1 -1
- package/dist/projections/graph.d.ts +25 -0
- package/dist/projections/graph.d.ts.map +1 -1
- package/dist/projections/graph.js +476 -335
- package/dist/projections/graph.js.map +1 -1
- package/dist/projections/linear.d.ts +135 -0
- package/dist/projections/linear.d.ts.map +1 -1
- package/dist/projections/linear.js +266 -31
- package/dist/projections/linear.js.map +1 -1
- package/dist/projections/tree.d.ts.map +1 -1
- package/dist/projections/tree.js +29 -3
- package/dist/projections/tree.js.map +1 -1
- package/dist/styles.d.ts +10 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +1275 -132
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +5 -6
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +119 -7
- package/dist/theme.js.map +1 -1
- package/dist/vocabulary.d.ts +39 -0
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +27 -0
- package/dist/vocabulary.js.map +1 -1
- package/package.json +2 -2
package/dist/styles.js
CHANGED
|
@@ -12,14 +12,305 @@
|
|
|
12
12
|
* loaded by a bare Node runtime (`ERR_UNKNOWN_FILE_EXTENSION`), and this
|
|
13
13
|
* package's floor is checked by a smoke test that imports the built entry. A
|
|
14
14
|
* string also means no consumer needs a bundler to use it.
|
|
15
|
+
*
|
|
16
|
+
* THE VIEWER DRAWS THE PANEL, and that is a decision this file records rather
|
|
17
|
+
* than a detail. §16a is one bordered surface with a header bar, hairline-
|
|
18
|
+
* separated rows and a footer group on a second ground; §9097's done-when read
|
|
19
|
+
* "§16 IS that test — the viewer with no editor attached", so the thing a host
|
|
20
|
+
* mounts with no chrome of its own has to BE the frame. Rows therefore carry no
|
|
21
|
+
* border and no radius of their own — they are separated by the panel's own
|
|
22
|
+
* hairlines — and a host that wants the panel suppressed overrides the four
|
|
23
|
+
* properties on `.ig-viewer`.
|
|
15
24
|
*/
|
|
25
|
+
/**
|
|
26
|
+
* §17j's rail density, written once and emitted under two keys.
|
|
27
|
+
*
|
|
28
|
+
* ## Why this is a function and not a block
|
|
29
|
+
*
|
|
30
|
+
* The density has to be reachable two ways — by the container's width, which is
|
|
31
|
+
* §17j's own rule, and by a consumer who disagrees with the crossover this
|
|
32
|
+
* package picked. CSS cannot express "this condition OR that selector" across
|
|
33
|
+
* an `@container` boundary: a rule inside the query cannot also be a rule
|
|
34
|
+
* outside it, so the declarations would have to be written twice. The
|
|
35
|
+
* stylesheet is already a template literal, so they are written once here and
|
|
36
|
+
* interpolated twice instead.
|
|
37
|
+
*
|
|
38
|
+
* `scope` is a selector the whole block hangs off, and every rule below is
|
|
39
|
+
* RELATIVE to it. The original block carried its own `.ig-viewer` prefixes on
|
|
40
|
+
* about half its rules; those are gone, because the scope supplies one and two
|
|
41
|
+
* would never match.
|
|
42
|
+
*
|
|
43
|
+
* ## What it does to specificity, which is a gain rather than a risk
|
|
44
|
+
*
|
|
45
|
+
* Every selector here gains the scope's two compound units, so each dense rule
|
|
46
|
+
* beats the base rule it overrides by SPECIFICITY rather than by source order.
|
|
47
|
+
* One of these already had to be hand-scoped for exactly that reason — the
|
|
48
|
+
* together-unit row, where a container query adds no specificity and the base
|
|
49
|
+
* rule further down the file won, leaving the unit row drawn with nothing in
|
|
50
|
+
* it. That hazard is now closed for the whole block rather than at one site.
|
|
51
|
+
*/
|
|
52
|
+
function railDensity(scope) {
|
|
53
|
+
return `
|
|
54
|
+
${scope} .ig-slot {
|
|
55
|
+
align-items: center;
|
|
56
|
+
grid-template-columns: var(--ig-rank-column-dense) 1fr auto;
|
|
57
|
+
min-height: var(--ig-row-height-dense);
|
|
58
|
+
padding-block: var(--ig-row-padding-block-dense);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* TWO LINES, AND NEITHER WRAPS. A wrapping title is what turns a 53px row
|
|
62
|
+
into a 106px one, so the truncation is not cosmetic — it is what makes the
|
|
63
|
+
height a property of the row rather than of its longest title. */
|
|
64
|
+
${scope} .ig-slot .ig-row-head {
|
|
65
|
+
gap: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
${scope} .ig-slot .ig-title {
|
|
69
|
+
line-height: var(--ig-row-title-line);
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* STEP 5 OF THE DROP ORDER, AND IT IS LAST FOR A REASON '17j' STATES:
|
|
76
|
+
*"A row without a title is not a row."* Everything above leaves first. */
|
|
77
|
+
${scope} .ig-slot .ig-row-body {
|
|
78
|
+
gap: var(--ig-row-line-gap);
|
|
79
|
+
min-width: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* LINE 2, AND IT DOES NOT WRAP. '17j' draws it as one mono run —
|
|
83
|
+
'#512 . P0 . blocked-by #488' — so the chips sit ON the identity's line
|
|
84
|
+
rather than under it. 'nowrap' is what makes the 53px a property of the
|
|
85
|
+
ROW: one long title or one extra badge would otherwise buy a second line
|
|
86
|
+
and double the height, which is exactly how this rail reached 408px. */
|
|
87
|
+
${scope} .ig-slot .ig-row-meta {
|
|
88
|
+
align-items: center;
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-wrap: nowrap;
|
|
91
|
+
gap: var(--ig-space-tight);
|
|
92
|
+
line-height: var(--ig-row-meta-line);
|
|
93
|
+
min-width: 0;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* The chips keep their own row semantics and stop wrapping, so what does not
|
|
98
|
+
fit is clipped rather than stacked. The drop order above decides WHICH
|
|
99
|
+
chips are there to clip; this only decides they stay on the line. */
|
|
100
|
+
${scope} .ig-slot .ig-row-meta .ig-badges {
|
|
101
|
+
flex-wrap: nowrap;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* The issue number never shrinks away — '17j' lists it among the five things
|
|
106
|
+
never dropped at any width. The title truncates first and the chips clip;
|
|
107
|
+
the number holds its ground. */
|
|
108
|
+
${scope} .ig-slot .ig-row-meta .ig-id {
|
|
109
|
+
flex: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* AT RAIL DENSITY LINE 2 IS TEXT, NOT PILLS — and this is read off the frame
|
|
113
|
+
rather than inferred from the height. '17j' draws the meta line as a single
|
|
114
|
+
mono run, '#512 . P0 . blocked-by #488': no outlines, no fills, just the
|
|
115
|
+
glyph and the words. §16a's wider panel is where they are chips.
|
|
116
|
+
|
|
117
|
+
IT IS ALSO WHAT MAKES 53px REACHABLE. A bordered chip is its padding plus
|
|
118
|
+
its stroke plus its line box, so a line of them cannot be 15px however the
|
|
119
|
+
line-height is set — the flex row grows to its tallest item and the row
|
|
120
|
+
followed it to 74px. Removing the chrome removes the reason.
|
|
121
|
+
|
|
122
|
+
THE FOUR REDUNDANT CHANNELS SURVIVE THIS, which is the thing worth
|
|
123
|
+
checking before doing it: dash, terminal and glyph are untouched, and hue
|
|
124
|
+
moves from the border to the text it was outlining. '17j' names the hold
|
|
125
|
+
GLYPH among the five things never dropped, and it is still drawn. */
|
|
126
|
+
${scope} .ig-slot .ig-row-meta .ig-badge {
|
|
127
|
+
background: none;
|
|
128
|
+
border: 0;
|
|
129
|
+
border-radius: 0;
|
|
130
|
+
padding: 0;
|
|
131
|
+
/* AND THE TEXT INSIDE A CHIP DOES NOT WRAP EITHER. Stopping the ROW from
|
|
132
|
+
wrapping is not enough: a chip squeezed by its neighbours wraps its own
|
|
133
|
+
label instead, and 'signals disagree' at 84px became two 15px lines and
|
|
134
|
+
took the row to 67px while every sibling sat at 53. One row in seven,
|
|
135
|
+
which is exactly the kind of thing that survives a review and does not
|
|
136
|
+
survive a measurement. What does not fit is clipped, per the drop
|
|
137
|
+
order. */
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* AND THE SWAP, AT RAIL DENSITY. '17j' draws a together unit here as a
|
|
142
|
+
marker inline with an ordinary title at the ordinary 53px, so the head
|
|
143
|
+
comes back and the enclosure stands down. One row, one rank, in both
|
|
144
|
+
densities - RULINGS.md section 1 - drawn the way each width can afford. */
|
|
145
|
+
/* SCOPED WITH '.ig-viewer' SO ORDER CANNOT DECIDE THIS. A container query
|
|
146
|
+
adds no specificity, so this rule and the wide-density one it overrides
|
|
147
|
+
were an even 0,3,0 and the LATER of the two won — which put the base rule,
|
|
148
|
+
written further down the file, in charge of both densities and left the
|
|
149
|
+
unit row showing nothing but its em dash. Measured, not spotted: every row
|
|
150
|
+
was 53px and one of them was empty. The extra class makes the override win
|
|
151
|
+
on specificity, where it does not depend on where anyone adds a rule
|
|
152
|
+
later. */
|
|
153
|
+
${scope} .ig-slot[data-unit='true'] > .ig-row-body > .ig-row-head {
|
|
154
|
+
display: flex;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
${scope} .ig-slot[data-unit='true'] .ig-unit-mark,
|
|
158
|
+
${scope} .ig-slot[data-unit='true'] .ig-unit {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The marker itself: the glyph and the count, on the title's line. It is a
|
|
163
|
+
COUNT and not a pill here because 53px has no room for one, and because
|
|
164
|
+
the pill's words are already the accessible name's. */
|
|
165
|
+
${scope} .ig-unit-count {
|
|
166
|
+
color: var(--ig-edge-together-with);
|
|
167
|
+
display: inline;
|
|
168
|
+
font-family: var(--ig-font-mono);
|
|
169
|
+
margin-right: var(--ig-space-tight);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* THE FOOTER ROWS TOO, AND THIS IS THE POPULATION THE ROW FIX MISSED.
|
|
173
|
+
A footer entry is deliberately ONE LINE and shorter than a ranked row -
|
|
174
|
+
16a gives it no rank, no station and no explanation block, because it is
|
|
175
|
+
not a fact about the work. That part was right. What was not governed is
|
|
176
|
+
its TITLE: it wrapped, and the excluded row carrying a canonical reference
|
|
177
|
+
ran to 73px while its siblings sat at 36. A footer row taller than a
|
|
178
|
+
ranked row inverts the whole point of the group.
|
|
179
|
+
|
|
180
|
+
THE DEFECT IS THE ONE ALREADY FIXED ON .ig-slot, ONE ROW KIND OVER: a
|
|
181
|
+
title with nothing stopping it wrapping takes the row with it. Fixed at
|
|
182
|
+
the class this time rather than at the site - the rail renders three row
|
|
183
|
+
kinds and the first pass governed two. */
|
|
184
|
+
${scope} .ig-footer-row .ig-title {
|
|
185
|
+
line-height: var(--ig-row-title-line);
|
|
186
|
+
min-width: 0;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
text-overflow: ellipsis;
|
|
189
|
+
white-space: nowrap;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
${scope} .ig-footer-row .ig-badges {
|
|
193
|
+
flex-wrap: nowrap;
|
|
194
|
+
min-width: 0;
|
|
195
|
+
overflow: hidden;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* AND THE CHIPS LOSE THEIR CHROME HERE TOO, which is what actually makes a
|
|
199
|
+
footer entry ONE LINE. 16a draws it as "a label chip, a title and an
|
|
200
|
+
identity, on one line"; a bordered chip is padding plus stroke plus line
|
|
201
|
+
box, so a row carrying any relationship badge stood at 36px against 22px
|
|
202
|
+
for one carrying none - and the badge block, not the title, was the last
|
|
203
|
+
thing driving it.
|
|
204
|
+
|
|
205
|
+
MEASURED IN TWO PASSES, WHICH IS THE POINT. Stopping after the title fix
|
|
206
|
+
left the footer group at two heights and looked finished: the row that had
|
|
207
|
+
been 73px was down to 36 and matched its siblings, so the obvious check
|
|
208
|
+
passed. It was the SECOND measurement - why 22 and why 36 - that found the
|
|
209
|
+
badges. A class is not fixed until nothing in it varies for a reason you
|
|
210
|
+
have not named. */
|
|
211
|
+
${scope} .ig-footer-row .ig-badge {
|
|
212
|
+
background: none;
|
|
213
|
+
border: 0;
|
|
214
|
+
border-radius: 0;
|
|
215
|
+
padding: 0;
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* THE THIRD AND LAST MEMBER OF THE CLASS: the identity. An excluded row
|
|
220
|
+
carries TWO - its own, and the canonical it defers to ('455 -> 512') - and
|
|
221
|
+
the second wrapped, which is why that row alone stood at 30px when the
|
|
222
|
+
others reached 17. Same defect as the title and the chips, third element,
|
|
223
|
+
found by asking the same question a third time rather than by stopping at
|
|
224
|
+
the first uniform-looking answer. */
|
|
225
|
+
${scope} .ig-footer-row .ig-id {
|
|
226
|
+
white-space: nowrap;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* THE NOW STRIP AT RAIL DENSITY. '17j' draws it as the first row of the rail
|
|
230
|
+
at all three backlog sizes, in the same rhythm as the rows beneath it -
|
|
231
|
+
RULINGS.md section 4: "a single now strip as the first row of the rail,
|
|
232
|
+
above rank 1, on a raised surface, carrying no rank number".
|
|
233
|
+
|
|
234
|
+
THE RANK SLOT IS ALREADY RIGHT AND IS NOT TOUCHED HERE: the strip carries a
|
|
235
|
+
'now' mark where a rank would be, never a number, which is the half of
|
|
236
|
+
section 4 the build already had. What was wrong was only its height. */
|
|
237
|
+
${scope} .ig-now-row {
|
|
238
|
+
min-height: var(--ig-row-height-dense);
|
|
239
|
+
padding-block: var(--ig-row-padding-block-dense);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
${scope} .ig-now-row .ig-row-head {
|
|
243
|
+
gap: 0;
|
|
244
|
+
min-width: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
${scope} .ig-now-row .ig-title {
|
|
248
|
+
line-height: var(--ig-row-title-line);
|
|
249
|
+
overflow: hidden;
|
|
250
|
+
text-overflow: ellipsis;
|
|
251
|
+
white-space: nowrap;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
${scope} .ig-now-row .ig-id {
|
|
255
|
+
line-height: var(--ig-row-meta-line);
|
|
256
|
+
overflow: hidden;
|
|
257
|
+
text-overflow: ellipsis;
|
|
258
|
+
white-space: nowrap;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* HIDDEN HERE, AND NOT YET EXPANDABLE — WHICH IS HALF OF WHAT 17j ASKS FOR.
|
|
262
|
+
The tile says: "The provenance line never renders inline here. In the rail
|
|
263
|
+
it is expand-on-demand; in 16a's wider panel it is inline. Same row, two
|
|
264
|
+
densities."
|
|
265
|
+
|
|
266
|
+
THE INLINE HALF IS DONE AND THE ON-DEMAND HALF IS NOT. There is no
|
|
267
|
+
row-level expand affordance at this density — 16f's right-arrow "expand
|
|
268
|
+
provenance" does not exist — so a rail reader reaches the explanation
|
|
269
|
+
through the inspector's WHY RANK block rather than through the row. The
|
|
270
|
+
information is one selection away, not lost, but this comment previously
|
|
271
|
+
claimed "expand-on-demand" and that was an overclaim about a control
|
|
272
|
+
nothing draws.
|
|
273
|
+
|
|
274
|
+
The markup stays whole so the wider container still renders it inline and
|
|
275
|
+
so the accessible name is unchanged; what the rail declines is spending
|
|
276
|
+
53px of every row on it. The missing affordance is filed, not forgotten. */
|
|
277
|
+
${scope} .ig-slot .ig-provenance,
|
|
278
|
+
${scope} .ig-slot .ig-hold,
|
|
279
|
+
${scope} .ig-slot .ig-caveat {
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* AND THE ON-DEMAND HALF, WHICH IS WHAT 17j ACTUALLY ASKED FOR. A row the
|
|
284
|
+
reader opened with 16f's right-arrow draws its provenance again.
|
|
285
|
+
|
|
286
|
+
THE 53px IS NOT BROKEN BY THIS, it is spent deliberately: an open row is
|
|
287
|
+
taller because the reader asked it to be, and closing it returns the row to
|
|
288
|
+
the rhythm. That is the difference between a row that grows because nothing
|
|
289
|
+
governs it - the defect this sweep removed - and one that grows because
|
|
290
|
+
somebody pressed a key.
|
|
291
|
+
|
|
292
|
+
ONLY THE PROVENANCE COMES BACK. Holds and caveats stay down: 17j names the
|
|
293
|
+
provenance line and nothing else, and the holds are already in the
|
|
294
|
+
inspector's WHY RANK block as controls rather than prose. */
|
|
295
|
+
${scope} .ig-slot[aria-expanded='true'] {
|
|
296
|
+
min-height: var(--ig-row-height-dense);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
${scope} .ig-slot[aria-expanded='true'] .ig-provenance {
|
|
300
|
+
display: block;
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
16
304
|
export const viewerStylesheet = `
|
|
17
305
|
.ig-viewer {
|
|
18
306
|
background: var(--ig-bg);
|
|
307
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
308
|
+
border-radius: var(--ig-radius-large);
|
|
19
309
|
color: var(--ig-text-body);
|
|
20
310
|
font-family: var(--ig-font-ui);
|
|
21
311
|
font-size: var(--ig-font-size);
|
|
22
312
|
line-height: var(--ig-line-height);
|
|
313
|
+
overflow: hidden;
|
|
23
314
|
position: relative;
|
|
24
315
|
}
|
|
25
316
|
|
|
@@ -31,37 +322,515 @@ export const viewerStylesheet = `
|
|
|
31
322
|
|
|
32
323
|
.ig-viewer :focus-visible {
|
|
33
324
|
outline: var(--ig-focus-ring) solid var(--ig-focus);
|
|
34
|
-
outline-offset: var(--ig-
|
|
325
|
+
outline-offset: calc(var(--ig-focus-ring) * -1);
|
|
35
326
|
}
|
|
36
327
|
|
|
328
|
+
/* THE ROW'S DENSITY IS READ FROM THIS BOX, AND THAT IS §17j's CLOSING RULE
|
|
329
|
+
RATHER THAN A PREFERENCE. The same row renders at 390px in the workspace
|
|
330
|
+
rail and at 330px in §18's settings rail, so it *"must take its width from
|
|
331
|
+
its container and pick its drop step from that — not from a viewport media
|
|
332
|
+
query, and not from a prop the host sets. A package that reads the window
|
|
333
|
+
cannot be dropped into someone else's settings page, which is the whole BYO
|
|
334
|
+
premise."*
|
|
335
|
+
|
|
336
|
+
A viewport query would have been the obvious reach and it is precisely
|
|
337
|
+
wrong: it answers a question about the WINDOW when the row's question is
|
|
338
|
+
about the COLUMN it was given. Two rails of different widths on one screen
|
|
339
|
+
is not an edge case here — §17a's rail and §18's settings rail are that
|
|
340
|
+
screen.
|
|
341
|
+
|
|
342
|
+
"NOT FROM A PROP THE HOST SETS" IS ABOUT THE DEFAULT, AND data-density DOES
|
|
343
|
+
NOT BREAK IT. What §17j refuses is a package that CANNOT work out its own
|
|
344
|
+
density — one that draws a wrong row until a host wires something up, which
|
|
345
|
+
is how a drop-in stops being a drop-in. This box still answers that question
|
|
346
|
+
on its own and nothing has to be passed for it to be right. What the
|
|
347
|
+
attribute adds is the ability to DISAGREE, which is a different thing: the
|
|
348
|
+
crossover this package picked is a default, the host is the only party that
|
|
349
|
+
knows about print, a user preference or its own breakpoints, and an override
|
|
350
|
+
nobody uses costs the drop-in case nothing. See the crossover comment below
|
|
351
|
+
for why the number needs no ruling once it is a default. */
|
|
37
352
|
.ig-list {
|
|
353
|
+
container-name: ig-rail;
|
|
354
|
+
container-type: inline-size;
|
|
38
355
|
list-style: none;
|
|
39
356
|
margin: 0;
|
|
40
357
|
padding: 0;
|
|
41
358
|
}
|
|
42
359
|
|
|
43
|
-
/* ── the
|
|
360
|
+
/* ── the panel header ──────────────────────────────────────────────────── */
|
|
44
361
|
|
|
45
|
-
|
|
362
|
+
/* Present only when the host stated a fact; a document with none draws no
|
|
363
|
+
header at all, which is what keeps the pure-graph markup unchanged. */
|
|
364
|
+
.ig-header {
|
|
365
|
+
background: var(--ig-surface-2);
|
|
366
|
+
border-bottom: var(--ig-stroke) solid var(--ig-line);
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
gap: var(--ig-space);
|
|
370
|
+
margin: 0;
|
|
371
|
+
padding: var(--ig-space-loose) var(--ig-space-wide);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.ig-header-lead {
|
|
46
375
|
align-items: center;
|
|
47
|
-
|
|
376
|
+
display: inline-flex;
|
|
377
|
+
gap: var(--ig-space-snug);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* The segmented projection toggle §16a and §16b both draw. One rounded outline
|
|
381
|
+
around two buttons, the pressed one filled with the accent. */
|
|
382
|
+
.ig-toggle {
|
|
48
383
|
border: var(--ig-stroke) solid var(--ig-line);
|
|
49
384
|
border-radius: var(--ig-radius);
|
|
385
|
+
display: inline-flex;
|
|
386
|
+
overflow: hidden;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.ig-toggle-option {
|
|
390
|
+
align-items: center;
|
|
391
|
+
background: none;
|
|
392
|
+
border: 0;
|
|
393
|
+
color: var(--ig-text-body);
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
display: inline-flex;
|
|
396
|
+
font: inherit;
|
|
397
|
+
font-size: var(--ig-font-size-small);
|
|
398
|
+
gap: var(--ig-space-tight);
|
|
399
|
+
padding: var(--ig-space-micro) var(--ig-space-snug);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.ig-toggle-option[aria-pressed='true'] {
|
|
403
|
+
background: var(--ig-accent);
|
|
404
|
+
color: var(--ig-bg);
|
|
405
|
+
font-weight: var(--ig-weight-strong);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.ig-header-top {
|
|
409
|
+
align-items: center;
|
|
410
|
+
display: flex;
|
|
411
|
+
flex-wrap: wrap;
|
|
412
|
+
gap: var(--ig-space);
|
|
413
|
+
justify-content: space-between;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* The panel's own name, at the tracking the frame gives a frame-level label. */
|
|
417
|
+
.ig-header-label {
|
|
418
|
+
color: var(--ig-accent);
|
|
419
|
+
font-family: var(--ig-font-mono);
|
|
420
|
+
font-size: var(--ig-font-size-small);
|
|
421
|
+
letter-spacing: var(--ig-tracking-label);
|
|
422
|
+
text-transform: uppercase;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* THE COUNTS ARE CHIPS, NOT A SENTENCE. The frame gives each tally its own
|
|
426
|
+
outline and tints only the one a reader acts on — "how many could run right
|
|
427
|
+
now, against the cap" — so the accent is spent once instead of on a whole
|
|
428
|
+
line of muted prose. */
|
|
429
|
+
.ig-counts {
|
|
430
|
+
align-items: center;
|
|
431
|
+
display: flex;
|
|
432
|
+
flex-wrap: wrap;
|
|
433
|
+
gap: var(--ig-space-snug);
|
|
434
|
+
margin: 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.ig-count-chip {
|
|
438
|
+
background: var(--ig-bg);
|
|
439
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
440
|
+
border-radius: var(--ig-radius);
|
|
441
|
+
color: var(--ig-text-body);
|
|
442
|
+
font-family: var(--ig-font-mono);
|
|
443
|
+
font-size: var(--ig-font-size-small);
|
|
444
|
+
font-variant-numeric: tabular-nums;
|
|
445
|
+
padding: var(--ig-space-micro) var(--ig-space-snug);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.ig-count-chip[data-count='ready'] {
|
|
449
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-fill), transparent);
|
|
450
|
+
border-color: color-mix(in srgb, var(--ig-accent) var(--ig-tint-border), transparent);
|
|
451
|
+
color: var(--ig-text);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.ig-freshness {
|
|
455
|
+
align-items: center;
|
|
456
|
+
color: var(--ig-text-muted);
|
|
457
|
+
display: inline-flex;
|
|
458
|
+
font-family: var(--ig-font-mono);
|
|
459
|
+
font-size: var(--ig-font-size-small);
|
|
460
|
+
font-variant-numeric: tabular-nums;
|
|
461
|
+
gap: var(--ig-space-tight);
|
|
462
|
+
margin: 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/* STALE GOES GOLD, as the design draws it. The gold this palette already holds
|
|
466
|
+
is the conflict state's — "the document moved upstream" — and a stale mirror
|
|
467
|
+
is that fact about the whole document, so the state hue is reused rather
|
|
468
|
+
than a token added for one word. No new token: the theme's token list is
|
|
469
|
+
the contract every host theme has to fill. */
|
|
470
|
+
.ig-freshness[data-stale='true'] {
|
|
471
|
+
color: var(--ig-state-conflict);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* A control the viewer publishes and does not wire: the UA button reset, so it
|
|
475
|
+
reads as the inline control the frame draws beside the stamp. */
|
|
476
|
+
.ig-refresh,
|
|
477
|
+
.ig-size {
|
|
478
|
+
background: none;
|
|
479
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
480
|
+
border-radius: var(--ig-radius);
|
|
481
|
+
color: var(--ig-text);
|
|
482
|
+
cursor: pointer;
|
|
483
|
+
font: inherit;
|
|
484
|
+
padding: var(--ig-space-micro) var(--ig-space-snug);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.ig-refresh:hover,
|
|
488
|
+
.ig-size:hover {
|
|
489
|
+
border-color: var(--ig-accent);
|
|
490
|
+
color: var(--ig-accent);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* ── the panel-level notice ────────────────────────────────────────────── */
|
|
494
|
+
|
|
495
|
+
/* WHAT THE HOST SAYS IS TRUE OF THE WHOLE PANEL, drawn between the NOW row and
|
|
496
|
+
the order it qualifies. Its own surface, so it reads as a statement about the
|
|
497
|
+
panel rather than as a row that lost its rank. */
|
|
498
|
+
.ig-notice {
|
|
499
|
+
align-items: flex-start;
|
|
500
|
+
background: var(--ig-surface-2);
|
|
501
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
502
|
+
border-radius: var(--ig-radius);
|
|
503
|
+
display: flex;
|
|
504
|
+
gap: var(--ig-space-snug);
|
|
505
|
+
justify-content: space-between;
|
|
506
|
+
padding: var(--ig-space-snug) var(--ig-space-loose);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.ig-notice-text {
|
|
510
|
+
display: flex;
|
|
511
|
+
flex-direction: column;
|
|
512
|
+
gap: var(--ig-space-micro);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.ig-notice-head {
|
|
516
|
+
align-items: center;
|
|
517
|
+
color: var(--ig-text);
|
|
518
|
+
display: flex;
|
|
519
|
+
gap: var(--ig-space-tight);
|
|
520
|
+
margin: 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.ig-notice-body {
|
|
524
|
+
color: var(--ig-text-body);
|
|
525
|
+
margin: 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* A COUNT, SO IT SITS ON THE FIGURE FACE. A progress figure is read as a number,
|
|
529
|
+
and the proportional face makes its digits wander the way a rank column's do. */
|
|
530
|
+
.ig-notice-progress {
|
|
531
|
+
color: var(--ig-text-muted);
|
|
532
|
+
font-family: var(--ig-font-mono);
|
|
533
|
+
font-size: var(--ig-font-size-small);
|
|
534
|
+
font-variant-numeric: tabular-nums;
|
|
535
|
+
margin: 0;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/* THE SAME CONTROL RESET THE REFRESH BUTTON TAKES, and for the same reason: it
|
|
539
|
+
is published and not wired, so it must look like the inline control the frame
|
|
540
|
+
draws rather than like a UA button. The anchor arm gets it too — a host that
|
|
541
|
+
routes instead of acting should not get a different-looking affordance. */
|
|
542
|
+
.ig-notice-action {
|
|
543
|
+
background: none;
|
|
544
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
545
|
+
border-radius: var(--ig-radius);
|
|
546
|
+
color: var(--ig-text);
|
|
547
|
+
cursor: pointer;
|
|
548
|
+
font: inherit;
|
|
549
|
+
padding: var(--ig-space-micro) var(--ig-space-snug);
|
|
550
|
+
text-decoration: none;
|
|
551
|
+
white-space: nowrap;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.ig-notice-action:hover {
|
|
555
|
+
border-color: var(--ig-accent);
|
|
556
|
+
color: var(--ig-accent);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* GOLD ON FIRST IMPORT, and gold is the conflict state's hue — the same reuse
|
|
560
|
+
the stale stamp already makes, for the same reason: the token list is the
|
|
561
|
+
contract every host theme has to fill, so a token added for one state is a
|
|
562
|
+
theme every host has to revisit.
|
|
563
|
+
|
|
564
|
+
AND NOTHING FOR THE ERROR ARM. The design makes exactly one panel-level outline
|
|
565
|
+
claim, and it is this one; an error is drawn calm on purpose, because its own
|
|
566
|
+
sentence is that settings are safe and the pipeline is still running. A
|
|
567
|
+
failure-hued outline around the whole panel would say the opposite. */
|
|
568
|
+
.ig-viewer[data-ig-condition='importing'] {
|
|
569
|
+
outline: var(--ig-stroke) solid var(--ig-state-conflict);
|
|
570
|
+
outline-offset: var(--ig-space-micro);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.ig-notice[data-ig-condition='importing'] .ig-notice-head {
|
|
574
|
+
color: var(--ig-state-conflict);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.ig-notice[data-ig-condition='error'] .ig-notice-head {
|
|
578
|
+
color: var(--ig-state-failed);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* ── the adoption line ─────────────────────────────────────────────────── */
|
|
582
|
+
|
|
583
|
+
/* ONE QUIET LINE, which is the whole of what the design asks for here: the
|
|
584
|
+
day-one panel has to read complete and calm, and a block explaining what the
|
|
585
|
+
reader is missing is the opposite of calm. */
|
|
586
|
+
.ig-adoption {
|
|
587
|
+
align-items: center;
|
|
588
|
+
color: var(--ig-text-muted);
|
|
589
|
+
display: flex;
|
|
590
|
+
font-size: var(--ig-font-size-small);
|
|
591
|
+
gap: var(--ig-space-tight);
|
|
592
|
+
margin: 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/* THE SENTENCE IS NOT THE LINK. Underlining the whole line made the quietest
|
|
596
|
+
element on the panel its loudest; the link is a short label beside it. */
|
|
597
|
+
.ig-adoption-text {
|
|
598
|
+
color: var(--ig-text-muted);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.ig-adoption-link {
|
|
602
|
+
color: var(--ig-text-muted);
|
|
603
|
+
margin-left: auto;
|
|
604
|
+
text-decoration: underline;
|
|
605
|
+
white-space: nowrap;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.ig-adoption-link:hover {
|
|
609
|
+
color: var(--ig-accent);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/* PUBLISHED, NOT PERFORMED. The viewer never removes its own line; the host
|
|
613
|
+
re-renders without the note. */
|
|
614
|
+
.ig-adoption-dismiss {
|
|
615
|
+
background: none;
|
|
616
|
+
border: 0;
|
|
617
|
+
color: var(--ig-text-muted);
|
|
618
|
+
cursor: pointer;
|
|
619
|
+
font: inherit;
|
|
620
|
+
padding: 0;
|
|
621
|
+
text-decoration: underline;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.ig-adoption-dismiss:hover {
|
|
625
|
+
color: var(--ig-accent);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* ── working now ───────────────────────────────────────────────────────── */
|
|
629
|
+
|
|
630
|
+
.ig-now {
|
|
631
|
+
/* THE NOW STRIP IS PART OF THE RAIL'S RHYTHM, so it reads the same container
|
|
632
|
+
the rows do. It is its own list element, so without this the density rules
|
|
633
|
+
scoped to 'ig-rail' simply did not reach it and the strip stood 83px tall
|
|
634
|
+
beside 53px rows - 57% taller than everything under it, in the one slot
|
|
635
|
+
17j gives the top of the rail. */
|
|
636
|
+
container-name: ig-rail;
|
|
637
|
+
container-type: inline-size;
|
|
638
|
+
list-style: none;
|
|
639
|
+
margin: 0;
|
|
640
|
+
padding: 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* A BAND, NOT A CARD. The frame runs it full-bleed across the panel with a
|
|
644
|
+
wash and an accent rail down its leading edge — the same treatment the shell
|
|
645
|
+
gives anything in flight — so it reads as a state of the panel rather than
|
|
646
|
+
as the first row of the order. */
|
|
647
|
+
.ig-now-row {
|
|
648
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-wash), transparent);
|
|
649
|
+
border-bottom: var(--ig-stroke) solid var(--ig-line);
|
|
650
|
+
box-shadow: inset var(--ig-band-rail) 0 0 var(--ig-accent);
|
|
50
651
|
column-gap: var(--ig-space);
|
|
51
652
|
display: grid;
|
|
52
|
-
grid-template-columns: auto
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
653
|
+
grid-template-columns: auto 1fr auto;
|
|
654
|
+
align-items: center;
|
|
655
|
+
padding: var(--ig-space) var(--ig-space-wide);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.ig-now-mark {
|
|
659
|
+
background: var(--ig-accent);
|
|
660
|
+
border-radius: var(--ig-radius-small);
|
|
661
|
+
color: var(--ig-bg);
|
|
662
|
+
font-family: var(--ig-font-mono);
|
|
663
|
+
font-size: var(--ig-font-size-pill);
|
|
664
|
+
font-weight: var(--ig-weight-strong);
|
|
665
|
+
letter-spacing: var(--ig-tracking-pill);
|
|
666
|
+
padding: var(--ig-space-micro) var(--ig-space-tight);
|
|
667
|
+
text-transform: uppercase;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.ig-now-phase {
|
|
671
|
+
align-items: center;
|
|
672
|
+
color: var(--ig-accent);
|
|
673
|
+
display: inline-flex;
|
|
674
|
+
font-family: var(--ig-font-mono);
|
|
675
|
+
font-size: var(--ig-font-size-small);
|
|
676
|
+
gap: var(--ig-space-tight);
|
|
677
|
+
white-space: nowrap;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* The pulse is the frame's, and it is the one moving thing in the panel. */
|
|
681
|
+
.ig-now-pulse {
|
|
682
|
+
background: var(--ig-accent);
|
|
683
|
+
block-size: var(--ig-space-tight);
|
|
684
|
+
border-radius: 50%;
|
|
685
|
+
display: inline-block;
|
|
686
|
+
inline-size: var(--ig-space-tight);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* ── the order ─────────────────────────────────────────────────────────── */
|
|
690
|
+
|
|
691
|
+
/* TWO COLUMNS, WHICH IS THE FRAME'S GRID EXACTLY. The rank track holds the
|
|
692
|
+
figure and the readiness dot stacked, and everything else is one body
|
|
693
|
+
column, so a row has exactly two children and nothing can auto-place into
|
|
694
|
+
the rank track and set its width. That was a real defect: a single
|
|
695
|
+
relationship badge left unplaced widened the rank column for the whole list
|
|
696
|
+
and squeezed every title to pay for it. */
|
|
697
|
+
.ig-slot {
|
|
698
|
+
border-bottom: var(--ig-stroke) solid var(--ig-line);
|
|
699
|
+
column-gap: var(--ig-space);
|
|
700
|
+
display: grid;
|
|
701
|
+
grid-template-columns: var(--ig-rank-column) 1fr;
|
|
702
|
+
min-height: var(--ig-row-min-height);
|
|
703
|
+
padding: var(--ig-row-padding-block) var(--ig-space-wide);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* ── §17j's RAIL DENSITY ───────────────────────────────────────────────────
|
|
707
|
+
|
|
708
|
+
THE 53px ROW, AND THE ORDER THINGS LEAVE IT. '17j' fixes both, and the
|
|
709
|
+
reason it fixes the second is that the build *"showed everything because
|
|
710
|
+
nothing told it what to give up"*. This is that instruction.
|
|
711
|
+
|
|
712
|
+
9 pad + 17 title + 3 gap + 15 meta + 9 pad = 53px, divider inclusive
|
|
713
|
+
tracks: 30px rank · 1fr issue · auto delta
|
|
714
|
+
|
|
715
|
+
THE FINDING THIS ENCODES, which is the one to keep: at 6, 60 and 312 issues
|
|
716
|
+
the rail is IDENTICAL. What changes is around it — virtualisation appears,
|
|
717
|
+
the runner-hold footer grows, filter chips stop being optional, the canvas
|
|
718
|
+
starts refusing. *"If you are making the rail denser as the backlog grows,
|
|
719
|
+
you are compressing the surface that was never the problem."* So nothing
|
|
720
|
+
here keys off the backlog size, and nothing ever should.
|
|
721
|
+
|
|
722
|
+
430 IS THIS PACKAGE'S DEFAULT, NOT A CLAIM ABOUT THE DESIGN, AND A CONSUMER
|
|
723
|
+
CAN TAKE IT OVER. That is the whole posture of this package — sensible
|
|
724
|
+
defaults, everything overridable — and it is why this number needed no
|
|
725
|
+
ruling in the end. '17j' gives two anchors and no crossover: the workspace
|
|
726
|
+
rail is 390, §18's settings rail is 330 where steps 1-3 apply "by default",
|
|
727
|
+
and §16a's panel, which keeps provenance inline, is wider than either and
|
|
728
|
+
carries no published figure anywhere. 430 sits above the rail and below that
|
|
729
|
+
panel. A host that does nothing gets it and gets correct behaviour; a host
|
|
730
|
+
that disagrees sets data-density on the viewer root and this condition stops
|
|
731
|
+
deciding for them.
|
|
732
|
+
|
|
733
|
+
SO THE OPEN QUESTION IS CLOSED RATHER THAN WAITING. An earlier revision of
|
|
734
|
+
this comment filed the distance between 390 and 430 as needing a width for
|
|
735
|
+
§16a's panel. It does not: a DEFAULT only has to be reasonable, and the
|
|
736
|
+
figure would only have mattered if this were the single crossover every
|
|
737
|
+
consumer had to live with. It is not, so it is not blocking.
|
|
738
|
+
|
|
739
|
+
IT CANNOT BE A TOKEN, AND THE NEXT PERSON WILL TRY. Making the threshold a
|
|
740
|
+
theme property is the obvious reach, and it does not work: a container
|
|
741
|
+
condition takes a length and only a length. Measured in a browser — a
|
|
742
|
+
max-width condition written as a literal applies, and the same condition
|
|
743
|
+
naming a custom property PARSES INTO A RULE THAT MATCHES AT NO WIDTH AT ALL,
|
|
744
|
+
while CSS.supports() reports that same condition as supported. It fails
|
|
745
|
+
silently in both directions: the rule is not dropped, it is simply never
|
|
746
|
+
satisfied, and the one API that could have warned agrees it is fine. That is
|
|
747
|
+
a CSS limitation rather than a decision, and it is why the escape hatch below
|
|
748
|
+
is a selector rather than a property.
|
|
749
|
+
|
|
750
|
+
'17k' MAKES THE LOWER ANCHOR EXACT, which is worth keeping. It fixes the
|
|
751
|
+
three workspace layouts under one rule — "the rail never yields" — so the
|
|
752
|
+
workspace draws its rail at 390 at every width it supports and no amount of
|
|
753
|
+
resizing moves it across this threshold. Before that it fell to 312 below
|
|
754
|
+
1360, so 430 had to clear two numbers and only one was '17j's. The editor's
|
|
755
|
+
sheet pins that relationship rather than restating it: see its test "keeps
|
|
756
|
+
the workspace rail on the dense side of layer 1's crossover". */
|
|
757
|
+
@container ig-rail (max-width: 430px) {
|
|
758
|
+
${railDensity('.ig-viewer:not([data-density])')}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* THE ESCAPE HATCH ITSELF, AND IT IS THE SAME BLOCK WITH A DIFFERENT KEY.
|
|
762
|
+
Emitted a second time under an attribute that carries no width at all, so
|
|
763
|
+
a consumer who sets it gets the dense row wherever they want it. The
|
|
764
|
+
scopes above and below are mutually exclusive by construction — one is
|
|
765
|
+
:not([data-density]) and the other names a value — so nothing here
|
|
766
|
+
depends on which of them a browser reads first.
|
|
767
|
+
|
|
768
|
+
THE OTHER DIRECTION NEEDS NO RULES. data-density='wide' matches neither
|
|
769
|
+
scope, so the row falls back to the base density this sheet declares
|
|
770
|
+
above, at any container width. */
|
|
771
|
+
${railDensity(".ig-viewer[data-density='dense']")}
|
|
772
|
+
|
|
773
|
+
/* STEPS 1-3 OF THE DROP ORDER, AT §18's 330px SETTINGS RAIL, which '17j'
|
|
774
|
+
names as the width where they apply by default.
|
|
775
|
+
|
|
776
|
+
THE SELECTORS ARE THE MARKUP'S OWN ATTRIBUTES, not class guesses: evidence
|
|
777
|
+
is 'data-evidence', a relationship is 'data-edge', a priority is
|
|
778
|
+
'data-priority' with 'query' / 'tier' / 'promoted'. Three of these were
|
|
779
|
+
written from memory first and two were wrong — the parts publish
|
|
780
|
+
'data-evidence="verified"', never a 'data-ig-badge'. */
|
|
781
|
+
|
|
782
|
+
/* STEP 1 — evidence and verification chips. '17j': *"Reassurance, not an
|
|
783
|
+
answer to any question the rail is asked."* */
|
|
784
|
+
@container ig-rail (max-width: 360px) {
|
|
785
|
+
.ig-slot .ig-badge[data-evidence] {
|
|
786
|
+
display: none;
|
|
787
|
+
}
|
|
56
788
|
}
|
|
57
789
|
|
|
790
|
+
/* STEP 2 — relationship badges past the FIRST, which is kept because it is
|
|
791
|
+
the one causing the hold. Scoped to 'data-edge' so it counts relationships
|
|
792
|
+
and not the priority, evidence and readiness chips that share '.ig-badge';
|
|
793
|
+
a positional 'nth-of-type' would have hidden whichever chip happened to sit
|
|
794
|
+
second, which is not what the rule says at all. The '+n more' chip carries
|
|
795
|
+
what left, and it already exists — 'data-omitted'. */
|
|
796
|
+
@container ig-rail (max-width: 345px) {
|
|
797
|
+
.ig-slot .ig-badge[data-edge] ~ .ig-badge[data-edge] {
|
|
798
|
+
display: none;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* STEP 3 — the priority token. A DECLARED TIER goes, because '17j' says it is
|
|
803
|
+
*"recoverable from the rank itself"*. A PROMOTION stays: 'P3 → 0' is the
|
|
804
|
+
one form the rank cannot give back, and it is rank provenance rather than a
|
|
805
|
+
restatement. A matched query stays for the same reason — it names WHICH
|
|
806
|
+
query, which no rank encodes. */
|
|
807
|
+
@container ig-rail (max-width: 335px) {
|
|
808
|
+
.ig-slot .ig-badge[data-priority='tier'] {
|
|
809
|
+
display: none;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/* THE ONE ROW GROUND THE FRAME DRAWS, and it is not a stripe. A together unit
|
|
814
|
+
is the compound station, and the tint says so; the other ranked rows carry
|
|
815
|
+
no background at all. */
|
|
816
|
+
.ig-slot[data-unit='true'] {
|
|
817
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-unit), transparent);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* A HELD ROW IS HATCHED, NOT DASHED. It keeps its position in the sequence —
|
|
821
|
+
"why isn't my P1 running" is answerable in place — so it must read as
|
|
822
|
+
unavailable without leaving the grid the rows above and below hang off. A
|
|
823
|
+
dashed border around one row breaks that alignment; a ground does not. */
|
|
58
824
|
.ig-slot[data-held='true'] {
|
|
59
|
-
background:
|
|
60
|
-
|
|
825
|
+
background-image: repeating-linear-gradient(
|
|
826
|
+
135deg,
|
|
827
|
+
color-mix(in srgb, var(--ig-text) var(--ig-tint-unit), transparent) 0 var(--ig-space-tight),
|
|
828
|
+
transparent var(--ig-space-tight) calc(var(--ig-space-tight) * 2)
|
|
829
|
+
);
|
|
61
830
|
}
|
|
62
831
|
|
|
63
832
|
.ig-slot[aria-current='true'] {
|
|
64
|
-
|
|
833
|
+
box-shadow: inset var(--ig-band-rail) 0 0 var(--ig-accent);
|
|
65
834
|
}
|
|
66
835
|
|
|
67
836
|
/* A CANVAS-OWNED NODE IS SELECTABLE TOO, and only the rail rows had a selected
|
|
@@ -75,16 +844,67 @@ export const viewerStylesheet = `
|
|
|
75
844
|
stroke-width: calc(var(--ig-stroke) * 2);
|
|
76
845
|
}
|
|
77
846
|
|
|
847
|
+
/* The rank track: figure over dot, centred, so the title column starts at one
|
|
848
|
+
x on every row whether or not the row has a number to print. */
|
|
849
|
+
.ig-rank-cell {
|
|
850
|
+
align-items: center;
|
|
851
|
+
display: flex;
|
|
852
|
+
flex-direction: column;
|
|
853
|
+
gap: var(--ig-space-tight);
|
|
854
|
+
}
|
|
855
|
+
|
|
78
856
|
.ig-rank {
|
|
79
857
|
color: var(--ig-text);
|
|
80
858
|
font-family: var(--ig-font-mono);
|
|
859
|
+
font-size: var(--ig-font-size-rank);
|
|
81
860
|
font-variant-numeric: tabular-nums;
|
|
82
|
-
|
|
83
|
-
|
|
861
|
+
font-weight: var(--ig-weight-strong);
|
|
862
|
+
line-height: 1;
|
|
84
863
|
}
|
|
85
864
|
|
|
86
865
|
.ig-rank[data-held='true'] {
|
|
87
866
|
color: var(--ig-text-muted);
|
|
867
|
+
font-weight: var(--ig-weight-regular);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.ig-row-body {
|
|
871
|
+
display: flex;
|
|
872
|
+
flex-direction: column;
|
|
873
|
+
gap: var(--ig-space-snug);
|
|
874
|
+
min-width: 0;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* The title, the identity and the two-line pair they form: 2px apart, which is
|
|
878
|
+
what makes them read as one object rather than as two rows of metadata. */
|
|
879
|
+
.ig-row-head {
|
|
880
|
+
display: flex;
|
|
881
|
+
flex-direction: column;
|
|
882
|
+
gap: var(--ig-space-micro);
|
|
883
|
+
min-width: 0;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/* A UNIT AT THE WIDE DENSITY IS ITS ENCLOSURE, which is what §16a draws: the
|
|
887
|
+
pill, the note, and a member list. The ordinary head the row also carries is
|
|
888
|
+
the RAIL's way of drawing the same unit, so it stands down here rather than
|
|
889
|
+
printing the lead's title above a box that already opens with it. */
|
|
890
|
+
.ig-slot[data-unit='true'] > .ig-row-body > .ig-row-head {
|
|
891
|
+
display: none;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.ig-unit-count {
|
|
895
|
+
display: none;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/* LINE 2 AT THE WIDE DENSITY. §16a's panel draws the same sequence — identity
|
|
899
|
+
then chips — and simply has room, so here it WRAPS rather than clipping and
|
|
900
|
+
the provenance turnstile stays inline beneath it. Same markup, two
|
|
901
|
+
densities; the rail's rules are in the container query above. */
|
|
902
|
+
.ig-row-meta {
|
|
903
|
+
align-items: center;
|
|
904
|
+
display: flex;
|
|
905
|
+
flex-wrap: wrap;
|
|
906
|
+
gap: var(--ig-space-tight);
|
|
907
|
+
min-width: 0;
|
|
88
908
|
}
|
|
89
909
|
|
|
90
910
|
/* ── readiness stations ────────────────────────────────────────────────── */
|
|
@@ -93,7 +913,6 @@ export const viewerStylesheet = `
|
|
|
93
913
|
block-size: var(--ig-station-size);
|
|
94
914
|
border: var(--ig-stroke) solid var(--ig-station-held);
|
|
95
915
|
border-radius: 50%;
|
|
96
|
-
box-shadow: 0 0 0 var(--ig-station-halo) var(--ig-bg);
|
|
97
916
|
display: inline-block;
|
|
98
917
|
inline-size: var(--ig-station-size);
|
|
99
918
|
}
|
|
@@ -116,35 +935,84 @@ export const viewerStylesheet = `
|
|
|
116
935
|
|
|
117
936
|
/* ── titles, identity, provenance ──────────────────────────────────────── */
|
|
118
937
|
|
|
938
|
+
/* NOTHING TRUNCATES. The ellipsis this rule used to carry, against a FIXED row
|
|
939
|
+
height, is the direct cause of the frame's titles arriving on screen as
|
|
940
|
+
"Retype the ca…" — a panel whose job is to say what the work is, declining
|
|
941
|
+
to say what the work is. A row grows instead; --ig-row-min-height is a
|
|
942
|
+
floor, not a height. anywhere covers the one case wrapping cannot: an
|
|
943
|
+
unbroken token wider than the column, which would otherwise overflow the
|
|
944
|
+
panel rather than the line. */
|
|
119
945
|
.ig-title {
|
|
120
946
|
color: var(--ig-text);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
947
|
+
font-size: var(--ig-font-size-row);
|
|
948
|
+
overflow-wrap: anywhere;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.ig-slot[data-held='true'] .ig-title,
|
|
952
|
+
.ig-footer .ig-title {
|
|
953
|
+
color: var(--ig-text-body);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* A held row's CONTENT recedes, not its position. The frame dims the body and
|
|
957
|
+
leaves the rank track at full strength, so the row still reads as occupying
|
|
958
|
+
the slot it would have taken. */
|
|
959
|
+
.ig-slot[data-held='true'] .ig-row-body {
|
|
960
|
+
opacity: 0.72;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/* The isolated-issue tally sits directly on the panel, outside the padded
|
|
964
|
+
groups, so it carries the panel's own inset rather than none. */
|
|
965
|
+
.ig-count {
|
|
966
|
+
margin: 0;
|
|
967
|
+
padding: var(--ig-space) var(--ig-space-wide);
|
|
124
968
|
}
|
|
125
969
|
|
|
126
970
|
.ig-id,
|
|
127
971
|
.ig-count {
|
|
128
972
|
color: var(--ig-text-muted);
|
|
129
973
|
font-family: var(--ig-font-mono);
|
|
974
|
+
font-size: var(--ig-font-size-small);
|
|
130
975
|
font-variant-numeric: tabular-nums;
|
|
131
976
|
}
|
|
132
977
|
|
|
133
978
|
.ig-link {
|
|
134
|
-
color:
|
|
979
|
+
color: inherit;
|
|
135
980
|
text-decoration: none;
|
|
136
981
|
}
|
|
137
982
|
|
|
138
983
|
.ig-link:hover {
|
|
984
|
+
color: var(--ig-accent);
|
|
139
985
|
text-decoration: underline;
|
|
140
986
|
}
|
|
141
987
|
|
|
988
|
+
/* THE PROVENANCE LINE IS NOT MUTED. It is the answer to "why is this here",
|
|
989
|
+
which is the panel's whole reason to exist, so the frame gives it body text
|
|
990
|
+
and lifts the named parts to the brightest ink on the page. Muting the whole
|
|
991
|
+
sentence — which is what shipped — files the explanation under decoration. */
|
|
142
992
|
.ig-provenance,
|
|
143
993
|
.ig-hold,
|
|
144
994
|
.ig-caveat {
|
|
145
|
-
|
|
995
|
+
align-items: baseline;
|
|
996
|
+
color: var(--ig-text-body);
|
|
997
|
+
display: flex;
|
|
998
|
+
font-family: var(--ig-font-mono);
|
|
146
999
|
font-size: var(--ig-font-size-small);
|
|
147
|
-
|
|
1000
|
+
gap: var(--ig-space-tight);
|
|
1001
|
+
margin: 0;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.ig-provenance .ig-id,
|
|
1005
|
+
.ig-hold .ig-id,
|
|
1006
|
+
.ig-caveat .ig-id {
|
|
1007
|
+
color: var(--ig-text);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/* The turnstile the frame puts in front of every explanation, so the line reads
|
|
1011
|
+
as subordinate to the row above it without being dimmed. */
|
|
1012
|
+
.ig-turn {
|
|
1013
|
+
color: var(--ig-text-muted);
|
|
1014
|
+
font-size: var(--ig-font-size-micro);
|
|
1015
|
+
flex: 0 0 auto;
|
|
148
1016
|
}
|
|
149
1017
|
|
|
150
1018
|
.ig-strike {
|
|
@@ -153,155 +1021,224 @@ export const viewerStylesheet = `
|
|
|
153
1021
|
|
|
154
1022
|
/* ── edge badges ───────────────────────────────────────────────────────── */
|
|
155
1023
|
|
|
156
|
-
/* SPANNED FROM THE TITLE COLUMN, LIKE EVERY OTHER FULL-WIDTH ROW MEMBER. Left
|
|
157
|
-
unplaced this was the fifth child of a four-column grid, so it auto-placed
|
|
158
|
-
into column ONE of a second row — the rank track, an auto-sized column that
|
|
159
|
-
takes the width of its widest item. One relationship badge then set the width
|
|
160
|
-
of the rank column for the whole row, pushing the station and title across
|
|
161
|
-
and squeezing the title to pay for it. Badges belong with the row's other
|
|
162
|
-
metadata, which is what 3 / -1 already means here for provenance and holds.
|
|
163
|
-
(No backticks in this file: the stylesheet is a template literal.) */
|
|
164
1024
|
.ig-badges {
|
|
165
1025
|
display: flex;
|
|
166
1026
|
flex-wrap: wrap;
|
|
167
1027
|
gap: var(--ig-space-tight);
|
|
168
|
-
grid-column: 3 / -1;
|
|
169
1028
|
}
|
|
170
1029
|
|
|
1030
|
+
/* A TINTED CHIP, NOT A BARE OUTLINE. The frame fills every relationship chip
|
|
1031
|
+
with its own hue at a low alpha inside a heavier border of the same hue;
|
|
1032
|
+
drawn as an outline alone the badge row reads as a row of empty boxes, and
|
|
1033
|
+
the hue channel carries a third of the weight the design gives it. The two
|
|
1034
|
+
alphas are the theme's, so a host retinting a relationship retints its chip. */
|
|
171
1035
|
.ig-badge {
|
|
172
1036
|
align-items: center;
|
|
173
|
-
|
|
174
|
-
colours deliberately — see below. Left as currentColor the border would
|
|
175
|
-
follow the text to --ig-text and the hue channel would vanish from the
|
|
176
|
-
badge entirely, which is the opposite of the fix. */
|
|
1037
|
+
background: var(--ig-surface-2);
|
|
177
1038
|
border: var(--ig-stroke) solid var(--ig-line);
|
|
178
|
-
border-radius: var(--ig-radius);
|
|
1039
|
+
border-radius: var(--ig-radius-small);
|
|
179
1040
|
color: var(--ig-text);
|
|
180
1041
|
display: inline-flex;
|
|
181
|
-
font-
|
|
1042
|
+
font-family: var(--ig-font-mono);
|
|
1043
|
+
font-size: var(--ig-font-size-micro);
|
|
182
1044
|
gap: var(--ig-space-tight);
|
|
183
|
-
padding:
|
|
1045
|
+
padding: var(--ig-space-micro) var(--ig-space-tight);
|
|
184
1046
|
}
|
|
185
1047
|
|
|
186
|
-
/* THE HUE IS A BORDER
|
|
1048
|
+
/* THE HUE IS A BORDER AND A FILL HERE, NOT A TEXT COLOUR — the theme holds the
|
|
187
1049
|
edge hues to the 3:1 NON-TEXT bar, and says so where it defines them, so
|
|
188
|
-
painting badge LABELS with them
|
|
1050
|
+
painting badge LABELS with them contradicts the palette's own claim. It was
|
|
189
1051
|
not merely theoretical: at this size duplicate-of measured 3.98:1 on
|
|
190
1052
|
--ig-surface and decomposed-from 4.37:1, both under the 4.5:1 the text test
|
|
191
|
-
asserts for every text colour. The label
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
1053
|
+
asserts for every text colour. The label takes --ig-text, which that test
|
|
1054
|
+
already proves on all three surfaces, and the hue keeps the non-text use it
|
|
1055
|
+
was measured for. This is a DELIBERATE DEPARTURE from the frame, which tints
|
|
1056
|
+
its chip labels; the frame's own colour-blind-safety clause says hue is one
|
|
1057
|
+
of four redundant channels, and here it is carried by two of them.
|
|
1058
|
+
NO CHANNEL IS LOST. The dash pattern and the glyph are untouched, and the
|
|
196
1059
|
vocabulary test independently proves all five stay distinguishable with hue
|
|
197
1060
|
removed ENTIRELY, which is the stronger claim. */
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.
|
|
201
|
-
|
|
202
|
-
|
|
1061
|
+
/* WRITTEN OUT PER EDGE RATHER THAN THROUGH ONE INTERMEDIATE PROPERTY. A
|
|
1062
|
+
an intermediate --ig-badge-hue set here and read here would be a property no
|
|
1063
|
+
theme and no layout declares, and styles.test.ts refuses those on purpose:
|
|
1064
|
+
the whole point of that guard is that every var() in this file names
|
|
1065
|
+
something a host or the layout actually sets. */
|
|
1066
|
+
.ig-badge[data-edge='blocked-by'] {
|
|
1067
|
+
background: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-fill), transparent);
|
|
1068
|
+
border-color: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-border), transparent);
|
|
1069
|
+
border-style: solid;
|
|
1070
|
+
}
|
|
203
1071
|
|
|
204
|
-
.ig-
|
|
205
|
-
|
|
1072
|
+
.ig-badge[data-edge='serialize-with'] {
|
|
1073
|
+
background: color-mix(in srgb, var(--ig-edge-serialize-with) var(--ig-tint-fill), transparent);
|
|
1074
|
+
border-color: color-mix(in srgb, var(--ig-edge-serialize-with) var(--ig-tint-border), transparent);
|
|
1075
|
+
border-style: double;
|
|
206
1076
|
}
|
|
207
1077
|
|
|
208
|
-
|
|
1078
|
+
.ig-badge[data-edge='together-with'] {
|
|
1079
|
+
background: color-mix(in srgb, var(--ig-edge-together-with) var(--ig-tint-fill), transparent);
|
|
1080
|
+
border-color: color-mix(in srgb, var(--ig-edge-together-with) var(--ig-tint-border), transparent);
|
|
1081
|
+
border-style: solid;
|
|
1082
|
+
}
|
|
209
1083
|
|
|
210
|
-
.ig-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
1084
|
+
.ig-badge[data-edge='duplicate-of'] {
|
|
1085
|
+
background: color-mix(in srgb, var(--ig-edge-duplicate-of) var(--ig-tint-fill), transparent);
|
|
1086
|
+
border-color: color-mix(in srgb, var(--ig-edge-duplicate-of) var(--ig-tint-border), transparent);
|
|
1087
|
+
border-style: dotted;
|
|
214
1088
|
}
|
|
215
1089
|
|
|
216
|
-
.ig-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
1090
|
+
.ig-badge[data-edge='decomposed-from'] {
|
|
1091
|
+
background: color-mix(in srgb, var(--ig-edge-decomposed-from) var(--ig-tint-fill), transparent);
|
|
1092
|
+
border-color: color-mix(in srgb, var(--ig-edge-decomposed-from) var(--ig-tint-border), transparent);
|
|
1093
|
+
border-style: dashed;
|
|
220
1094
|
}
|
|
221
1095
|
|
|
222
|
-
/*
|
|
1096
|
+
/* A PROMOTION IS THE ONE CHIP THE FRAME ACCENTS. It is the most interesting
|
|
1097
|
+
event the panel can show — a low tier pulled to the top because it blocks a
|
|
1098
|
+
high one — and the design spends its accent on exactly that. */
|
|
1099
|
+
.ig-badge[data-priority='promoted'] {
|
|
1100
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-fill), transparent);
|
|
1101
|
+
border-color: color-mix(in srgb, var(--ig-accent) var(--ig-tint-border), transparent);
|
|
1102
|
+
}
|
|
223
1103
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1104
|
+
.ig-badge[data-evidence='verified'] {
|
|
1105
|
+
background: color-mix(in srgb, var(--ig-station-ready) var(--ig-tint-fill), transparent);
|
|
1106
|
+
border-color: color-mix(in srgb, var(--ig-station-ready) var(--ig-tint-border), transparent);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* A caveat or hold-label chip carries no relationship hue: it is a word, not
|
|
1110
|
+
an edge, so it keeps the line colour the plain badge has. */
|
|
1111
|
+
.ig-badge[data-caveat],
|
|
1112
|
+
.ig-badge[data-hold] {
|
|
1113
|
+
color: var(--ig-text-body);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
.ig-badge[data-caveat='preview-only'] {
|
|
1117
|
+
border-color: color-mix(in srgb, var(--ig-state-conflict) var(--ig-tint-border), transparent);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.ig-badge[data-not-ready='true'] {
|
|
1121
|
+
background: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-fill), transparent);
|
|
1122
|
+
border-color: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-border), transparent);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.ig-glyph {
|
|
1126
|
+
font-family: var(--ig-font-mono);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/* ── the together unit: one rank, two issues ───────────────────────────── */
|
|
1130
|
+
|
|
1131
|
+
.ig-unit-mark {
|
|
1132
|
+
align-items: center;
|
|
228
1133
|
display: flex;
|
|
229
|
-
|
|
230
|
-
gap: var(--ig-space);
|
|
231
|
-
margin: 0 0 var(--ig-space);
|
|
1134
|
+
gap: var(--ig-space-snug);
|
|
232
1135
|
}
|
|
233
1136
|
|
|
234
|
-
.ig-
|
|
235
|
-
|
|
1137
|
+
.ig-unit-pill {
|
|
1138
|
+
background: var(--ig-accent);
|
|
1139
|
+
border-radius: var(--ig-radius-small);
|
|
1140
|
+
color: var(--ig-bg);
|
|
1141
|
+
font-family: var(--ig-font-mono);
|
|
1142
|
+
font-size: var(--ig-font-size-pill);
|
|
1143
|
+
font-weight: var(--ig-weight-strong);
|
|
1144
|
+
letter-spacing: var(--ig-tracking-badge);
|
|
1145
|
+
padding: var(--ig-space-micro) var(--ig-space-tight);
|
|
1146
|
+
text-transform: uppercase;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.ig-unit-note {
|
|
236
1150
|
color: var(--ig-text-muted);
|
|
237
|
-
font-
|
|
238
|
-
font-
|
|
1151
|
+
font-family: var(--ig-font-mono);
|
|
1152
|
+
font-size: var(--ig-font-size-micro);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/* THE MEMBERS GET ONE LINE EACH. Joining them with a separator — which is what
|
|
1156
|
+
shipped — makes a two-issue unit read as one issue with a long title, and
|
|
1157
|
+
the unit is the one place the design draws an enclosure to say the opposite. */
|
|
1158
|
+
/* THE UNIT'S ENCLOSURE IS THE together-with MARK. It is drawn at the
|
|
1159
|
+
connector's hairline width, which is what that token has always meant: finer
|
|
1160
|
+
than an ordinary edge, so the enclosure stays the primary read. */
|
|
1161
|
+
.ig-unit {
|
|
1162
|
+
border: var(--ig-stroke-connector) solid color-mix(in srgb, var(--ig-edge-together-with) var(--ig-tint-border), transparent);
|
|
1163
|
+
border-radius: var(--ig-radius);
|
|
1164
|
+
list-style: none;
|
|
239
1165
|
margin: 0;
|
|
1166
|
+
overflow: hidden;
|
|
1167
|
+
padding: 0;
|
|
240
1168
|
}
|
|
241
1169
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
.ig-freshness[data-stale='true'] {
|
|
248
|
-
color: var(--ig-state-conflict);
|
|
1170
|
+
.ig-unit-member {
|
|
1171
|
+
display: flex;
|
|
1172
|
+
flex-direction: column;
|
|
1173
|
+
gap: var(--ig-space-micro);
|
|
1174
|
+
padding: var(--ig-space-snug) var(--ig-space);
|
|
249
1175
|
}
|
|
250
1176
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
.ig-refresh {
|
|
254
|
-
background: none;
|
|
255
|
-
border: 0;
|
|
256
|
-
color: var(--ig-accent);
|
|
257
|
-
cursor: pointer;
|
|
258
|
-
font: inherit;
|
|
259
|
-
margin-left: var(--ig-space-tight);
|
|
260
|
-
padding: 0;
|
|
1177
|
+
.ig-unit-member + .ig-unit-member {
|
|
1178
|
+
border-top: var(--ig-stroke) solid var(--ig-line);
|
|
261
1179
|
}
|
|
262
1180
|
|
|
263
|
-
.ig-
|
|
264
|
-
|
|
1181
|
+
.ig-unit-member .ig-title {
|
|
1182
|
+
font-size: var(--ig-font-size-compact);
|
|
265
1183
|
}
|
|
266
1184
|
|
|
267
|
-
.ig-
|
|
268
|
-
|
|
269
|
-
margin: 0 0 var(--ig-space);
|
|
270
|
-
padding: 0;
|
|
1185
|
+
.ig-unit-member .ig-id {
|
|
1186
|
+
font-size: var(--ig-font-size-meta);
|
|
271
1187
|
}
|
|
272
1188
|
|
|
273
|
-
/*
|
|
274
|
-
|
|
275
|
-
.ig-
|
|
1189
|
+
/* ── the footer group: holds that earn no rank slot ────────────────────── */
|
|
1190
|
+
|
|
1191
|
+
.ig-footer {
|
|
1192
|
+
background: var(--ig-surface-2);
|
|
1193
|
+
padding: var(--ig-space) var(--ig-space-wide);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.ig-footer-head {
|
|
276
1197
|
align-items: center;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
column-gap: var(--ig-space);
|
|
281
|
-
display: grid;
|
|
282
|
-
grid-template-columns: auto 1fr auto auto;
|
|
283
|
-
min-height: var(--ig-row-height);
|
|
284
|
-
padding: var(--ig-space-tight) var(--ig-space);
|
|
1198
|
+
display: flex;
|
|
1199
|
+
gap: var(--ig-space);
|
|
1200
|
+
justify-content: space-between;
|
|
285
1201
|
}
|
|
286
1202
|
|
|
287
|
-
.ig-
|
|
288
|
-
color: var(--ig-
|
|
289
|
-
font-
|
|
290
|
-
|
|
291
|
-
text-transform: uppercase;
|
|
1203
|
+
.ig-footer-title {
|
|
1204
|
+
color: var(--ig-text-body);
|
|
1205
|
+
font-size: var(--ig-font-size-compact);
|
|
1206
|
+
margin: 0;
|
|
292
1207
|
}
|
|
293
1208
|
|
|
294
|
-
.ig-
|
|
1209
|
+
.ig-footer-labels {
|
|
295
1210
|
color: var(--ig-text-muted);
|
|
296
|
-
font-
|
|
297
|
-
|
|
1211
|
+
font-family: var(--ig-font-mono);
|
|
1212
|
+
font-size: var(--ig-font-size-meta);
|
|
298
1213
|
}
|
|
299
1214
|
|
|
300
|
-
/*
|
|
301
|
-
|
|
302
|
-
.
|
|
303
|
-
.ig-
|
|
304
|
-
|
|
1215
|
+
/* ONE LINE EACH, and the frame is explicit about why: these are not facts
|
|
1216
|
+
about the work, so they earn no rank slot and no explanation block. A full
|
|
1217
|
+
row here — station, badges, provenance — was claiming the opposite. */
|
|
1218
|
+
.ig-footer .ig-list {
|
|
1219
|
+
display: flex;
|
|
1220
|
+
flex-direction: column;
|
|
1221
|
+
gap: var(--ig-space-snug);
|
|
1222
|
+
margin-top: var(--ig-space);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.ig-footer-row {
|
|
1226
|
+
align-items: center;
|
|
1227
|
+
display: flex;
|
|
1228
|
+
gap: var(--ig-space-snug);
|
|
1229
|
+
opacity: 0.72;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
.ig-footer-row .ig-title {
|
|
1233
|
+
font-size: var(--ig-font-size-compact);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
.ig-footer-row .ig-id {
|
|
1237
|
+
font-size: var(--ig-font-size-meta);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.ig-footer-row[aria-current='true'] {
|
|
1241
|
+
opacity: 1;
|
|
305
1242
|
}
|
|
306
1243
|
|
|
307
1244
|
/* ── the graph canvas ──────────────────────────────────────────────────── */
|
|
@@ -325,7 +1262,26 @@ export const viewerStylesheet = `
|
|
|
325
1262
|
inline-size: var(--ig-stage-w);
|
|
326
1263
|
}
|
|
327
1264
|
|
|
328
|
-
/* The
|
|
1265
|
+
/* The three column headers, which say what each column is FOR. Without them
|
|
1266
|
+
the gutters read as two more piles of issues rather than as the two answers
|
|
1267
|
+
the spine deliberately keeps off itself. */
|
|
1268
|
+
.ig-column-head {
|
|
1269
|
+
color: var(--ig-text-muted);
|
|
1270
|
+
font-family: var(--ig-font-mono);
|
|
1271
|
+
font-size: var(--ig-font-size-pill);
|
|
1272
|
+
letter-spacing: var(--ig-tracking-group);
|
|
1273
|
+
position: absolute;
|
|
1274
|
+
text-transform: uppercase;
|
|
1275
|
+
inset-block-start: var(--ig-space-snug);
|
|
1276
|
+
inset-inline-start: var(--ig-col-x);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.ig-column-head[data-column='spine'] {
|
|
1280
|
+
color: var(--ig-accent);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/* The node cards and the station column, sitting on the coordinates the layout
|
|
1284
|
+
computed. */
|
|
329
1285
|
.ig-rail {
|
|
330
1286
|
inset: 0;
|
|
331
1287
|
pointer-events: none;
|
|
@@ -333,16 +1289,147 @@ export const viewerStylesheet = `
|
|
|
333
1289
|
}
|
|
334
1290
|
|
|
335
1291
|
.ig-rail-row {
|
|
336
|
-
block-size:
|
|
1292
|
+
block-size: auto;
|
|
337
1293
|
inline-size: var(--ig-row-w);
|
|
338
1294
|
inset-block-start: var(--ig-row-y);
|
|
339
1295
|
inset-inline-start: var(--ig-row-x);
|
|
340
|
-
|
|
341
|
-
min-block-size: 0;
|
|
1296
|
+
min-block-size: var(--ig-row-h);
|
|
342
1297
|
pointer-events: auto;
|
|
343
1298
|
position: absolute;
|
|
344
1299
|
}
|
|
345
1300
|
|
|
1301
|
+
/* A CARD, NOT A LABELLED RECTANGLE. Drawing the node's text in SVG is what
|
|
1302
|
+
forced every title through a width fit and out the other side truncated; an
|
|
1303
|
+
HTML card wraps, so the acceptance criterion "nothing truncates" is met by
|
|
1304
|
+
construction rather than by choosing a wider box. */
|
|
1305
|
+
.ig-card {
|
|
1306
|
+
background: var(--ig-surface);
|
|
1307
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
1308
|
+
border-radius: var(--ig-radius-large);
|
|
1309
|
+
display: flex;
|
|
1310
|
+
flex-direction: column;
|
|
1311
|
+
gap: var(--ig-space-tight);
|
|
1312
|
+
padding: var(--ig-space) var(--ig-space);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.ig-card .ig-title {
|
|
1316
|
+
font-size: var(--ig-font-size-compact);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.ig-card .ig-id {
|
|
1320
|
+
font-size: var(--ig-font-size-meta);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
.ig-card[data-column='left'] {
|
|
1324
|
+
background: var(--ig-surface-2);
|
|
1325
|
+
border-color: color-mix(in srgb, var(--ig-edge-blocked-by) var(--ig-tint-border), transparent);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/* NEVER WORKED READS AS NEVER DRAWN IN. The right gutter's two node states are
|
|
1329
|
+
the design's own: a dashed provenance origin and a dotted duplicate, both on
|
|
1330
|
+
no ground at all, so the eye files them as outside the order before reading
|
|
1331
|
+
a word. */
|
|
1332
|
+
.ig-card[data-column='right'] {
|
|
1333
|
+
background: transparent;
|
|
1334
|
+
border-style: dashed;
|
|
1335
|
+
opacity: 0.8;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
.ig-card[data-exclusion='true'] {
|
|
1339
|
+
border-style: dotted;
|
|
1340
|
+
border-color: color-mix(in srgb, var(--ig-edge-duplicate-of) var(--ig-tint-border), transparent);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.ig-card[data-held='true'] {
|
|
1344
|
+
background: transparent;
|
|
1345
|
+
border-style: dashed;
|
|
1346
|
+
opacity: 0.75;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.ig-card[data-now='true'] {
|
|
1350
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-wash), transparent);
|
|
1351
|
+
border-color: color-mix(in srgb, var(--ig-accent) var(--ig-tint-border), transparent);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
.ig-card[data-unit='true'] {
|
|
1355
|
+
background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-unit), transparent);
|
|
1356
|
+
border-color: var(--ig-accent);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.ig-rail-row[aria-current='true'] .ig-card {
|
|
1360
|
+
border-color: var(--ig-accent);
|
|
1361
|
+
box-shadow: 0 0 0 var(--ig-band-rail) color-mix(in srgb, var(--ig-accent) var(--ig-tint-border), transparent);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/* The station column: a disc on the spine, carrying the rank the row would
|
|
1365
|
+
otherwise have to be read for. */
|
|
1366
|
+
.ig-spine-station {
|
|
1367
|
+
align-items: center;
|
|
1368
|
+
background: var(--ig-bg);
|
|
1369
|
+
block-size: var(--ig-station-box);
|
|
1370
|
+
border: calc(var(--ig-stroke) * 2) solid var(--ig-station-ready);
|
|
1371
|
+
border-radius: 50%;
|
|
1372
|
+
box-shadow: 0 0 0 var(--ig-station-halo) var(--ig-bg);
|
|
1373
|
+
color: var(--ig-station-pending);
|
|
1374
|
+
display: flex;
|
|
1375
|
+
font-family: var(--ig-font-mono);
|
|
1376
|
+
font-size: var(--ig-font-size-small);
|
|
1377
|
+
font-weight: var(--ig-weight-heavy);
|
|
1378
|
+
inline-size: var(--ig-station-box);
|
|
1379
|
+
inset-block-start: var(--ig-station-y);
|
|
1380
|
+
inset-inline-start: var(--ig-station-x);
|
|
1381
|
+
justify-content: center;
|
|
1382
|
+
pointer-events: none;
|
|
1383
|
+
position: absolute;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.ig-spine-station[data-fill='filled'] {
|
|
1387
|
+
background: var(--ig-station-ready);
|
|
1388
|
+
color: var(--ig-bg);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.ig-spine-station[data-fill='hollow'] {
|
|
1392
|
+
border-color: var(--ig-station-ready);
|
|
1393
|
+
color: var(--ig-station-ready);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
.ig-spine-station[data-fill='dashed'] {
|
|
1397
|
+
border-style: dashed;
|
|
1398
|
+
border-width: var(--ig-stroke);
|
|
1399
|
+
border-color: var(--ig-station-held);
|
|
1400
|
+
color: var(--ig-text-muted);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/* The serialize hold is gold in the frame, on the station as well as the arc:
|
|
1404
|
+
a station that waits for a PEER is a different fact from one that waits for
|
|
1405
|
+
a rank above it. */
|
|
1406
|
+
/* THE RING TAKES THE HUE, THE DIGIT DOES NOT. §16b draws both in gold, and the
|
|
1407
|
+
theme holds every edge hue to the 3:1 NON-TEXT bar — so painting the rank
|
|
1408
|
+
figure with it makes a claim the palette does not support, on the one glyph
|
|
1409
|
+
in the station a reader has to actually read. The hue keeps the ring, which
|
|
1410
|
+
is the channel it was measured for; the number keeps the ink every text
|
|
1411
|
+
contrast test already proves. */
|
|
1412
|
+
.ig-spine-station[data-wait='serialize'] {
|
|
1413
|
+
border-color: var(--ig-edge-serialize-with);
|
|
1414
|
+
color: var(--ig-text);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.ig-spine {
|
|
1418
|
+
stroke: var(--ig-line);
|
|
1419
|
+
stroke-width: calc(var(--ig-stroke) * 2);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
.ig-more {
|
|
1423
|
+
color: var(--ig-text-muted);
|
|
1424
|
+
font-family: var(--ig-font-mono);
|
|
1425
|
+
font-size: var(--ig-font-size-micro);
|
|
1426
|
+
inline-size: var(--ig-row-w);
|
|
1427
|
+
inset-block-start: var(--ig-row-y);
|
|
1428
|
+
inset-inline-start: var(--ig-row-x);
|
|
1429
|
+
position: absolute;
|
|
1430
|
+
text-align: center;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
346
1433
|
.ig-node {
|
|
347
1434
|
fill: var(--ig-surface);
|
|
348
1435
|
stroke: var(--ig-line);
|
|
@@ -372,18 +1459,15 @@ export const viewerStylesheet = `
|
|
|
372
1459
|
.ig-edge[data-edge='decomposed-from'] { stroke: var(--ig-edge-decomposed-from); }
|
|
373
1460
|
|
|
374
1461
|
/* The dash pattern is set per element from the edge vocabulary, never here —
|
|
375
|
-
one source for the channel the colour-blind-safety claim rests on.
|
|
1462
|
+
one source for the channel the colour-blind-safety claim rests on. Drawn only
|
|
1463
|
+
in the legend now: a together unit is ONE card with its members listed inside
|
|
1464
|
+
it, so the canvas has no pair of boxes to surround. */
|
|
376
1465
|
.ig-enclosure {
|
|
377
1466
|
fill: none;
|
|
378
1467
|
stroke: var(--ig-edge-together-with);
|
|
379
1468
|
stroke-width: var(--ig-stroke);
|
|
380
1469
|
}
|
|
381
1470
|
|
|
382
|
-
.ig-connector {
|
|
383
|
-
stroke: var(--ig-edge-together-with);
|
|
384
|
-
stroke-width: var(--ig-stroke-connector);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
1471
|
/* currentColor on the marker resolves to the inherited text colour, not to the
|
|
388
1472
|
edge's stroke — so a terminal has to be given the hue explicitly or it renders
|
|
389
1473
|
in body text and the fourth channel silently collapses. */
|
|
@@ -392,6 +1476,35 @@ export const viewerStylesheet = `
|
|
|
392
1476
|
stroke-width: var(--ig-stroke);
|
|
393
1477
|
}
|
|
394
1478
|
|
|
1479
|
+
/* A MARK IS HIT-TESTABLE, AND THAT IS THE OPPOSITE OF WHAT IT FIRST SAID.
|
|
1480
|
+
|
|
1481
|
+
It carried a pointer-events none rule, reasoning from the halo: a decoration
|
|
1482
|
+
over a line must not swallow the click aimed at the line. That reasoning does
|
|
1483
|
+
not transfer, because a halo IS the line again and a mark is NOT — every one
|
|
1484
|
+
of these is deliberately offset AWAY from the path, so a click on it does not
|
|
1485
|
+
fall through onto the edge. It falls through onto the canvas, and the viewer's
|
|
1486
|
+
walk climbs to the canvas group and reports a click on nothing, clearing the
|
|
1487
|
+
very selection the reader was making. A conflict's companion is the clearest
|
|
1488
|
+
case: a visibly separate line that could not be pointed at.
|
|
1489
|
+
|
|
1490
|
+
That is a defect this package has already paid for once. The overlay module
|
|
1491
|
+
keeps its pointer identity on the dash clone for exactly this reason, and says
|
|
1492
|
+
so: dropping the identity makes a clone a dead zone over its own edge. Every
|
|
1493
|
+
mark here publishes the same identity, so making it unhittable spent the
|
|
1494
|
+
identity it had just been given.
|
|
1495
|
+
|
|
1496
|
+
Colour is set per element, from the token name the caller supplied, for the
|
|
1497
|
+
reason the edge hues are set per element: one source for a channel. Nothing
|
|
1498
|
+
here paints a state, because nothing here knows what a state is. */
|
|
1499
|
+
.ig-edge-mark {
|
|
1500
|
+
font-family: var(--ig-font-ui);
|
|
1501
|
+
font-size: var(--ig-font-size-small);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
.ig-edge-companion {
|
|
1505
|
+
stroke-width: var(--ig-stroke);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
395
1508
|
.ig-terminal[data-edge='blocked-by'] { color: var(--ig-edge-blocked-by); }
|
|
396
1509
|
.ig-terminal[data-edge='serialize-with'] { color: var(--ig-edge-serialize-with); }
|
|
397
1510
|
.ig-terminal[data-edge='together-with'] { color: var(--ig-edge-together-with); }
|
|
@@ -406,6 +1519,7 @@ export const viewerStylesheet = `
|
|
|
406
1519
|
border: var(--ig-stroke) solid var(--ig-line);
|
|
407
1520
|
border-radius: var(--ig-radius);
|
|
408
1521
|
color: var(--ig-text-body);
|
|
1522
|
+
margin: var(--ig-space-wide);
|
|
409
1523
|
padding: var(--ig-space);
|
|
410
1524
|
}
|
|
411
1525
|
|
|
@@ -445,6 +1559,10 @@ export const viewerStylesheet = `
|
|
|
445
1559
|
padding: 0;
|
|
446
1560
|
}
|
|
447
1561
|
|
|
1562
|
+
.ig-tree {
|
|
1563
|
+
padding: var(--ig-space) var(--ig-space-wide);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
448
1566
|
.ig-tree .ig-list {
|
|
449
1567
|
border-left: var(--ig-stroke) dashed var(--ig-edge-decomposed-from);
|
|
450
1568
|
margin-left: var(--ig-space);
|
|
@@ -457,19 +1575,44 @@ export const viewerStylesheet = `
|
|
|
457
1575
|
|
|
458
1576
|
/* ── legend ────────────────────────────────────────────────────────────── */
|
|
459
1577
|
|
|
1578
|
+
/* A FOOTER BAR, WHERE §16b PUTS IT, and worded rather than glyph-only. It sits
|
|
1579
|
+
under the drawing it explains, carries a drawn sample of each line so the
|
|
1580
|
+
dash and terminal channels are legible cold, and ends with the readiness key
|
|
1581
|
+
the station fills need. */
|
|
460
1582
|
.ig-legend {
|
|
1583
|
+
align-items: center;
|
|
1584
|
+
background: var(--ig-surface-2);
|
|
461
1585
|
border: 0;
|
|
1586
|
+
border-top: var(--ig-stroke) solid var(--ig-line);
|
|
462
1587
|
display: flex;
|
|
463
1588
|
flex-wrap: wrap;
|
|
464
|
-
gap: var(--ig-space);
|
|
465
|
-
margin: 0
|
|
466
|
-
padding:
|
|
1589
|
+
gap: var(--ig-space-wide);
|
|
1590
|
+
margin: 0;
|
|
1591
|
+
padding: var(--ig-space) var(--ig-space-wide);
|
|
467
1592
|
}
|
|
468
1593
|
|
|
469
1594
|
.ig-legend-caption {
|
|
470
1595
|
color: var(--ig-text-muted);
|
|
471
|
-
font-
|
|
1596
|
+
font-family: var(--ig-font-mono);
|
|
1597
|
+
font-size: var(--ig-font-size-pill);
|
|
1598
|
+
letter-spacing: var(--ig-tracking-group);
|
|
472
1599
|
padding: 0;
|
|
1600
|
+
text-transform: uppercase;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
.ig-legend-item {
|
|
1604
|
+
align-items: center;
|
|
1605
|
+
color: var(--ig-text-body);
|
|
1606
|
+
display: inline-flex;
|
|
1607
|
+
font-family: var(--ig-font-mono);
|
|
1608
|
+
font-size: var(--ig-font-size-meta);
|
|
1609
|
+
gap: var(--ig-space-tight);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.ig-legend-keys {
|
|
1613
|
+
display: inline-flex;
|
|
1614
|
+
gap: var(--ig-space-loose);
|
|
1615
|
+
margin-inline-start: auto;
|
|
473
1616
|
}
|
|
474
1617
|
`;
|
|
475
1618
|
//# sourceMappingURL=styles.js.map
|