@orkestrel/scaffold 0.0.58 → 0.0.60
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/host/agents/skills/enterprise-bootstrap/SKILL.md +91 -82
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +5 -5
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +10 -10
- package/dist/host/agents/skills/enterprise-bootstrap/references/inputs.md +501 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +167 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +2 -2
- package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +4 -1
- package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +71 -50
- package/dist/host/agents/skills/orkestrel-prove-journey/SKILL.md +93 -29
- package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-prove-journey/references/captures.md +62 -38
- package/dist/host/agents/skills/orkestrel-prove-journey/references/decide.md +68 -0
- package/dist/host/agents/skills/orkestrel-prove-journey/references/layer.md +107 -79
- package/dist/host/agents/skills/orkestrel-prove-journey/references/statechart.md +84 -0
- package/dist/host/agents/skills/orkestrel-prove-journey/references/styles.md +87 -0
- package/dist/host/claude/agents/orkestrel.md +11 -10
- package/dist/host/claude/skills/orkestrel-prove-journey/SKILL.md +1 -1
- package/dist/host/manifest.json +43 -13
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/src/core/index.cjs +5 -5
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +5 -5
- package/dist/src/core/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Input affordances
|
|
2
|
+
|
|
3
|
+
Pick an affordance from what the person is asked for, not from the name a schema gives the field.
|
|
4
|
+
Where one category draws several ways, let the density and the list size decide.
|
|
5
|
+
|
|
6
|
+
Read [The fixed state set](#the-fixed-state-set) before the catalog: every affordance handles that
|
|
7
|
+
same set, and each category names only what it adds or changes. Take the data states a whole surface
|
|
8
|
+
ships — ideal, empty, loading, partial, error — from
|
|
9
|
+
[bootstrap-reference.md](bootstrap-reference.md) → The data states instead.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [The fixed state set](#the-fixed-state-set)
|
|
14
|
+
- [Rules that cross every category](#rules-that-cross-every-category)
|
|
15
|
+
- [The catalog](#the-catalog)
|
|
16
|
+
- [Where Bootstrap ships no component](#where-bootstrap-ships-no-component)
|
|
17
|
+
|
|
18
|
+
## The fixed state set
|
|
19
|
+
|
|
20
|
+
Draw every one of these for every affordance you place. A category adds `empty` and `full` when its
|
|
21
|
+
value is a set.
|
|
22
|
+
|
|
23
|
+
- **rest** — no pointer, no keyboard focus, the value the field holds.
|
|
24
|
+
- **hover** — pointer over the control. The chrome moves, the value does not.
|
|
25
|
+
- **focus-visible** — keyboard focus carrying the ring the theme ships. Never write `outline: none`.
|
|
26
|
+
- **disabled** — not editable and not submitted. Use the `disabled` attribute; the contrast bars
|
|
27
|
+
exempt it.
|
|
28
|
+
- **locked** — not editable and still submitted. Use `readonly` on a control that honors it, and
|
|
29
|
+
`disabled` plus a carrier that submits the value on one that does not.
|
|
30
|
+
- **invalid** — `is-invalid` on the control, a sibling `.invalid-feedback` message,
|
|
31
|
+
`aria-invalid="true"`, and the message wired to the control with `aria-describedby`.
|
|
32
|
+
- **busy** — waiting on work the person cannot see: a select whose options are still loading, a
|
|
33
|
+
field checking a value against a server. Mark the region `aria-busy="true"` and show a
|
|
34
|
+
`spinner-border spinner-border-sm` in the control's own chrome. Leave the control operable unless
|
|
35
|
+
its value depends on the work.
|
|
36
|
+
- **required** — state the requirement in the visible label and set the `required` attribute on the
|
|
37
|
+
control. A `text-danger` asterisk is decoration and carries `aria-hidden="true"`; the word in the
|
|
38
|
+
label is what a screen reader user gets.
|
|
39
|
+
- **with help** — a `.form-text` under the control, wired with `aria-describedby` beside the error
|
|
40
|
+
message rather than in place of it.
|
|
41
|
+
- **empty** — the set holds nothing. Say what an entry would be, not "nothing here".
|
|
42
|
+
- **full** — the set is at its cap. State the cap and stop accepting, rather than dropping an entry
|
|
43
|
+
silently.
|
|
44
|
+
|
|
45
|
+
## Rules that cross every category
|
|
46
|
+
|
|
47
|
+
- **Keep a read-only field on the same affordance the edit state uses.** Take `readonly`, or
|
|
48
|
+
`disabled` plus a carrier, and neutralize the chrome with one transparent combination declared
|
|
49
|
+
once by name — the combination is a class contract, so declare it and reuse it rather than
|
|
50
|
+
retyping the utilities. Never swap to `form-control-plaintext`: it drops the horizontal padding,
|
|
51
|
+
so the read view and the edit view reflow against each other.
|
|
52
|
+
- **Give a locked select `disabled` and a hidden input beside it.** A native select cannot be
|
|
53
|
+
read-only, so `disabled` stops its value submitting and the hidden input carries that value.
|
|
54
|
+
- **Give a chosen filter an accent tone class, not the neutral outline.** A `btn-outline-secondary`
|
|
55
|
+
label reads as chosen in light and as muted in dark, so one markup says opposite things.
|
|
56
|
+
- **Give each field one visible label**, per [bootstrap-reference.md](bootstrap-reference.md) →
|
|
57
|
+
Forms in production. Take labels, validation timing, and the error summary from that section, and
|
|
58
|
+
the affordance that carries them from [The catalog](#the-catalog).
|
|
59
|
+
- **Match the control sizes in a row.** Take `form-control-sm`, `form-select-sm`, `input-group-sm`,
|
|
60
|
+
and `btn-sm` together so a dense row shares one height.
|
|
61
|
+
- **Where Bootstrap ships no component, name the APG pattern the hand-roll owes** and route the
|
|
62
|
+
build-or-buy decision to [bootstrap-reference.md](bootstrap-reference.md) → When not to hand-roll.
|
|
63
|
+
Take those categories and their patterns from
|
|
64
|
+
[Where Bootstrap ships no component](#where-bootstrap-ships-no-component).
|
|
65
|
+
|
|
66
|
+
## The catalog
|
|
67
|
+
|
|
68
|
+
### One line of text
|
|
69
|
+
|
|
70
|
+
**Default.** An `input.form-control` under its own `label.form-label`, at rung 1.
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<label for="account-name" class="form-label">Account name</label>
|
|
74
|
+
<input type="text" class="form-control" id="account-name" aria-describedby="account-name-help" />
|
|
75
|
+
<div id="account-name-help" class="form-text">The name on the invoice.</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Alternates.** Take `.form-floating` when the row is too dense for a label line and the field is
|
|
79
|
+
never empty at rest. Take `.input-group` with `.input-group-text` when a prefix, a unit, or an
|
|
80
|
+
adjacent action belongs to the field; add `.has-validation` to the group so the feedback keeps the
|
|
81
|
+
border radius. Inside a dense grid cell, keep `form-control` and neutralize its chrome with the
|
|
82
|
+
declared transparent combination rather than dropping the control.
|
|
83
|
+
|
|
84
|
+
**States.** The fixed set, and nothing more.
|
|
85
|
+
|
|
86
|
+
### Text over many lines
|
|
87
|
+
|
|
88
|
+
**Default.** A `textarea.form-control` with a `rows` attribute sized to the expected answer, at
|
|
89
|
+
rung 1.
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<label for="incident-notes" class="form-label">Notes</label>
|
|
93
|
+
<textarea class="form-control" id="incident-notes" rows="3"></textarea>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Alternates.** Take `.form-floating` when the surrounding rows use it, and set the height with a
|
|
97
|
+
stylesheet rule or a component variable rather than a `style` attribute. Draw a one-row composer that
|
|
98
|
+
grows as the person types from this same control with a scripted height. Bootstrap ships no
|
|
99
|
+
rich-text editor, so treat one as a hand-roll.
|
|
100
|
+
|
|
101
|
+
**States.** The fixed set, plus `full` where a character cap bounds the answer. Show the remaining
|
|
102
|
+
count in the `.form-text`, and keep it out of a live region unless the cap is close.
|
|
103
|
+
|
|
104
|
+
### A secret
|
|
105
|
+
|
|
106
|
+
**Default.** An `input[type=password].form-control` under a visible label, at rung 1.
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<label for="passphrase" class="form-label">Passphrase</label>
|
|
110
|
+
<div class="input-group">
|
|
111
|
+
<input type="password" class="form-control" id="passphrase" autocomplete="current-password" />
|
|
112
|
+
<button type="button" class="btn btn-outline-secondary" aria-pressed="false">Show</button>
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Alternates.** Take the `.input-group` reveal button whenever the value is typed rather than pasted
|
|
117
|
+
from a manager, and toggle `aria-pressed` with the input `type`. Never block paste, and never mask a
|
|
118
|
+
one-time code the person must read back.
|
|
119
|
+
|
|
120
|
+
**States.** The fixed set. A strength or availability check runs as `busy` while it waits, not as
|
|
121
|
+
`invalid`.
|
|
122
|
+
|
|
123
|
+
### A number
|
|
124
|
+
|
|
125
|
+
**Default.** An `input[type=number].form-control`, at rung 1. In a column of figures add
|
|
126
|
+
`text-end font-monospace` so the digits align, at rung 2.
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<label for="unit-count" class="form-label">Units</label>
|
|
130
|
+
<input type="number" class="form-control text-end font-monospace" id="unit-count" step="1" />
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Alternates.** Take `.input-group` with `.input-group-text` for a currency symbol or a unit, so the
|
|
134
|
+
unit is chrome rather than something the person must type. Where the value is an identifier rather
|
|
135
|
+
than a quantity, take the one-line-of-text category instead.
|
|
136
|
+
|
|
137
|
+
**States.** The fixed set. Validate the range on blur and state the bound in the message.
|
|
138
|
+
|
|
139
|
+
### A number in a bounded range
|
|
140
|
+
|
|
141
|
+
**Default.** An `input.form-range`, at rung 1, and only when a minimum, a maximum, and a step are all
|
|
142
|
+
fixed.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<label for="threshold" class="form-label">Threshold</label>
|
|
146
|
+
<div class="d-flex align-items-center gap-2">
|
|
147
|
+
<input type="range" class="form-range" id="threshold" min="0" max="100" step="5" />
|
|
148
|
+
<output for="threshold" class="font-monospace">50</output>
|
|
149
|
+
</div>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Alternates.** Keep a number input beside or instead of the slider when an exact value matters — a
|
|
153
|
+
range paints no read-out of its own, so a lone slider hides the value it sets. A two-thumb range is a
|
|
154
|
+
hand-roll: Bootstrap ships one thumb per input.
|
|
155
|
+
|
|
156
|
+
**States.** The fixed set. A disabled range still shows its value, so keep the read-out visible.
|
|
157
|
+
|
|
158
|
+
### A date
|
|
159
|
+
|
|
160
|
+
**Default.** An `input[type=date].form-control`, at rung 1. Take the calendar, the keyboard model,
|
|
161
|
+
and the locale format from the platform rather than authoring any of them.
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<label for="starts" class="form-label">Starts</label>
|
|
165
|
+
<input type="date" class="form-control" id="starts" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Alternates.** For a period, take two native inputs — a start and an end — before reaching for a
|
|
169
|
+
range picker, and validate the order on blur. Treat a calendar grid of your own as a hand-roll.
|
|
170
|
+
|
|
171
|
+
**States.** The fixed set. Express an unavailable day with `min`, `max`, and a stated rule in the
|
|
172
|
+
help text, because a native picker takes no per-day exclusion.
|
|
173
|
+
|
|
174
|
+
### A time
|
|
175
|
+
|
|
176
|
+
**Default.** An `input[type=time].form-control`, at rung 1, with `step` set to the granularity the
|
|
177
|
+
value actually carries.
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<label for="cutoff" class="form-label">Cutoff</label>
|
|
181
|
+
<input type="time" class="form-control" id="cutoff" step="900" />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Alternates.** Treat segmented numeric fields as a hand-roll owing a spinbutton contract per
|
|
185
|
+
segment. Where the person picks from a fixed set of slots, take the one-of-many category instead: a
|
|
186
|
+
select is the lighter control.
|
|
187
|
+
|
|
188
|
+
**States.** The fixed set.
|
|
189
|
+
|
|
190
|
+
### A date and time
|
|
191
|
+
|
|
192
|
+
**Default.** An `input[type=datetime-local].form-control`, at rung 1.
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<label for="window-opens" class="form-label">Window opens</label>
|
|
196
|
+
<input type="datetime-local" class="form-control" id="window-opens" />
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Alternates.** Split into a date field and a time field when the two halves validate apart, when
|
|
200
|
+
one half is optional, or when a time zone control belongs between them. Name the zone the value is
|
|
201
|
+
stored in; a local datetime carries none.
|
|
202
|
+
|
|
203
|
+
**States.** The fixed set.
|
|
204
|
+
|
|
205
|
+
### A color
|
|
206
|
+
|
|
207
|
+
**Default.** An `input.form-control-color[type=color]`, at rung 1.
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<label for="brand-tint" class="form-label">Brand tint</label>
|
|
211
|
+
<input type="color" class="form-control form-control-color" id="brand-tint" value="#4a6fa5" />
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Alternates.** Pair the swatch with a text field when the value is copied, pasted, or read aloud
|
|
215
|
+
between people. Keep the swatch at a 24×24px target or larger.
|
|
216
|
+
|
|
217
|
+
**States.** The fixed set. A color input has no empty value, so give the field a default and say what
|
|
218
|
+
it is.
|
|
219
|
+
|
|
220
|
+
### One on/off answer
|
|
221
|
+
|
|
222
|
+
**Default.** A `.form-check` holding one `input.form-check-input[type=checkbox]` and its
|
|
223
|
+
`label.form-check-label`, at rung 1.
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<div class="form-check">
|
|
227
|
+
<input class="form-check-input" type="checkbox" id="send-receipt" />
|
|
228
|
+
<label class="form-check-label" for="send-receipt">Email me a receipt</label>
|
|
229
|
+
</div>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Alternates.** Take `.form-check.form-switch` with `role="switch"` when the change applies the
|
|
233
|
+
moment it is flipped, and keep the plain checkbox when the value commits on submit. A lone box is
|
|
234
|
+
one answer; a group of boxes holding a list is a different category, so read the any-of rows before
|
|
235
|
+
grouping boxes.
|
|
236
|
+
|
|
237
|
+
**States.** The fixed set. A switch that applies immediately is `busy` while the change is in
|
|
238
|
+
flight, and reverts visibly when it fails.
|
|
239
|
+
|
|
240
|
+
### One of a few
|
|
241
|
+
|
|
242
|
+
**Default.** A radio group: `fieldset` and `legend` around `.form-check` rows, at rung 1.
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<fieldset>
|
|
246
|
+
<legend class="form-label">Billing cycle</legend>
|
|
247
|
+
<div class="form-check">
|
|
248
|
+
<input class="form-check-input" type="radio" name="cycle" id="cycle-monthly" />
|
|
249
|
+
<label class="form-check-label" for="cycle-monthly">Monthly</label>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="form-check">
|
|
252
|
+
<input class="form-check-input" type="radio" name="cycle" id="cycle-annual" />
|
|
253
|
+
<label class="form-check-label" for="cycle-annual">Annual</label>
|
|
254
|
+
</div>
|
|
255
|
+
</fieldset>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Alternates.** Take a segmented `.btn-group` of `.btn-check` radios, at rung 2, when the choice
|
|
259
|
+
sits in a toolbar or a filter bar and every option fits on one row without wrapping; give the group
|
|
260
|
+
`role="radiogroup"` and one accessible name. A radio group and a segmented group draw the same
|
|
261
|
+
question, and the list size decides between them. Give a chosen filter an accent tone class rather
|
|
262
|
+
than `btn-outline-secondary`.
|
|
263
|
+
|
|
264
|
+
**States.** The fixed set, applied to the group rather than to one option. Mark the group invalid,
|
|
265
|
+
name it in the message, and keep the error under the last row.
|
|
266
|
+
|
|
267
|
+
### One of many
|
|
268
|
+
|
|
269
|
+
**Default.** A `select.form-select`, at rung 1.
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<label for="territory" class="form-label">Territory</label>
|
|
273
|
+
<select class="form-select" id="territory">
|
|
274
|
+
<option value="" selected disabled>Choose a territory</option>
|
|
275
|
+
<option value="emea">EMEA</option>
|
|
276
|
+
</select>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Alternates.** Drop to a radio group when the whole list fits in view and the options deserve
|
|
280
|
+
comparison. Move up to a searched list when the person can name the value faster than they can find
|
|
281
|
+
it, or when the list outgrows one scroll of the menu.
|
|
282
|
+
|
|
283
|
+
**States.** The fixed set, plus `empty` when the option list itself is empty — say why, and offer
|
|
284
|
+
the action that fills it. A locked select is `disabled`, so its value stops submitting; carry it in a
|
|
285
|
+
hidden input. A select whose options are loading is `busy`.
|
|
286
|
+
|
|
287
|
+
### One of many with an unlisted value admitted
|
|
288
|
+
|
|
289
|
+
**Default.** An `input.form-control` bound to a `<datalist>`, at rung 1. The list suggests; the
|
|
290
|
+
person can still submit a value it does not hold.
|
|
291
|
+
|
|
292
|
+
```html
|
|
293
|
+
<label for="carrier" class="form-label">Carrier</label>
|
|
294
|
+
<input class="form-control" list="carrier-options" id="carrier" />
|
|
295
|
+
<datalist id="carrier-options">
|
|
296
|
+
<option value="Northwind Freight"></option>
|
|
297
|
+
</datalist>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Alternates.** A combobox with free text is a hand-roll; take it only when the suggestions must be
|
|
301
|
+
fetched as the person types.
|
|
302
|
+
|
|
303
|
+
**States.** The fixed set. Attaching `list` changes the control's computed role to `combobox`, so
|
|
304
|
+
re-target every test and journey that finds this field by role.
|
|
305
|
+
|
|
306
|
+
### Any of a few
|
|
307
|
+
|
|
308
|
+
**Default.** `fieldset` and `legend` around `.form-check` checkbox rows sharing one name, at rung 1.
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<fieldset>
|
|
312
|
+
<legend class="form-label">Notify me about</legend>
|
|
313
|
+
<div class="form-check">
|
|
314
|
+
<input class="form-check-input" type="checkbox" name="notify" id="notify-billing" />
|
|
315
|
+
<label class="form-check-label" for="notify-billing">Billing</label>
|
|
316
|
+
</div>
|
|
317
|
+
</fieldset>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Alternates.** Take `.form-check-inline` when the options are short words and the row has space.
|
|
321
|
+
Bootstrap ships no `.form-check` color class, so an accent or danger box is an authored rule over
|
|
322
|
+
tokens — take it only under the exception in [inspection.md](inspection.md) → When an authored rule
|
|
323
|
+
is already earned.
|
|
324
|
+
|
|
325
|
+
**States.** The fixed set, plus `empty` and `full`. State a minimum or a maximum count in the help
|
|
326
|
+
text and validate it on the group.
|
|
327
|
+
|
|
328
|
+
### Any of many
|
|
329
|
+
|
|
330
|
+
**Default.** A bounded, scrollable list of `.form-check` rows inside a bordered box, at rung 2, with
|
|
331
|
+
a filter field preceding it so the person can narrow the list before choosing.
|
|
332
|
+
|
|
333
|
+
```html
|
|
334
|
+
<label for="regions-filter" class="form-label">Regions</label>
|
|
335
|
+
<input type="search" class="form-control form-control-sm mb-2" id="regions-filter" />
|
|
336
|
+
<div class="border rounded overflow-auto p-2 mh-100" role="group" aria-label="Regions">
|
|
337
|
+
<div class="form-check">
|
|
338
|
+
<input class="form-check-input" type="checkbox" name="regions" id="region-emea" />
|
|
339
|
+
<label class="form-check-label" for="region-emea">EMEA</label>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Alternates.** Take `select[multiple].form-select` where the platform control is acceptable to the
|
|
345
|
+
audience; its multi-select gesture is unteachable in a consumer flow but familiar in an internal
|
|
346
|
+
tool. A tags input is the ordered-set category. Bound the box's height from the layout that holds
|
|
347
|
+
it — Bootstrap ships `mh-100` and no other maximum-height step — never from a `style` attribute.
|
|
348
|
+
|
|
349
|
+
**States.** The fixed set, plus `empty` and `full`. Show the chosen count beside the list and give a
|
|
350
|
+
one-action way to clear it.
|
|
351
|
+
|
|
352
|
+
### A value picked from a searched list
|
|
353
|
+
|
|
354
|
+
**Default.** A combobox composed from shipped classes at rung 2, with the keyboard model
|
|
355
|
+
hand-rolled against the APG combobox pattern: an `input.form-control` carrying `role="combobox"`,
|
|
356
|
+
`aria-expanded`, `aria-controls`, `aria-autocomplete="list"`, and `aria-activedescendant`, over a
|
|
357
|
+
`ul.dropdown-menu[role=listbox]` of `.dropdown-item` buttons.
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<div class="input-group">
|
|
361
|
+
<input
|
|
362
|
+
class="form-control"
|
|
363
|
+
type="text"
|
|
364
|
+
role="combobox"
|
|
365
|
+
aria-expanded="false"
|
|
366
|
+
aria-controls="owner-listbox"
|
|
367
|
+
aria-autocomplete="list"
|
|
368
|
+
id="owner"
|
|
369
|
+
/>
|
|
370
|
+
<button type="button" class="btn btn-outline-secondary" aria-label="Clear">Clear</button>
|
|
371
|
+
</div>
|
|
372
|
+
<ul class="dropdown-menu show w-100 shadow" id="owner-listbox" role="listbox">
|
|
373
|
+
<li><button type="button" class="dropdown-item" role="option">Northwind Freight</button></li>
|
|
374
|
+
</ul>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Alternates.** Take the datalist in [One of many with an unlisted value
|
|
378
|
+
admitted](#one-of-many-with-an-unlisted-value-admitted) when the suggestions are static and short,
|
|
379
|
+
and pay for the combobox only when the list is fetched, ranked, or long enough to need one.
|
|
380
|
+
|
|
381
|
+
**States.** The fixed set, plus `empty` for a search that matched nothing — draw that with
|
|
382
|
+
`.dropdown-item-text`, never with an empty menu. The menu is `busy` while a query is in flight, and
|
|
383
|
+
the input stays operable throughout.
|
|
384
|
+
|
|
385
|
+
### Files
|
|
386
|
+
|
|
387
|
+
**Default.** An `input[type=file].form-control`, at rung 1.
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<label for="statement" class="form-label">Statement</label>
|
|
391
|
+
<input class="form-control" type="file" id="statement" accept=".csv" />
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Alternates.** Drive a hidden input from a button, a card, or a dropzone when the surface wants a
|
|
395
|
+
larger target; keep that input in the markup as the non-drag path, because a drag-only upload
|
|
396
|
+
strands keyboard and assistive-technology users. Dropzone chrome is an authored class contract over
|
|
397
|
+
tokens, and it owes a visible focus state of its own.
|
|
398
|
+
|
|
399
|
+
**States.** The fixed set, plus `empty` and `full`. Name the accepted types and the size cap in the
|
|
400
|
+
help text before the person picks, and report a rejected file beside the input rather than in a
|
|
401
|
+
toast.
|
|
402
|
+
|
|
403
|
+
### An ordered set of tags
|
|
404
|
+
|
|
405
|
+
**Default.** Bootstrap ships no tags input. Compose one at rung 2 from a text field that commits on
|
|
406
|
+
Enter plus a row of chips, each chip a `.badge` carrying a `btn-close` with its own accessible name.
|
|
407
|
+
|
|
408
|
+
```html
|
|
409
|
+
<label for="tag-entry" class="form-label">Tags</label>
|
|
410
|
+
<input class="form-control" type="text" id="tag-entry" aria-describedby="tag-entry-help" />
|
|
411
|
+
<div id="tag-entry-help" class="form-text">Press Enter to add a tag.</div>
|
|
412
|
+
<ul class="list-unstyled d-flex flex-wrap gap-2 mt-2">
|
|
413
|
+
<li>
|
|
414
|
+
<span class="badge text-bg-secondary d-inline-flex align-items-center gap-1">
|
|
415
|
+
Priority
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
class="btn-close"
|
|
419
|
+
data-bs-theme="dark"
|
|
420
|
+
aria-label="Remove Priority"
|
|
421
|
+
></button>
|
|
422
|
+
</span>
|
|
423
|
+
</li>
|
|
424
|
+
</ul>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Alternates.** Where the tags come from a fixed vocabulary, this is the any-of-many category and the
|
|
428
|
+
list is the better control. Where order carries meaning, give the reorder a non-drag path — a move
|
|
429
|
+
control per chip.
|
|
430
|
+
|
|
431
|
+
**States.** The fixed set, plus `empty` and `full`. Announce an added or removed tag in a
|
|
432
|
+
`role="status"` region, because the chip row is far from the field that changed it.
|
|
433
|
+
|
|
434
|
+
### A rating
|
|
435
|
+
|
|
436
|
+
**Default.** Bootstrap ships no rating. Draw the interactive form as a radio group at rung 2 — one
|
|
437
|
+
radio per value, restyled through `.btn-check` — so the keyboard model, the name, and the submitted
|
|
438
|
+
value come from the platform. Star chrome over that structure is an authored class contract at
|
|
439
|
+
rung 4.
|
|
440
|
+
|
|
441
|
+
```html
|
|
442
|
+
<fieldset>
|
|
443
|
+
<legend class="form-label">Rating</legend>
|
|
444
|
+
<div class="btn-group" role="radiogroup">
|
|
445
|
+
<input type="radio" class="btn-check" name="rating" id="rating-1" />
|
|
446
|
+
<label class="btn btn-outline-primary" for="rating-1">1</label>
|
|
447
|
+
</div>
|
|
448
|
+
</fieldset>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Alternates.** Draw a read-only rating as a glyph row with one accessible name stating the value,
|
|
452
|
+
per [components.md](components.md) → Status glyph marks; it is not a control. Treat a
|
|
453
|
+
`role="slider"` rating as a hand-roll owing the APG slider contract, including its keyboard model.
|
|
454
|
+
|
|
455
|
+
**States.** The fixed set. Keep a cleared rating reachable, and say what cleared means.
|
|
456
|
+
|
|
457
|
+
### A step in a sequence
|
|
458
|
+
|
|
459
|
+
**Default.** Draw a step indicator from shipped parts at rung 1 — a `nav` or `.list-group-numbered`
|
|
460
|
+
whose current item carries `aria-current="step"`, with a `.progress` bar over a long sequence. A step
|
|
461
|
+
indicator reports where the person is and holds no value, so it is not a field.
|
|
462
|
+
|
|
463
|
+
```html
|
|
464
|
+
<nav aria-label="Application progress">
|
|
465
|
+
<ol class="list-group list-group-numbered list-group-horizontal">
|
|
466
|
+
<li class="list-group-item" aria-current="step">Details</li>
|
|
467
|
+
<li class="list-group-item">Review</li>
|
|
468
|
+
</ol>
|
|
469
|
+
</nav>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Alternates.** Treat custom stepper chrome — connectors, dots, tick marks — as an authored class
|
|
473
|
+
contract over tokens. Take the rules for the sequence itself, including validation on leaving a step
|
|
474
|
+
and where the answers are held, from [bootstrap-reference.md](bootstrap-reference.md) → Wizards &
|
|
475
|
+
multi-step forms.
|
|
476
|
+
|
|
477
|
+
**States.** The fixed set, drawn on the controls that move between steps. The indicator holds no
|
|
478
|
+
value and is not a field, so it carries its own set instead: `rest` and a current mark that survives
|
|
479
|
+
every theme the surface ships. Never make position the only signal that a step failed.
|
|
480
|
+
|
|
481
|
+
## Where Bootstrap ships no component
|
|
482
|
+
|
|
483
|
+
Work the native-first ladder in [bootstrap-reference.md](bootstrap-reference.md) → When not to
|
|
484
|
+
hand-roll before building any category in this table, and take the hand-roll only with the named
|
|
485
|
+
pattern's contract in hand. Settle each row as a build-or-buy decision before it is a markup
|
|
486
|
+
decision. Take the contracts for dialog, combobox, radio group, and toolbar from
|
|
487
|
+
[bootstrap-reference.md](bootstrap-reference.md) → Pattern contracts.
|
|
488
|
+
|
|
489
|
+
| Category | Pattern the hand-roll owes |
|
|
490
|
+
| --------------------------- | -------------------------------------- |
|
|
491
|
+
| A date, as a calendar grid | APG dialog plus a grid keyboard model |
|
|
492
|
+
| A time, as segmented fields | APG spinbutton, per segment |
|
|
493
|
+
| A searched list | APG combobox |
|
|
494
|
+
| An ordered set of tags | APG combobox plus removable buttons |
|
|
495
|
+
| A rating, as one control | APG slider |
|
|
496
|
+
| A two-thumb range | APG slider, multi-thumb |
|
|
497
|
+
| A files dropzone | The visible input as the non-drag path |
|
|
498
|
+
| A data grid or a tree | APG grid, APG tree view |
|
|
499
|
+
|
|
500
|
+
Take skeletons from [components.md](components.md) → Placeholder (skeletons) instead; Bootstrap
|
|
501
|
+
ships them, so they stay out of this table.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Instruments
|
|
2
|
+
|
|
3
|
+
Reach for an instrument here where a capture cannot settle the claim. Run every one with its negative
|
|
4
|
+
control, in the same conditions, on the real compiled cascade the page loads. Treat an instrument
|
|
5
|
+
whose negative control passes as broken and refuse its reading as evidence;
|
|
6
|
+
`.claude/rules/quality.md` owns that law where it is present.
|
|
7
|
+
|
|
8
|
+
Take each entry's property, population, reading, negative control, and coverage as written, and hold
|
|
9
|
+
every entry to these rules.
|
|
10
|
+
|
|
11
|
+
- **Report the population.** A reading carries the population it walked. An empty population fails
|
|
12
|
+
the run, because an extractor that quietly matched nothing satisfies every other assertion.
|
|
13
|
+
- **Draw every negative control from outside the population.** Name the membership rule first, then
|
|
14
|
+
pick a negative control the rule excludes. Reject a negative control that rule admits.
|
|
15
|
+
- **Enter a negative control through the same door the surface enters.** A negative control handed
|
|
16
|
+
straight to the reading tests the reading alone, so pair it with one appended to the tree, the stylesheet,
|
|
17
|
+
or the registry the instrument walks wherever the instrument has an extraction step.
|
|
18
|
+
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [Authored class in the shipped cascade](#authored-class-in-the-shipped-cascade)
|
|
22
|
+
- [Declared class combinations](#declared-class-combinations)
|
|
23
|
+
- [Style escapes](#style-escapes)
|
|
24
|
+
- [Token discipline](#token-discipline)
|
|
25
|
+
- [Custom rule doing a utility's job](#custom-rule-doing-a-utilitys-job)
|
|
26
|
+
- [Composited contrast in both themes](#composited-contrast-in-both-themes)
|
|
27
|
+
- [One glyph, one meaning](#one-glyph-one-meaning)
|
|
28
|
+
- [When an authored rule is already earned](#when-an-authored-rule-is-already-earned)
|
|
29
|
+
|
|
30
|
+
## Authored class in the shipped cascade
|
|
31
|
+
|
|
32
|
+
- **Property.** Every class token the surface's own templates and components author has a rule in
|
|
33
|
+
the compiled CSS the page loads.
|
|
34
|
+
- **Population.** The class tokens the authored markup carries, read against every stylesheet the
|
|
35
|
+
page loads: the vendor build, each skin, and the project's own.
|
|
36
|
+
- **Reading.** Subtract the tokens the loaded stylesheets define from the tokens the markup carries.
|
|
37
|
+
A remainder fails the run and names each token with the file that authored it. Report the token
|
|
38
|
+
population walked, and fail a run that walked none.
|
|
39
|
+
- **Negative control.** A fed control and an appended control, each of which the reading must report.
|
|
40
|
+
Feed the first — a token no stylesheet defines — straight to the reading. Append the second through
|
|
41
|
+
the extraction door: an element built in the harness carrying that undefined token on an SVG
|
|
42
|
+
`class` attribute, added to the tree the reading walks. Each sits outside the population, which is
|
|
43
|
+
authored tokens the cascade resolves.
|
|
44
|
+
- **Coverage.** The fed negative control covers the subtraction. The appended negative control covers the extractor,
|
|
45
|
+
and it is what fails a reading that never leaves the root or that drops SVG tokens by reaching for
|
|
46
|
+
`className`, where the value is an `SVGAnimatedString` rather than a string. Together they prove
|
|
47
|
+
authored tokens are a subset of the cascade. The instrument says nothing about a cascade rule
|
|
48
|
+
nobody authored, a token a build step or a script adds after the read, or whether a resolved rule
|
|
49
|
+
paints what the author intended.
|
|
50
|
+
|
|
51
|
+
## Declared class combinations
|
|
52
|
+
|
|
53
|
+
- **Property.** Every multi-utility chrome string the surface reuses is declared once by name with
|
|
54
|
+
the invariant it holds, and the markup carries no undeclared combination.
|
|
55
|
+
- **Population.** The declared combinations, each with its name and its invariant, and every
|
|
56
|
+
multi-utility string the authored markup carries.
|
|
57
|
+
- **Reading.** Match each string in the markup against the declared set. An undeclared combination
|
|
58
|
+
fails and names the element that carries it.
|
|
59
|
+
- **Negative control.** A fed control and an appended control, each of which the reading must refuse.
|
|
60
|
+
Feed the first — a string one utility away from a declared combination — straight to the reading.
|
|
61
|
+
Append the second through the extraction door: an element built in the harness carrying that same
|
|
62
|
+
undeclared string on an SVG `class` attribute, added to the tree the reading walks. Each sits
|
|
63
|
+
outside the declared set.
|
|
64
|
+
- **Coverage.** The fed negative control covers the match against the declared set. The appended negative
|
|
65
|
+
control covers the extractor, and it is what fails a reading that never leaves the root or that drops SVG
|
|
66
|
+
tokens by reaching for `className`. Together they prove reused chrome is declared. The instrument
|
|
67
|
+
does not prove a declared invariant is true, and it does not read a single utility used alone.
|
|
68
|
+
- **Never substitute a cancellation heuristic.** A rule that flags a string for its utility count, or
|
|
69
|
+
for mixing categories, refuses the legitimate transparent read chrome that keeps a read view and an
|
|
70
|
+
edit view from reflowing.
|
|
71
|
+
|
|
72
|
+
## Style escapes
|
|
73
|
+
|
|
74
|
+
- **Property.** The surface's own markup carries no `style` attribute and no `<style>` element.
|
|
75
|
+
- **Population.** The elements of a freshly mounted, undriven tree — the surface as authored, before
|
|
76
|
+
any interaction drives it.
|
|
77
|
+
- **Reading.** Collect every element carrying an inline declaration or an embedded style element and
|
|
78
|
+
report it with its markup. Any hit fails.
|
|
79
|
+
- **Exemptions, declared by name.** Exempt the framework's own runtime styles and name each exemption
|
|
80
|
+
in the instrument: a Bootstrap Modal, Offcanvas, Collapse, or Dropdown writes inline styles as it
|
|
81
|
+
runs, and a conditional-visibility directive such as `v-show` emits `style="display: none"` at
|
|
82
|
+
mount. Run the reading on an undriven tree, because a reading taken after a journey drives the
|
|
83
|
+
surface reports on the framework rather than on the author.
|
|
84
|
+
- **Negative control.** An element carrying an inline declaration, built in the harness rather
|
|
85
|
+
than taken from the surface, fed to the reading. It sits outside the surface's own markup and the
|
|
86
|
+
reading must report it.
|
|
87
|
+
- **Coverage.** The instrument reads authored markup at mount. It does not see a style a component
|
|
88
|
+
writes after the person interacts, a rule authored in a stylesheet, or an escape inside a
|
|
89
|
+
third-party component's own markup.
|
|
90
|
+
|
|
91
|
+
## Token discipline
|
|
92
|
+
|
|
93
|
+
- **Property.** No authored rule carries a literal color, and every custom paint resolves through a
|
|
94
|
+
token in each color mode the product ships.
|
|
95
|
+
- **Population.** The project's own authored stylesheet rules, and the resolved value of each
|
|
96
|
+
custom-painted property in each color mode.
|
|
97
|
+
- **Reading.** A literal color in an authored declaration fails. For each custom paint, read the
|
|
98
|
+
resolved value once per mode; a mode that leaves it unresolved fails, and so does a pair of modes
|
|
99
|
+
that resolve it identically where the design says the modes differ.
|
|
100
|
+
- **Negative control.** A rule carrying a literal color, and a paint whose token the cascade does
|
|
101
|
+
not define, both fed to the reading rather than authored into the surface. Each sits outside the
|
|
102
|
+
population of authored rules that pass, and the reading must report both.
|
|
103
|
+
- **Coverage.** The instrument covers authored rules and the paints it was given. It does not judge
|
|
104
|
+
whether the chosen token is the right one, and it reads no vendor rule and no inline declaration —
|
|
105
|
+
[Style escapes](#style-escapes) covers those.
|
|
106
|
+
|
|
107
|
+
## Custom rule doing a utility's job
|
|
108
|
+
|
|
109
|
+
- **Property.** Every authored selector expresses something no shipped utility expresses, or records
|
|
110
|
+
the reason the utility does not fit.
|
|
111
|
+
- **Population.** The selectors in the project's own stylesheets.
|
|
112
|
+
- **Reading.** For each selector, name the utility that would carry the same declarations. A selector
|
|
113
|
+
a shipped utility already expresses fails unless it carries the recorded reason.
|
|
114
|
+
- **Negative control.** A rule restating a shipped utility exactly — a padding declaration matching
|
|
115
|
+
a spacing step — fed to the reading rather than authored into the stylesheet. It sits outside the
|
|
116
|
+
set of authored selectors that pass, and the reading must report it.
|
|
117
|
+
- **Coverage.** The instrument reads declarations, not intent. A rule that does a utility's job
|
|
118
|
+
alongside something else passes it, so a person still reads the authored stylesheet.
|
|
119
|
+
|
|
120
|
+
## Composited contrast in both themes
|
|
121
|
+
|
|
122
|
+
- **Property.** Every pairing the surface paints meets its bar in every theme: 4.5:1 for anything
|
|
123
|
+
information-bearing, 3:1 for textless marks and the chrome that carries state.
|
|
124
|
+
- **Population.** The pairings the surface renders, read per theme on the compiled cascade, with
|
|
125
|
+
every translucent layer composited. Exempt disabled controls, per [SKILL.md](../SKILL.md) →
|
|
126
|
+
Surfaces, color, contrast.
|
|
127
|
+
- **Reading.** Composite the painted layers, read the ratio, and fail anything under its bar with the
|
|
128
|
+
pairing named. Take the mechanics from [bootstrap-reference.md](bootstrap-reference.md) → Measuring
|
|
129
|
+
the bars.
|
|
130
|
+
- **Negative control.** An opaque pairing and a translucent stack, each of which the reading must
|
|
131
|
+
fail. Compose the opaque pairing in the harness at a ratio known to sit under the bar. Compose the
|
|
132
|
+
stack with a translucent layer over a floor, so its composited ratio sits under the bar while its
|
|
133
|
+
top layer read alone sits above it. Each is composed in the harness rather than taken from the
|
|
134
|
+
surface, so each sits outside the rendered population.
|
|
135
|
+
- **Coverage.** The opaque pairing covers the ratio arithmetic. The translucent stack covers the
|
|
136
|
+
compositing step, and it is what fails a reader that takes the top layer's declared color and skips
|
|
137
|
+
the layers under it. Together they measure what rendered, in the themes and viewports the run
|
|
138
|
+
entered. A pairing that appears only in a state the run never reached is unmeasured, so name the
|
|
139
|
+
states the run covered beside the result.
|
|
140
|
+
|
|
141
|
+
## One glyph, one meaning
|
|
142
|
+
|
|
143
|
+
- **Property.** Each status meaning takes one glyph, each glyph serves one meaning, and every
|
|
144
|
+
registered glyph resolves in the icon set the product actually ships.
|
|
145
|
+
- **Population.** The registry of meanings and glyphs the surface uses, and the shipped icon set.
|
|
146
|
+
- **Reading.** A meaning registered twice, a glyph registered against two meanings, or a glyph the
|
|
147
|
+
shipped set does not resolve fails, each named.
|
|
148
|
+
- **Negative control.** A registry entry binding a second meaning to a glyph already registered,
|
|
149
|
+
plus a glyph name the shipped set lacks. Both sit outside the registered set, and the reading must
|
|
150
|
+
report both.
|
|
151
|
+
- **Coverage.** The instrument proves the registry is consistent and resolvable. It does not prove
|
|
152
|
+
the markup draws the registered glyph for the meaning it carries, so pair it with a capture of the
|
|
153
|
+
states that use marks.
|
|
154
|
+
|
|
155
|
+
## When an authored rule is already earned
|
|
156
|
+
|
|
157
|
+
Leave rung 4 to the developer, per [SKILL.md](../SKILL.md) → When custom CSS is justified. Write an
|
|
158
|
+
authored rule without asking only when every one of these holds:
|
|
159
|
+
|
|
160
|
+
- an instrument here reports the vendor cascade failing a stated bar — the focus ring under 3:1, the
|
|
161
|
+
status text under 4.5:1, the shipped component with no class for the state the surface must
|
|
162
|
+
show;
|
|
163
|
+
- the rule cites that reading beside it, naming the instrument, the bar, and the value read;
|
|
164
|
+
- the rule restores the bar and does nothing else;
|
|
165
|
+
- the rule is written over `--bs-*` tokens, so both color modes move with the theme.
|
|
166
|
+
|
|
167
|
+
Treat anything wider as a proposal: name what the rule would buy, and stop.
|