toolroll 0.9.87 → 0.9.88

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.
@@ -87,26 +87,32 @@
87
87
  .k-tabs > [aria-selected="true"], .k-tabs > [aria-current] { color: var(--so-ink); border-bottom-color: var(--so-ink); }
88
88
  .k-tabs .k-count { margin-left: 2px; }
89
89
 
90
- /* ---------- Fields ---------- */
90
+ /* ---------- Fields: one family (KIT.md "Fields") ----------
91
+ Every text field, select and secondary text box is one filled surface (--so-field), no outline, radius 12px; the
92
+ label above names it, the hint and the error sit below it, focus draws the 2px ink ring. A transparent 1px border
93
+ keeps the box's edge in forced colours. Textareas grow with their words (field-sizing), no resize grip. */
91
94
  .k-field { display: grid; gap: 6px; margin: 0; min-width: 0; }
92
- .k-label { display: block; margin: 0; font: var(--so-typography-caption); color: var(--so-ink); }
95
+ .k-label { display: block; margin: 0; font: 500 12.5px/1.35 var(--so-font-sans); color: var(--so-muted); }
93
96
  .k-hint { margin: 0; font-size: 12px; color: var(--so-muted); line-height: 1.45; }
94
- .k-input.k-input, .k-textarea.k-textarea { box-sizing: border-box; width: 100%; margin: 0; border: 1px solid var(--so-field-line); border-radius: 8px; background: var(--so-paper); color: var(--so-ink); font: 400 13px/1.4 var(--so-font-sans); box-shadow: none; }
95
- .k-input.k-input { height: 32px; min-height: 0; padding: 0 10px; }
96
- .k-textarea.k-textarea { min-height: 72px; padding: 8px 10px; resize: vertical; max-width: 640px; }
97
- .k-input:focus-visible, .k-textarea:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 1px; box-shadow: none; }
97
+ .k-input.k-input, .k-textarea.k-textarea, .k-sel.k-sel { box-sizing: border-box; width: 100%; margin: 0; border: 1px solid transparent; border-radius: 12px; background: var(--so-field); color: var(--so-ink); font: 400 13.5px/1.4 var(--so-font-sans); box-shadow: none; transition: background-color var(--m-quick) var(--m-ease); }
98
+ .k-input.k-input { height: 36px; min-height: 0; padding: 0 12px; }
99
+ .k-textarea.k-textarea { min-height: 76px; max-height: 360px; padding: 10px 12px; resize: none; field-sizing: content; overflow-y: auto; max-width: 640px; line-height: 1.5; }
100
+ .k-input::placeholder, .k-textarea::placeholder { color: var(--so-muted); opacity: 1; }
101
+ .k-input:focus-visible, .k-textarea:focus-visible, .k-sel:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 1px; box-shadow: none; }
98
102
  .k-input[aria-invalid="true"], .k-textarea[aria-invalid="true"], .k-sel[aria-invalid="true"] { border-color: var(--so-danger); }
103
+ .k-input:disabled, .k-textarea:disabled, .k-sel:disabled { color: var(--so-muted); cursor: default; }
99
104
  .k-err { display: block; margin: 0; font-size: 12px; color: var(--so-fail-ink); line-height: 1.45; }
100
105
  .k-err:empty { display: none; }
101
- /* A native select with the chevron: 32px (sm 28px in a toolbar). */
106
+ /* A native select in the same family, with the chevron: 36px (sm 28px in a toolbar). */
102
107
  .k-selw { position: relative; display: inline-block; min-width: 0; max-width: 100%; }
103
- .k-selw::after { content: ""; position: absolute; right: 12px; top: 50%; margin-top: -5px; width: 6px; height: 6px; border: solid var(--so-muted); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); pointer-events: none; }
104
- .k-sel.k-sel { appearance: none; -webkit-appearance: none; box-sizing: border-box; width: 100%; height: 32px; min-height: 0; margin: 0; padding: 0 30px 0 10px; border: 1px solid var(--so-field-line); border-radius: 8px; background: var(--so-paper); color: var(--so-ink); font: 400 13px/1 var(--so-font-sans); text-overflow: ellipsis; cursor: pointer; box-shadow: none; }
105
- .k-sel:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 1px; }
106
- .k-sel:disabled { color: var(--so-muted); cursor: default; }
107
- .k-selw.sm .k-sel { height: 28px; font: 500 12.5px/1 var(--so-font-sans); padding: 0 26px 0 9px; }
108
- .k-selw.sm::after { right: 10px; }
108
+ .k-selw::after { content: ""; position: absolute; right: 13px; top: 50%; margin-top: -5px; width: 6px; height: 6px; border: solid var(--so-muted); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); pointer-events: none; }
109
+ .k-sel.k-sel { appearance: none; -webkit-appearance: none; height: 36px; min-height: 0; padding: 0 32px 0 12px; line-height: 1; text-overflow: ellipsis; cursor: pointer; }
110
+ .k-selw.sm .k-sel { height: 28px; border-radius: 999px; font: 500 12.5px/1 var(--so-font-sans); padding: 0 26px 0 11px; }
111
+ .k-selw.sm::after { right: 11px; }
109
112
  .k-selw.wide { display: block; width: 100%; }
113
+ /* A select drawn as a pill chip (a composer's project): filled, no box, works without script. */
114
+ .k-selw-chip::after { right: 12px; }
115
+ .k-selw-chip .k-sel.k-sel { height: 36px; border-radius: 999px; padding: 0 30px 0 14px; background: var(--so-composer-control); font: 500 13.5px/1 var(--so-font-sans); }
110
116
  /* Switch: a button[role=switch] that says its state, or a checkbox[role=switch] for a form. */
111
117
  .k-switch { position: relative; display: inline-flex; align-items: center; gap: 8px; min-height: 28px; margin: 0; padding: 0; border: 0; background: transparent; font: 500 12.5px/1 var(--so-font-sans); color: var(--so-muted); cursor: pointer; box-shadow: none; }
112
118
  .k-trk { position: relative; width: 32px; height: 18px; border-radius: 9px; border: 1px solid var(--so-field-line); background: var(--so-paper); transition: background-color var(--m-quick) var(--m-ease), border-color var(--m-quick) var(--m-ease); flex: none; box-sizing: border-box; }
@@ -314,25 +320,49 @@
314
320
  .k-swap-in { animation: k-swap var(--m-base) var(--m-ease) both; }
315
321
  @keyframes k-swap { from { opacity: 0; transform: translateY(4px); } }
316
322
 
317
- /* ---------- Composer: one soft rounded box; the textarea grows 1 → 8 lines; one bottom row inside ---------- */
318
- .k-composer { --c-r: 22px; box-sizing: border-box; border: 1px solid var(--so-field-line); border-radius: var(--c-r); background: var(--so-paper); display: grid; padding: 4px 6px 6px; outline: 3px solid transparent; outline-offset: 0; transition: border-color var(--m-quick) var(--m-ease), outline-color var(--m-quick) var(--m-ease); }
319
- .k-composer:focus-within { border-color: var(--so-ink); outline-color: color-mix(in srgb, var(--so-ink) 10%, transparent); }
320
- .k-composer.off { border-color: var(--so-line); background: var(--so-raised); }
321
- .k-composer-ta.k-composer-ta { box-sizing: border-box; width: 100%; min-height: 40px; margin: 0; padding: 10px 10px 6px; border: 0; border-radius: 0; outline: none; resize: none; background: transparent; box-shadow: none; color: var(--so-ink); font: var(--so-typography-body-reading); line-height: 1.5; overflow-y: hidden; scrollbar-width: thin; }
322
- .k-composer-ta::placeholder { color: var(--so-muted); }
323
+ /* ---------- Composer: one filled rounded object (KIT.md "Composer"); the textarea grows 1 → 8 lines; one bottom row
324
+ of filled circles and pills inside; its note and refusal sit under it, outside the box ---------- */
325
+ .k-composer-field { display: grid; gap: 6px; min-width: 0; }
326
+ .k-composer { --c-r: 28px; box-sizing: border-box; border: 1px solid transparent; border-radius: var(--c-r); background: var(--so-composer); display: grid; padding: 8px 10px 10px; transition: border-color var(--m-quick) var(--m-ease), background-color var(--m-quick) var(--m-ease); }
327
+ /* Focus: the fill lifts a step (a tap's cue is that and the caret); keyboard focus also draws the 2px ink ring, 3:1 or
328
+ better in both themes (html.k-kbd from the kit behaviour; without script the ring always shows). The textarea draws
329
+ no ring of its own. */
330
+ .k-composer { outline: 2px solid transparent; outline-offset: 2px; }
331
+ .k-composer:focus-within { background: color-mix(in srgb, var(--so-composer) 97%, var(--so-ink)); }
332
+ html:not(.k-js) .k-composer:focus-within, html.k-kbd .k-composer:focus-within { outline-color: var(--so-ring); }
333
+ .k-composer.off { background: var(--so-raised); color: var(--so-muted); }
334
+ .k-composer-ta.k-composer-ta { box-sizing: border-box; width: 100%; min-height: 44px; margin: 0; padding: 10px 10px 8px; border: 0; border-radius: 0; outline: none; resize: none; background: transparent; box-shadow: none; color: var(--so-ink); font: 400 17px/1.5 var(--so-font-sans); letter-spacing: -0.005em; overflow-y: hidden; scrollbar-width: thin; }
335
+ .k-composer-ta::placeholder { color: var(--so-muted); opacity: 1; }
323
336
  .k-composer-ta:focus, .k-composer-ta:focus-visible { outline: none; box-shadow: none; }
324
- .k-composer:has(.k-composer-ta[aria-invalid="true"]) { border-color: var(--so-danger); outline-color: transparent; }
337
+ .k-composer:has(.k-composer-ta[aria-invalid="true"]) { border-color: var(--so-danger); }
325
338
  .k-composer + .k-err { margin-top: 6px; }
326
- .k-composer-bar { display: flex; align-items: center; gap: 6px; padding: 2px 0 0 2px; flex-wrap: wrap; min-height: 36px; }
327
- .k-composer-plus { box-sizing: border-box; width: 32px; height: 32px; min-height: 0; margin: 0; padding: 0; border-radius: 50%; display: grid; place-items: center; color: var(--so-muted); border: 1px solid var(--so-line); background: transparent; cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease); }
328
- .k-composer-chip { box-sizing: border-box; height: 32px; min-height: 0; margin: 0; display: inline-flex; align-items: center; gap: 4px; padding: 0 8px 0 10px; border: 0; border-radius: 999px; font: 500 12.5px/1 var(--so-font-sans); color: var(--so-muted); background: transparent; cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease); }
339
+ .k-composer-bar { display: flex; align-items: center; gap: 8px; padding: 4px 0 0; flex-wrap: wrap; min-height: 40px; }
340
+ .k-composer-plus { box-sizing: border-box; width: 36px; height: 36px; min-height: 0; margin: 0; padding: 0; border-radius: 50%; display: grid; place-items: center; flex: none; color: var(--so-ink); border: 0; background: var(--so-composer-control); cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease); }
341
+ .k-composer-plus .k-i { width: 18px; height: 18px; }
342
+ /* A pill chip: the primary word, then a dimmer secondary word (a project, then the agent). */
343
+ .k-composer-chip { box-sizing: border-box; height: 36px; min-height: 0; max-width: 100%; margin: 0; display: inline-flex; align-items: center; gap: 6px; padding: 0 14px; border: 0; border-radius: 999px; font: 500 13.5px/1 var(--so-font-sans); color: var(--so-ink); background: var(--so-composer-control); cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease); }
344
+ .k-composer-chip > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
345
+ .k-composer-chip .k-chip-sub { color: var(--so-muted); font-weight: 400; }
329
346
  .k-composer-plus:disabled, .k-composer-chip:disabled { opacity: .5; pointer-events: none; }
330
- /* The round send: ink with text, muted until then; Stop takes its place while the work runs. */
331
- .k-send-slot { position: relative; display: inline-grid; width: 34px; height: 34px; flex: none; }
332
- .k-send-slot .k-send { grid-area: 1 / 1; position: relative; box-sizing: border-box; width: 34px; height: 34px; min-height: 0; margin: 0; padding: 0; border-radius: 50%; display: grid; place-items: center; background: var(--so-ink); color: var(--so-paper); border: 0; cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease), transform var(--m-base) var(--m-ease); }
333
- .k-send::before { content: ""; position: absolute; inset: -5px; }
334
- .k-send .k-i { width: 17px; height: 17px; stroke-width: 2.2; }
335
- .k-send[aria-disabled="true"], .k-composer:not(.has-text) .k-send:not(.k-stop) { background: var(--so-raised); color: var(--so-muted); }
347
+ /* A small act inside the bar (Pause and send) is a filled pill like the chips, never an outlined box in the box. */
348
+ .k-composer-bar .k-btn:not(.k-send) { border-color: transparent; border-radius: 999px; background: var(--so-composer-control); }
349
+ .k-composer-plus:focus-visible, .k-composer-chip:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 2px; }
350
+ /* A toggle chip (an on/off choice inside the box, as Read code before planning): a filled pill whose mark fills when
351
+ on; the checkbox inside is the control (role=switch), so it works without script. */
352
+ .k-tchip { position: relative; box-sizing: border-box; height: 36px; margin: 0; display: inline-flex; align-items: center; gap: 8px; padding: 0 14px 0 12px; border-radius: 999px; background: var(--so-composer-control); color: var(--so-muted); font: 500 13.5px/1 var(--so-font-sans); cursor: pointer; transition: color var(--m-quick) var(--m-ease); }
353
+ .k-tchip > input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
354
+ .k-tchip-mark { box-sizing: border-box; width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid currentColor; flex: none; display: grid; place-items: center; transition: background-color var(--m-quick) var(--m-ease), border-color var(--m-quick) var(--m-ease); }
355
+ .k-tchip:has(input:checked) { color: var(--so-ink); }
356
+ .k-tchip:has(input:checked) .k-tchip-mark { background: var(--so-ink); border-color: var(--so-ink); }
357
+ .k-tchip:has(input:checked) .k-tchip-mark::after { content: ""; width: 3px; height: 6px; margin-top: -2px; border: solid var(--so-paper); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); }
358
+ .k-tchip:has(input:focus-visible) { outline: 2px solid var(--so-ring); outline-offset: 2px; }
359
+ /* The round send: solid ink with a paper arrow once there are words, the muted control fill until then; Stop takes
360
+ its place while the work runs. */
361
+ .k-send-slot { position: relative; display: inline-grid; width: 36px; height: 36px; flex: none; }
362
+ .k-send-slot .k-send { grid-area: 1 / 1; position: relative; box-sizing: border-box; width: 36px; height: 36px; min-height: 0; margin: 0; padding: 0; border-radius: 50%; display: grid; place-items: center; background: var(--so-ink); color: var(--so-paper); border: 0; cursor: pointer; box-shadow: none; transition: background-color var(--m-quick) var(--m-ease), color var(--m-quick) var(--m-ease), transform var(--m-base) var(--m-ease); }
363
+ .k-send::before { content: ""; position: absolute; inset: -4px; }
364
+ .k-send .k-i { width: 18px; height: 18px; stroke-width: 2.2; }
365
+ .k-send[aria-disabled="true"], .k-composer:not(.has-text) .k-send:not(.k-stop) { background: var(--so-composer-control); color: var(--so-muted); }
336
366
  .k-send[aria-disabled="true"] { cursor: default; }
337
367
  .k-send:not([aria-disabled="true"]):active { transform: scale(.94); }
338
368
  .k-stop { visibility: hidden; }
@@ -344,17 +374,18 @@
344
374
  .k-send:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 2px; }
345
375
  .k-composer-files { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 8px; }
346
376
  .k-composer-files:empty { display: none; }
347
- .k-composer-file { display: inline-flex; align-items: center; gap: 6px; height: 28px; margin-top: 6px; padding: 0 4px 0 10px; border: 1px solid var(--so-line); border-radius: 999px; font-size: 12px; }
377
+ .k-composer-file { display: inline-flex; align-items: center; gap: 6px; height: 28px; margin-top: 6px; padding: 0 4px 0 12px; border: 0; border-radius: 999px; background: var(--so-composer-control); font-size: 12px; }
348
378
  .k-composer-file button { width: 22px; height: 22px; min-height: 0; margin: 0; padding: 0; display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--so-muted); }
349
- .k-composer-note { padding: 2px 10px 6px; }
350
- .k-composer-err { padding: 2px 10px 6px; }
379
+ /* Under the box, in small dim words: its note, then its refusal. */
380
+ .k-composer-note.k-composer-note { margin: 0; padding: 0 16px; font-size: 12px; color: var(--so-muted); line-height: 1.45; }
381
+ .k-composer-err { padding: 0 16px; }
351
382
  .k-composer-err:empty { display: none; }
352
383
  /* One line: the text and the send share a row. */
353
- .k-composer.line { grid-template-columns: minmax(0, 1fr) auto; align-items: end; padding: 4px 6px 4px 8px; }
354
- .k-composer.line .k-composer-ta { padding: 8px 6px; min-height: 36px; }
355
- .k-composer.line .k-composer-bar { padding: 0 0 1px; min-height: 0; }
384
+ .k-composer.line { --c-r: 26px; grid-template-columns: minmax(0, 1fr) auto; align-items: end; padding: 5px 6px 5px 10px; }
385
+ .k-composer.line .k-composer-ta { padding: 9px 6px; min-height: 42px; font-size: 16px; }
386
+ .k-composer.line .k-composer-bar { padding: 0 0 3px; min-height: 0; }
356
387
  .k-composer.line .k-composer-bar .k-grow { display: none; }
357
- .k-composer.line .k-composer-files, .k-composer.line .k-composer-err, .k-composer.line .k-composer-note { grid-column: 1 / -1; }
388
+ .k-composer.line .k-composer-files { grid-column: 1 / -1; }
358
389
  /* A composer docked to the bottom rides above the phone keyboard (kit behaviour sets html.kb-open and --kb). */
359
390
  .k-composer-dock { position: sticky; bottom: 0; z-index: 4; padding: 12px 24px 14px; background: var(--so-paper); border-top: 1px solid var(--so-line); }
360
391
 
@@ -425,8 +456,10 @@
425
456
  .k-card:hover { background: color-mix(in srgb, var(--so-raised) 60%, transparent); }
426
457
  .k-backlink:hover { color: var(--so-ink); }
427
458
  .k-menu > button:hover, .k-menu > a:hover { background: var(--so-soft); }
428
- .k-composer-plus:hover { background: var(--so-raised); color: var(--so-ink); }
429
- .k-composer-chip:hover { background: var(--so-raised); color: var(--so-ink); }
459
+ .k-composer-plus:hover, .k-composer-chip:hover, .k-tchip:hover { background: color-mix(in srgb, var(--so-composer-control) 88%, var(--so-ink)); }
460
+ .k-dock-needs:hover { background: color-mix(in srgb, var(--so-composer-control) 88%, var(--so-ink)); }
461
+ .k-input:hover, .k-textarea:hover, .k-sel:hover { background: color-mix(in srgb, var(--so-field) 94%, var(--so-ink)); }
462
+ .k-selw-chip .k-sel:hover { background: color-mix(in srgb, var(--so-composer-control) 88%, var(--so-ink)); }
430
463
  .k-nav:hover { background: var(--so-nav-hover); color: var(--so-ink); }
431
464
  .k-crumbs a:hover { color: var(--so-ink); }
432
465
  }
@@ -437,7 +470,7 @@
437
470
  /* ---------- Phone dock: the Needs-you button and the screen's one primary ---------- */
438
471
  .k-dock { display: none; }
439
472
  /* The Needs-you button (the app shell's dock): a dot, the words, the ink count, and which projects it covers. */
440
- .k-dock-needs { display: grid; align-content: center; gap: 1px; box-sizing: border-box; width: 136px; min-height: 48px; margin: 0; padding: 0 12px; border-radius: 8px; border: 1px solid var(--so-field-line); background: var(--so-paper); color: var(--so-ink); text-align: left; white-space: nowrap; cursor: pointer; box-shadow: none; }
473
+ .k-dock-needs { display: grid; align-content: center; gap: 1px; box-sizing: border-box; width: 136px; min-height: 48px; margin: 0; padding: 0 14px; border-radius: 14px; border: 1px solid transparent; background: var(--so-composer-control); color: var(--so-ink); text-align: left; white-space: nowrap; cursor: pointer; box-shadow: none; }
441
474
  .k-dock-needs-l { display: inline-flex; align-items: center; gap: 7px; font: 500 14px/1.2 var(--so-font-sans); }
442
475
  .k-dock-needs-s { overflow: hidden; text-overflow: ellipsis; font-size: 11.5px; color: var(--so-muted); padding-left: 14px; }
443
476
  .k-dock-needs:focus-visible { outline: 2px solid var(--so-ring); outline-offset: 2px; }
@@ -509,16 +542,17 @@
509
542
  .k-frow .k-err { text-align: left; }
510
543
  .k-frow-c .k-seg { width: 100%; }
511
544
  .k-frow-c .k-seg > button { flex: 1; justify-content: center; }
512
- .k-composer-ta.k-composer-ta { font-size: 16px; }
545
+ .k-composer-ta.k-composer-ta { font-size: 17px; }
513
546
  .k-composer-bar .k-btn-sm { height: 44px; }
514
- /* Every composer control is a real 44px target; the circles stay drawn at 36px inside it. */
547
+ /* Every composer control is a real, filled 44px target. */
548
+ .k-composer { --c-r: 26px; padding: 6px 8px 8px; }
549
+ .k-composer-ta.k-composer-ta { padding: 10px 10px 6px; }
515
550
  .k-send-slot, .k-send-slot .k-send { width: 44px; height: 44px; }
516
- .k-send-slot .k-send { background: radial-gradient(circle, var(--so-ink) 17.5px, transparent 18px); }
517
- .k-send[aria-disabled="true"], .k-composer:not(.has-text) .k-send:not(.k-stop) { background: radial-gradient(circle, var(--so-raised) 17.5px, transparent 18px); }
518
551
  .k-send::before { content: none; }
519
- .k-composer-plus { width: 44px; height: 44px; border: 0; background: radial-gradient(circle, transparent 15.5px, var(--so-line) 16px, var(--so-line) 16.5px, transparent 17px); }
520
- .k-composer-chip { height: 44px; }
521
- .k-composer-bar { gap: 2px; }
552
+ .k-composer-plus { width: 44px; height: 44px; }
553
+ .k-composer-chip, .k-tchip, .k-selw-chip .k-sel.k-sel { height: 44px; }
554
+ .k-selw-chip .k-sel.k-sel { font-size: 16px; }
555
+ .k-composer-bar { gap: 6px; }
522
556
  .k-composer-file button { width: 32px; height: 32px; }
523
557
  .k-composer-dock { padding: 12px 16px 16px; }
524
558
  html.kb-open .k-composer-dock { position: fixed; left: 0; right: 0; bottom: var(--kb, 0px); z-index: 13; }
@@ -535,6 +569,9 @@
535
569
  .k-dock { display: flex; gap: 8px; align-items: stretch; position: fixed; left: 0; right: 0; bottom: 0; z-index: 12; padding: 10px 12px calc(10px + env(safe-area-inset-bottom)); background: var(--so-paper); border-top: 1px solid var(--so-line); }
536
570
  .k-dock-primary { flex: 1; display: flex; }
537
571
  .k-dock-primary .k-btn { width: 100%; }
572
+ /* The dock is filled controls only: a secondary act there is a filled control, never an outlined box. */
573
+ .k-dock .k-btn-outline { border-color: transparent; background: var(--so-composer-control); }
574
+ .k-dock .k-btn { min-height: 48px; border-radius: 14px; }
538
575
  }
539
576
  @media (min-width: 761px) { .k-phone-only { display: none !important; } }
540
577
 
@@ -563,9 +600,9 @@ main:has(> .k-out) { max-width: none; margin: 0; padding: 0; }
563
600
  .k-refusal .k-empty-t::first-letter, .k-refusal .k-notice > span::first-letter { text-transform: uppercase; }
564
601
 
565
602
  /* A composer's note can follow its switch: k-on-note while it is on, k-off-note while it is off (no script). */
566
- .k-composer .k-off-note { display: none; }
567
- .k-composer:has(.k-switch-input):not(:has(.k-switch-input:checked)) .k-on-note { display: none; }
568
- .k-composer:has(.k-switch-input):not(:has(.k-switch-input:checked)) .k-off-note { display: inline; }
603
+ .k-composer-field .k-off-note { display: none; }
604
+ .k-composer-field:has(.k-switch-input):not(:has(.k-switch-input:checked)) .k-on-note { display: none; }
605
+ .k-composer-field:has(.k-switch-input):not(:has(.k-switch-input:checked)) .k-off-note { display: inline; }
569
606
 
570
607
  /* The composer's focus is the box's (KIT.md): the workspace's own :focus-visible ring never lands on its textarea. */
571
608
  .k-composer .k-composer-ta.k-composer-ta:focus-visible { outline: none; box-shadow: none; }
@@ -614,7 +651,7 @@ main:has(> .k-out) { max-width: none; margin: 0; padding: 0; }
614
651
 
615
652
  /* ---------- Reduced motion changes presentation only ---------- */
616
653
  @media (prefers-reduced-motion: reduce) {
617
- .k-btn, .k-send, .k-composer, .k-composer-plus, .k-composer-chip, .k-trk, .k-trk::after, .k-tabs > *, .k-slots, .k-crow-bar { transition: none; }
654
+ .k-btn, .k-send, .k-composer, .k-composer-plus, .k-composer-chip, .k-tchip, .k-tchip-mark, .k-input, .k-textarea, .k-sel, .k-trk, .k-trk::after, .k-tabs > *, .k-slots, .k-crow-bar { transition: none; }
618
655
  .k-steps-fill { transition: none; }
619
656
  .k-grow-in, .k-msg-in, .k-swap-in, .k-scrim, .k-btn.swap .k-face, .k-check-draw path, .k-composer .k-i { animation: none !important; }
620
657
  .k-check-draw path { stroke-dashoffset: 0; }
@@ -8,10 +8,16 @@
8
8
  --so-attention: var(--so-signal);
9
9
  --so-attention-soft: var(--so-signal-soft);
10
10
  --so-code-bg: #fafafa;
11
+ /* Composer: the filled surface of the message box, one step off the page. */
12
+ --so-composer: #f2f2f2;
13
+ /* Composer Control: the filled circle and pill chips inside the composer (+, project, toggle chips) and the empty send. */
14
+ --so-composer-control: #dcdcdc;
11
15
  /* Vermilion: failure and irreversible acts. */
12
16
  --so-danger: #c4320a;
13
17
  --so-danger-soft: #feebe7;
14
18
  --so-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
19
+ /* Field: the filled surface of a text field, select or secondary text box (the field family: no outline; the label names it, focus draws the ink ring). */
20
+ --so-field: #ececec;
15
21
  /* Field Line: the boundary that identifies a control (fields, outline buttons, switches, radio cards), 3:1 or better on paper. */
16
22
  --so-field-line: #8f8f8f;
17
23
  --so-font-mono: "Geist Mono", ui-monospace, "SF Mono", "SFMono-Regular", "Menlo", "Consolas", monospace;
@@ -162,8 +168,11 @@
162
168
  --so-accent-hover: #ffffff;
163
169
  --so-accent-text: #ededed;
164
170
  --so-code-bg: #111111;
171
+ --so-composer: #242424;
172
+ --so-composer-control: #333333;
165
173
  --so-danger: #ff977d;
166
174
  --so-danger-soft: rgb(255 151 125 / .12);
175
+ --so-field: #232323;
167
176
  --so-field-line: #6b6b6b;
168
177
  --so-ground: #0b0b0b;
169
178
  --so-info: #70b8ff;
@@ -210,8 +219,11 @@
210
219
  --so-accent-hover: #ffffff;
211
220
  --so-accent-text: #ededed;
212
221
  --so-code-bg: #111111;
222
+ --so-composer: #242424;
223
+ --so-composer-control: #333333;
213
224
  --so-danger: #ff977d;
214
225
  --so-danger-soft: rgb(255 151 125 / .12);
226
+ --so-field: #232323;
215
227
  --so-field-line: #6b6b6b;
216
228
  --so-ground: #0b0b0b;
217
229
  --so-info: #70b8ff;
package/dist/release.d.ts CHANGED
@@ -51,8 +51,10 @@ export declare const PROTECTION_MINIMUM_CHECKS: readonly string[];
51
51
  /**
52
52
  * Pull-request checks a release never waits for or fails on, by exact name and the workflow file that defines them:
53
53
  * youiyoux's UI drift check (src/design/KIT.md), which fails a pull request that adds design drift but says nothing
54
- * about whether a build is safe to release. Only a GitHub Actions run of that name from that workflow is left out; the
55
- * same name from anywhere else is judged like any other check. Never a required check.
54
+ * about whether a build is safe to release, and the scripted journeys on a pull request (journeys.yml,
55
+ * docs/plans/ci-speed.md item 4), advisory while they settle in CI: the release check runs every journey itself. Only a
56
+ * GitHub Actions run of that name from that workflow is left out; the same name from anywhere else is judged like any
57
+ * other check. Never a required check.
56
58
  */
57
59
  export declare const ADVISORY_PR_CHECKS: Readonly<Record<string, string>>;
58
60
  /**
package/dist/release.js CHANGED
@@ -84,10 +84,16 @@ export const PROTECTION_MINIMUM_CHECKS = REQUIRED_PR_CHECKS.filter(name => !PROT
84
84
  /**
85
85
  * Pull-request checks a release never waits for or fails on, by exact name and the workflow file that defines them:
86
86
  * youiyoux's UI drift check (src/design/KIT.md), which fails a pull request that adds design drift but says nothing
87
- * about whether a build is safe to release. Only a GitHub Actions run of that name from that workflow is left out; the
88
- * same name from anywhere else is judged like any other check. Never a required check.
87
+ * about whether a build is safe to release, and the scripted journeys on a pull request (journeys.yml,
88
+ * docs/plans/ci-speed.md item 4), advisory while they settle in CI: the release check runs every journey itself. Only a
89
+ * GitHub Actions run of that name from that workflow is left out; the same name from anywhere else is judged like any
90
+ * other check. Never a required check.
89
91
  */
90
- export const ADVISORY_PR_CHECKS = { "youiyoux · UI drift": ".github/workflows/youiyoux.yml" };
92
+ export const ADVISORY_PR_CHECKS = {
93
+ "youiyoux · UI drift": ".github/workflows/youiyoux.yml",
94
+ "journeys · app (scripted)": ".github/workflows/journeys.yml",
95
+ "journeys · flows (scripted)": ".github/workflows/journeys.yml",
96
+ };
91
97
  /** The checks a release's publication waits for on its tag (.github/workflows/publish.yml, docs/plans/test-matrix.md):
92
98
  * the release proof (the tagged tree passed every REQUIRED_PR_CHECKS check on its pull request, read from GitHub) and
93
99
  * the publish job that needs it. The tag runs no tests again. Nothing else reported on the merge commit (main-push.yml)
@@ -265,16 +265,26 @@
265
265
  .project-card .project-name:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: .375rem; }
266
266
 
267
267
  label { display: block; font-size: 0.8125rem; font-weight: 500; margin: .75rem 0 0; color: var(--foreground); }
268
- :where(input[type=text], input[type=password], input[type=number], input[type=url], input[type=email], input[type=search], input[type=time], textarea, select) {
269
- width: 100%; margin: .35rem 0 0; padding: .45rem .7rem;
270
- color: var(--foreground); background: var(--card); min-height: 2.125rem;
271
- border: 1px solid var(--input); border-radius: calc(var(--radius) - 3px);
272
- transition: border-color .15s, box-shadow .15s;
273
- }
268
+ /* The kit's field family (src/design/KIT.md "Fields"): one filled surface, no outline, 12px radius; the label above
269
+ names the field and focus draws the ink ring. A transparent border keeps the edge in forced colours. Textareas
270
+ grow with their words (no resize grip); selects draw the kit's chevron. */
271
+ :where(input[type=text], input[type=password], input[type=number], input[type=url], input[type=email], input[type=search], input[type=time], input:not([type]), textarea, select) {
272
+ width: 100%; margin: .35rem 0 0; padding: .5rem .75rem;
273
+ color: var(--foreground); background-color: var(--so-field); min-height: 2.25rem;
274
+ border: 1px solid transparent; border-radius: var(--so-radius-sheet); box-shadow: none;
275
+ transition: background-color .12s;
276
+ }
277
+ :where(textarea) { resize: none; field-sizing: content; min-height: 4.75rem; max-height: 22rem; overflow-y: auto; line-height: 1.5; }
278
+ :where(select:not([multiple])) {
279
+ appearance: none; -webkit-appearance: none; padding-right: 2.1rem; cursor: pointer;
280
+ background-image: linear-gradient(45deg, transparent 50%, var(--so-muted) 50%), linear-gradient(135deg, var(--so-muted) 50%, transparent 50%);
281
+ background-position: calc(100% - 17px) 52%, calc(100% - 12px) 52%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
282
+ }
283
+ :where(input, textarea, select)::placeholder { color: var(--muted-foreground); opacity: 1; }
274
284
  /* The box above is in :where() so a surface's own rules (the workspace's 44px phone targets) win; the type keeps
275
285
  its old weight, so the phone's 16px rule still loses to it as before. */
276
286
  input[type=text], input[type=password], input[type=number], input[type=url], input[type=email], input[type=search], input[type=time], textarea, select { font: 400 0.875rem/1.4 var(--font-sans); }
277
- @media (hover: hover) and (pointer: fine) { input:hover, textarea:hover, select:hover { border-color: var(--muted-foreground); } }
287
+ @media (hover: hover) and (pointer: fine) { :where(input[type=text], input[type=password], input[type=number], input[type=url], input[type=email], input[type=search], input[type=time], input:not([type]), textarea, select):hover { background-color: color-mix(in srgb, var(--so-field) 94%, var(--so-ink)); } }
278
288
  input[type=number] { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
279
289
  input[type=radio], input[type=checkbox] { accent-color: var(--ring); }
280
290
  .permission-field { border: 0; padding: 0; margin: 1rem 0 0; min-width: 0; }
@@ -312,7 +322,8 @@
312
322
  min-height: 8.5rem; max-height: 18rem; margin: 0; padding: .9rem 1rem; resize: vertical;
313
323
  border: 0; background: transparent; box-shadow: none; font-size: 1.05rem; line-height: 1.55;
314
324
  }
315
- .task-prompt textarea:focus-visible { border: 0; box-shadow: none; }
325
+ .task-prompt textarea:focus-visible { border: 0; box-shadow: none; outline: none; }
326
+ .task-composer:focus-within { outline: 2px solid var(--so-ring); outline-offset: 2px; }
316
327
  @media (hover: hover) and (pointer: fine) { .task-prompt textarea:hover { border: 0; box-shadow: none; } }
317
328
  .task-repo { margin: .25rem .45rem .7rem; }
318
329
  .task-composer-footer { display: flex; align-items: center; gap: .5rem; padding: .25rem; }
@@ -361,7 +372,7 @@
361
372
  }
362
373
  input[type=password] { font-family: var(--font-mono); }
363
374
  input:focus-visible, textarea:focus-visible, select:focus-visible {
364
- outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);
375
+ outline: 2px solid var(--so-ring); outline-offset: 1px; box-shadow: none;
365
376
  }
366
377
  button:focus-visible, a:focus-visible, summary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
367
378
 
@@ -2198,7 +2209,8 @@
2198
2209
  width: 100%; min-height: 3.5rem; max-height: 13rem; margin: 0; padding: .75rem .85rem;
2199
2210
  resize: vertical; border: 0; background: transparent; box-shadow: none; font-size: 1rem;
2200
2211
  }
2201
- .composer textarea:focus-visible { border: 0; box-shadow: none; }
2212
+ .composer textarea:focus-visible { border: 0; box-shadow: none; outline: none; }
2213
+ .composer:focus-within { outline: 2px solid var(--so-ring); outline-offset: 2px; }
2202
2214
  @media (hover: hover) and (pointer: fine) { .composer textarea:hover { border: 0; box-shadow: none; } }
2203
2215
  .composer button {
2204
2216
  flex: 0 0 2.75rem; width: 2.75rem; min-height: 2.75rem; padding: 0; border-radius: 999px;
@@ -17,6 +17,6 @@
17
17
  * HTML without it. It never rides a sensitive page (one that asks for a password or shows a secret), where the page
18
18
  * carries no chrome additions.
19
19
  */
20
- export declare const KIT_STANDALONE_SCRIPT = "(function(){\nif(window.__kitStandalone)return;window.__kitStandalone=1;var d=document;\nvar PHONE=\"(max-width: 760px)\";var FOCUSABLE='a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\nfunction phone(){return window.matchMedia(PHONE).matches;}\nfunction sync(t){var box=t.closest(\".k-composer\");if(!box)return;var st=getComputedStyle(t);var line=parseFloat(st.lineHeight)||21;\nvar pad=(parseFloat(st.paddingTop)||0)+(parseFloat(st.paddingBottom)||0);var max=line*(Number(t.getAttribute(\"data-max\"))||8)+pad;var min=line*(Number(t.getAttribute(\"data-min\"))||1)+pad;\nt.style.height=\"auto\";t.style.height=Math.max(min,Math.min(t.scrollHeight,max))+\"px\";t.style.overflowY=t.scrollHeight>max+1?\"auto\":\"hidden\";\nvar on=t.value.trim()!==\"\";box.classList.toggle(\"has-text\",on);var sends=box.querySelectorAll(\".k-send[data-gate]:not(:disabled)\");for(var i=0;i<sends.length;i++)sends[i].setAttribute(\"aria-disabled\",String(!on));}\nfunction syncAll(){var all=d.querySelectorAll(\".k-composer-ta\");for(var i=0;i<all.length;i++)sync(all[i]);}\nvar stack=[];\nfunction visible(el){return !!el&&el.isConnected&&el.getClientRects().length>0;}\nfunction openPanel(panel,trigger,onClose){if(stack.length)closeTop(\"close\");var floating=panel.classList.contains(\"k-pop\");var ph=phone();\nif(!panel.isConnected)d.body.appendChild(panel);var scrim=d.createElement(\"div\");scrim.className=\"k-scrim\"+(!floating||ph?\" dim\":\"\");panel.parentNode.insertBefore(scrim,panel);\npanel.hidden=false;panel.setAttribute(\"aria-modal\",\"true\");if(!panel.hasAttribute(\"tabindex\"))panel.tabIndex=-1;panel.classList.toggle(\"as-sheet\",floating&&ph);\nif(floating&&!ph&&trigger){var at=trigger.getBoundingClientRect();var w=panel.offsetWidth||336,h=panel.offsetHeight||200;var left=Math.min(Math.max(8,at.right-w),window.innerWidth-w-8);var below=at.bottom+6;var top=below+h>window.innerHeight-8?Math.max(8,at.top-h-6):below;panel.style.left=Math.round(left)+\"px\";panel.style.top=Math.round(top)+\"px\";}\nvar inerted=[];for(var node=panel;node!==d.body&&node.parentElement;node=node.parentElement){var kids=node.parentElement.children;for(var i=0;i<kids.length;i++){var s=kids[i];if(s===node||s===scrim||s.inert)continue;s.inert=true;inerted.push(s);}}\nvar entry={panel:panel,scrim:scrim,trigger:trigger,inerted:inerted,onClose:onClose};stack.push(entry);scrim.addEventListener(\"click\",function(){closeEntry(entry);});\nvar first=panel.querySelector(\"[autofocus]\")||panel.querySelector('input:not([type=\"hidden\"]), textarea, select')||panel;first.focus();}\nfunction closeEntry(entry){var at=stack.indexOf(entry);if(at<0)return;stack.splice(at,1);for(var i=0;i<entry.inerted.length;i++)entry.inerted[i].inert=false;\nentry.scrim.remove();entry.panel.hidden=true;entry.panel.classList.remove(\"as-sheet\");if(visible(entry.trigger))entry.trigger.focus();if(entry.onClose)entry.onClose();}\nfunction closeTop(){var top=stack[stack.length-1];if(top)closeEntry(top);}\nfunction menu(trigger,title,items){var ph=phone();var panel=d.createElement(\"div\");panel.className=ph?\"k-sheet\":\"k-pop k-pop-menu\";panel.setAttribute(\"role\",\"dialog\");\nvar id=\"k-menu-\"+Math.random().toString(36).slice(2,8);panel.setAttribute(\"aria-labelledby\",id);var head=d.createElement(\"div\");head.className=ph?\"k-sheet-h\":\"k-pop-h\";\nvar h=d.createElement(ph?\"h2\":\"h3\");h.id=id;h.textContent=title;var x=d.createElement(\"button\");x.type=\"button\";x.className=\"k-btn k-btn-ghost k-btn-sm k-btn-icon\";x.setAttribute(\"aria-label\",\"Close\");x.setAttribute(\"data-k-close\",\"\");x.textContent=\"\\u00d7\";\nhead.appendChild(h);head.appendChild(x);var list=d.createElement(\"div\");list.className=\"k-menu\";var entry;\nitems.forEach(function(item){var b=d.createElement(\"button\");b.type=\"button\";b.textContent=item.current?item.label+\" \\u00b7 current\":item.label;if(item.current)b.setAttribute(\"aria-current\",\"true\");b.addEventListener(\"click\",function(){closeTop();item.act();});list.appendChild(b);});\npanel.appendChild(head);panel.appendChild(list);openPanel(panel,trigger,function(){panel.remove();});}\nfunction composerMenu(target){var chip=target.closest(\"[data-k-composer-project]\");var plus=target.closest(\"[data-k-composer-plus]\");var id=chip?chip.getAttribute(\"data-k-composer-project\"):plus?plus.getAttribute(\"data-k-composer-plus\"):null;if(id===null)return false;\nvar hidden=d.getElementById(id+\"-project\");function choose(trigger){if(!hidden)return;var items=[];try{var parsed=JSON.parse(hidden.getAttribute(\"data-items\")||\"[]\");if(Array.isArray(parsed))items=parsed.filter(function(one){return typeof one===\"string\";});}catch(e){items=[];}\nmenu(trigger,\"Project\",items.map(function(name){return{label:name,current:name===hidden.value,act:function(){hidden.value=name;var c=d.querySelector('[data-k-composer-project=\"'+CSS.escape(id)+'\"]');if(c){var l=c.querySelector(\"[data-k-project-name]\");if(l)l.textContent=name;c.setAttribute(\"aria-label\",\"Project: \"+name+\". Change\");}hidden.dispatchEvent(new Event(\"change\",{bubbles:true}));}};}));}\nif(chip){choose(chip);return true;}var attach=d.querySelector('input[type=\"file\"][data-k-attach=\"'+CSS.escape(id)+'\"]');var items=[];if(attach)items.push({label:\"Attach a file\",act:function(){attach.click();}});\nif(hidden)items.push({label:\"Choose project\",act:function(){choose(d.querySelector('[data-k-composer-project=\"'+CSS.escape(id)+'\"]')||plus);}});if(items.length)menu(plus,\"Add to your message\",items);return true;}\nd.addEventListener(\"input\",function(e){var t=e.target;if(t&&t.classList&&t.classList.contains(\"k-composer-ta\"))sync(t);});\nd.addEventListener(\"click\",function(e){var t=e.target;if(!t||!t.closest)return;\nif(t.closest('.k-send[data-gate][aria-disabled=\"true\"]')){e.preventDefault();return;}\nvar draft=t.closest(\"[data-k-draft]\");if(draft){var box=d.getElementById((draft.getAttribute(\"data-k-into\")||\"\")+\"-text\");if(box&&!box.disabled){e.preventDefault();var words=draft.getAttribute(\"data-k-draft\")||\"\";var typed=box.value;\nbox.value=typed.trim()===\"\"?words:typed.indexOf(words)>=0?typed:typed.replace(/\\s+$/,\"\")+\"\\n\"+words;sync(box);box.focus();}return;}\nif((t.closest(\"[data-k-composer-project]\")||t.closest(\"[data-k-composer-plus]\"))&&composerMenu(t)){e.preventDefault();return;}\nvar opener=t.closest(\"[data-k-open]\");if(opener){var panel=d.getElementById(opener.getAttribute(\"data-k-open\")||\"\");if(panel){e.preventDefault();openPanel(panel,opener);}return;}\nif(t.closest(\"[data-k-close]\")){var top=stack[stack.length-1];if(top&&top.panel.contains(t)){e.preventDefault();closeEntry(top);}return;}\nvar sw=t.closest(\"button.k-switch[role=switch][data-k-switch]\");if(sw){var on=sw.getAttribute(\"aria-checked\")!==\"true\";sw.setAttribute(\"aria-checked\",String(on));var w=(sw.getAttribute(\"data-k-switch\")||\"On|Off\").split(\"|\");var s=sw.querySelector(\"[data-k-words]\");if(s)s.textContent=(on?w[0]:w[1])||\"\";sw.dispatchEvent(new CustomEvent(\"k-switch\",{bubbles:true,detail:{on:on}}));}});\nd.addEventListener(\"keydown\",function(e){var top=stack[stack.length-1];if(!top)return;if(e.key===\"Escape\"){e.preventDefault();closeEntry(top);return;}if(e.key!==\"Tab\")return;\nvar items=Array.prototype.filter.call(top.panel.querySelectorAll(FOCUSABLE),visible);if(!items.length){e.preventDefault();top.panel.focus();return;}var first=items[0],last=items[items.length-1];\nif(e.shiftKey&&(d.activeElement===first||d.activeElement===top.panel)){e.preventDefault();last.focus();}else if(!e.shiftKey&&d.activeElement===last){e.preventDefault();first.focus();}},true);\nfunction kb(){var v=window.visualViewport;if(!v)return;var covered=Math.max(0,Math.round(window.innerHeight-v.height-v.offsetTop));d.documentElement.style.setProperty(\"--kb\",covered+\"px\");d.documentElement.classList.toggle(\"kb-open\",covered>80);}\nif(window.visualViewport){window.visualViewport.addEventListener(\"resize\",kb);window.visualViewport.addEventListener(\"scroll\",kb);}\nif(d.readyState===\"loading\")d.addEventListener(\"DOMContentLoaded\",syncAll);else syncAll();\n})();";
20
+ export declare const KIT_STANDALONE_SCRIPT = "(function(){\nif(window.__kitStandalone)return;window.__kitStandalone=1;var d=document;\nvar PHONE=\"(max-width: 760px)\";var FOCUSABLE='a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\nfunction phone(){return window.matchMedia(PHONE).matches;}\nfunction sync(t){var box=t.closest(\".k-composer\");if(!box)return;var st=getComputedStyle(t);var line=parseFloat(st.lineHeight)||21;\nvar pad=(parseFloat(st.paddingTop)||0)+(parseFloat(st.paddingBottom)||0);var max=line*(Number(t.getAttribute(\"data-max\"))||8)+pad;var min=line*(Number(t.getAttribute(\"data-min\"))||1)+pad;\nt.style.height=\"auto\";t.style.height=Math.max(min,Math.min(t.scrollHeight,max))+\"px\";t.style.overflowY=t.scrollHeight>max+1?\"auto\":\"hidden\";\nvar on=t.value.trim()!==\"\";box.classList.toggle(\"has-text\",on);var sends=box.querySelectorAll(\".k-send[data-gate]:not(:disabled)\");for(var i=0;i<sends.length;i++)sends[i].setAttribute(\"aria-disabled\",String(!on));}\nfunction syncAll(){var all=d.querySelectorAll(\".k-composer-ta\");for(var i=0;i<all.length;i++)sync(all[i]);}\nvar stack=[];\nfunction visible(el){return !!el&&el.isConnected&&el.getClientRects().length>0;}\nfunction openPanel(panel,trigger,onClose){if(stack.length)closeTop(\"close\");var floating=panel.classList.contains(\"k-pop\");var ph=phone();\nif(!panel.isConnected)d.body.appendChild(panel);var scrim=d.createElement(\"div\");scrim.className=\"k-scrim\"+(!floating||ph?\" dim\":\"\");panel.parentNode.insertBefore(scrim,panel);\npanel.hidden=false;panel.setAttribute(\"aria-modal\",\"true\");if(!panel.hasAttribute(\"tabindex\"))panel.tabIndex=-1;panel.classList.toggle(\"as-sheet\",floating&&ph);\nif(floating&&!ph&&trigger){var at=trigger.getBoundingClientRect();var w=panel.offsetWidth||336,h=panel.offsetHeight||200;var left=Math.min(Math.max(8,at.right-w),window.innerWidth-w-8);var below=at.bottom+6;var top=below+h>window.innerHeight-8?Math.max(8,at.top-h-6):below;panel.style.left=Math.round(left)+\"px\";panel.style.top=Math.round(top)+\"px\";}\nvar inerted=[];for(var node=panel;node!==d.body&&node.parentElement;node=node.parentElement){var kids=node.parentElement.children;for(var i=0;i<kids.length;i++){var s=kids[i];if(s===node||s===scrim||s.inert)continue;s.inert=true;inerted.push(s);}}\nvar entry={panel:panel,scrim:scrim,trigger:trigger,inerted:inerted,onClose:onClose};stack.push(entry);scrim.addEventListener(\"click\",function(){closeEntry(entry);});\nvar first=panel.querySelector(\"[autofocus]\")||panel.querySelector('input:not([type=\"hidden\"]), textarea, select')||panel;first.focus();}\nfunction closeEntry(entry){var at=stack.indexOf(entry);if(at<0)return;stack.splice(at,1);for(var i=0;i<entry.inerted.length;i++)entry.inerted[i].inert=false;\nentry.scrim.remove();entry.panel.hidden=true;entry.panel.classList.remove(\"as-sheet\");if(visible(entry.trigger))entry.trigger.focus();if(entry.onClose)entry.onClose();}\nfunction closeTop(){var top=stack[stack.length-1];if(top)closeEntry(top);}\nfunction menu(trigger,title,items){var ph=phone();var panel=d.createElement(\"div\");panel.className=ph?\"k-sheet\":\"k-pop k-pop-menu\";panel.setAttribute(\"role\",\"dialog\");\nvar id=\"k-menu-\"+Math.random().toString(36).slice(2,8);panel.setAttribute(\"aria-labelledby\",id);var head=d.createElement(\"div\");head.className=ph?\"k-sheet-h\":\"k-pop-h\";\nvar h=d.createElement(ph?\"h2\":\"h3\");h.id=id;h.textContent=title;var x=d.createElement(\"button\");x.type=\"button\";x.className=\"k-btn k-btn-ghost k-btn-sm k-btn-icon\";x.setAttribute(\"aria-label\",\"Close\");x.setAttribute(\"data-k-close\",\"\");x.textContent=\"\\u00d7\";\nhead.appendChild(h);head.appendChild(x);var list=d.createElement(\"div\");list.className=\"k-menu\";var entry;\nitems.forEach(function(item){var b=d.createElement(\"button\");b.type=\"button\";b.textContent=item.current?item.label+\" \\u00b7 current\":item.label;if(item.current)b.setAttribute(\"aria-current\",\"true\");b.addEventListener(\"click\",function(){closeTop();item.act();});list.appendChild(b);});\npanel.appendChild(head);panel.appendChild(list);openPanel(panel,trigger,function(){panel.remove();});}\nfunction composerMenu(target){var chip=target.closest(\"[data-k-composer-project]\");var plus=target.closest(\"[data-k-composer-plus]\");var id=chip?chip.getAttribute(\"data-k-composer-project\"):plus?plus.getAttribute(\"data-k-composer-plus\"):null;if(id===null)return false;\nvar hidden=d.getElementById(id+\"-project\");function choose(trigger){if(!hidden)return;var items=[];try{var parsed=JSON.parse(hidden.getAttribute(\"data-items\")||\"[]\");if(Array.isArray(parsed))items=parsed.filter(function(one){return typeof one===\"string\";});}catch(e){items=[];}\nmenu(trigger,\"Project\",items.map(function(name){return{label:name,current:name===hidden.value,act:function(){hidden.value=name;var c=d.querySelector('[data-k-composer-project=\"'+CSS.escape(id)+'\"]');if(c){var l=c.querySelector(\"[data-k-project-name]\");if(l)l.textContent=name;c.setAttribute(\"aria-label\",\"Project: \"+name+\". Change\");}hidden.dispatchEvent(new Event(\"change\",{bubbles:true}));}};}));}\nif(chip){choose(chip);return true;}var attach=d.querySelector('input[type=\"file\"][data-k-attach=\"'+CSS.escape(id)+'\"]');var items=[];if(attach)items.push({label:\"Attach a file\",act:function(){attach.click();}});\nif(hidden)items.push({label:\"Choose project\",act:function(){choose(d.querySelector('[data-k-composer-project=\"'+CSS.escape(id)+'\"]')||plus);}});if(items.length)menu(plus,\"Add to your message\",items);return true;}\nd.addEventListener(\"input\",function(e){var t=e.target;if(t&&t.classList&&t.classList.contains(\"k-composer-ta\"))sync(t);});\nd.addEventListener(\"click\",function(e){var t=e.target;if(!t||!t.closest)return;\nif(t.closest('.k-send[data-gate][aria-disabled=\"true\"]')){e.preventDefault();return;}\nvar draft=t.closest(\"[data-k-draft]\");if(draft){var box=d.getElementById((draft.getAttribute(\"data-k-into\")||\"\")+\"-text\");if(box&&!box.disabled){e.preventDefault();var words=draft.getAttribute(\"data-k-draft\")||\"\";var typed=box.value;\nbox.value=typed.trim()===\"\"?words:typed.indexOf(words)>=0?typed:typed.replace(/\\s+$/,\"\")+\"\\n\"+words;sync(box);box.focus();}return;}\nif((t.closest(\"[data-k-composer-project]\")||t.closest(\"[data-k-composer-plus]\"))&&composerMenu(t)){e.preventDefault();return;}\nvar opener=t.closest(\"[data-k-open]\");if(opener){var panel=d.getElementById(opener.getAttribute(\"data-k-open\")||\"\");if(panel){e.preventDefault();openPanel(panel,opener);}return;}\nif(t.closest(\"[data-k-close]\")){var top=stack[stack.length-1];if(top&&top.panel.contains(t)){e.preventDefault();closeEntry(top);}return;}\nvar sw=t.closest(\"button.k-switch[role=switch][data-k-switch]\");if(sw){var on=sw.getAttribute(\"aria-checked\")!==\"true\";sw.setAttribute(\"aria-checked\",String(on));var w=(sw.getAttribute(\"data-k-switch\")||\"On|Off\").split(\"|\");var s=sw.querySelector(\"[data-k-words]\");if(s)s.textContent=(on?w[0]:w[1])||\"\";sw.dispatchEvent(new CustomEvent(\"k-switch\",{bubbles:true,detail:{on:on}}));}});\nd.addEventListener(\"keydown\",function(e){var top=stack[stack.length-1];if(!top)return;if(e.key===\"Escape\"){e.preventDefault();closeEntry(top);return;}if(e.key!==\"Tab\")return;\nvar items=Array.prototype.filter.call(top.panel.querySelectorAll(FOCUSABLE),visible);if(!items.length){e.preventDefault();top.panel.focus();return;}var first=items[0],last=items[items.length-1];\nif(e.shiftKey&&(d.activeElement===first||d.activeElement===top.panel)){e.preventDefault();last.focus();}else if(!e.shiftKey&&d.activeElement===last){e.preventDefault();first.focus();}},true);\nfunction kb(){var v=window.visualViewport;if(!v)return;var covered=Math.max(0,Math.round(window.innerHeight-v.height-v.offsetTop));d.documentElement.style.setProperty(\"--kb\",covered+\"px\");d.documentElement.classList.toggle(\"kb-open\",covered>80);}\nif(window.visualViewport){window.visualViewport.addEventListener(\"resize\",kb);window.visualViewport.addEventListener(\"scroll\",kb);}\nvar root=d.documentElement;root.classList.add(\"k-js\");d.addEventListener(\"keydown\",function(e){if(e.key===\"Tab\"||e.key===\"F6\")root.classList.add(\"k-kbd\");},true);d.addEventListener(\"pointerdown\",function(){root.classList.remove(\"k-kbd\");},true);\nif(d.readyState===\"loading\")d.addEventListener(\"DOMContentLoaded\",syncAll);else syncAll();\n})();";
21
21
  /** Whether a page's markup has kit pieces the standalone behaviour wires (a composer, an overlay, a button switch, an idea). */
22
22
  export declare const KIT_BEHAVIOR_MARKUP: RegExp;
@@ -63,6 +63,7 @@ var items=Array.prototype.filter.call(top.panel.querySelectorAll(FOCUSABLE),visi
63
63
  if(e.shiftKey&&(d.activeElement===first||d.activeElement===top.panel)){e.preventDefault();last.focus();}else if(!e.shiftKey&&d.activeElement===last){e.preventDefault();first.focus();}},true);
64
64
  function kb(){var v=window.visualViewport;if(!v)return;var covered=Math.max(0,Math.round(window.innerHeight-v.height-v.offsetTop));d.documentElement.style.setProperty("--kb",covered+"px");d.documentElement.classList.toggle("kb-open",covered>80);}
65
65
  if(window.visualViewport){window.visualViewport.addEventListener("resize",kb);window.visualViewport.addEventListener("scroll",kb);}
66
+ var root=d.documentElement;root.classList.add("k-js");d.addEventListener("keydown",function(e){if(e.key==="Tab"||e.key==="F6")root.classList.add("k-kbd");},true);d.addEventListener("pointerdown",function(){root.classList.remove("k-kbd");},true);
66
67
  if(d.readyState==="loading")d.addEventListener("DOMContentLoaded",syncAll);else syncAll();
67
68
  })();`;
68
69
  /** Whether a page's markup has kit pieces the standalone behaviour wires (a composer, an overlay, a button switch, an idea). */
@@ -90,7 +90,9 @@ export declare function textarea(options: {
90
90
  error?: boolean;
91
91
  attrs?: Attrs;
92
92
  }): Html;
93
- /** A native select with the kit's chevron: 32px, `sm` 28px in a toolbar, 44px and 16px text on a phone. */
93
+ /** A native select in the field family, with the chevron: 36px, `sm` 28px in a toolbar, 44px and 16px text on a phone.
94
+ * `chip` draws it as a composer's filled pill (a project inside the message box): still a native select, so it works
95
+ * without script. */
94
96
  export declare function select(options: {
95
97
  id?: string;
96
98
  name?: string;
@@ -99,6 +101,7 @@ export declare function select(options: {
99
101
  current?: string;
100
102
  sm?: boolean;
101
103
  wide?: boolean;
104
+ chip?: boolean;
102
105
  attrs?: Attrs;
103
106
  }): Html;
104
107
  /** An on/off setting in a form: a checkbox with role=switch and a visible label (posts `name=on` when on). */
@@ -109,6 +112,15 @@ export declare function switchInput(options: {
109
112
  on: boolean;
110
113
  attrs?: Attrs;
111
114
  }): Html;
115
+ /** An on/off choice inside a composer, drawn as a filled pill chip whose mark fills when on: a checkbox with
116
+ * role=switch (posts `name=value` when on), so it works without script. Its longer words go under the box (`note`). */
117
+ export declare function toggleChip(options: {
118
+ id: string;
119
+ name: string;
120
+ label: string;
121
+ on: boolean;
122
+ attrs?: Attrs;
123
+ }): Html;
112
124
  /** Two columns: label and one muted line | the control. `inline` keeps the control beside the label on a phone. */
113
125
  export declare function formRow(label: string, hint: string | null, control: Html, variant?: "" | "inline" | "top" | "wide"): Html;
114
126
  /** Radio cards: one choice from a few, each with a bold label and one muted line. */
@@ -273,8 +285,9 @@ export declare function sendButton(options?: {
273
285
  attrs?: Attrs;
274
286
  }): Html;
275
287
  /**
276
- * The message box: one soft rounded container, the textarea grows from `rows` to `maxRows` lines then scrolls,
277
- * one bottom row inside (+ menu, the project as a quiet chip, small controls, the round send). Textarea
288
+ * The message box: one filled rounded object, the textarea grows from `rows` to `maxRows` lines then scrolls,
289
+ * one bottom row inside (+ menu, the project as a filled pill chip, toggle chips, the round send); its note and its
290
+ * refusal sit under the box (all inside `.k-composer-field`). Textarea
278
291
  * `<id>-text` (named `name`), refusal slot `<id>-err`, chosen project a hidden input `<id>-project` (named
279
292
  * `projectName`). A refused send keeps the words in the box with the reason in the error slot.
280
293
  */
@@ -291,6 +304,7 @@ export declare function composer(options: {
291
304
  current: string;
292
305
  items: readonly string[];
293
306
  name?: string;
307
+ sub?: string;
294
308
  };
295
309
  before?: Html;
296
310
  send: Html;
@@ -71,18 +71,25 @@ export function input(options) {
71
71
  export function textarea(options) {
72
72
  return html `<textarea class="k-textarea" id="${options.id}" name="${options.name}" rows="${options.rows ?? 3}"${attributes({ "aria-invalid": options.error ? "true" : undefined, "aria-describedby": `${options.id}-err`, ...options.attrs })}>${options.value ?? ""}</textarea>`;
73
73
  }
74
- /** A native select with the kit's chevron: 32px, `sm` 28px in a toolbar, 44px and 16px text on a phone. */
74
+ /** A native select in the field family, with the chevron: 36px, `sm` 28px in a toolbar, 44px and 16px text on a phone.
75
+ * `chip` draws it as a composer's filled pill (a project inside the message box): still a native select, so it works
76
+ * without script. */
75
77
  export function select(options) {
76
78
  const opts = options.items.map(item => {
77
79
  const [value, label] = typeof item === "string" ? [item, item] : item;
78
80
  return html `<option value="${value}"${value === options.current ? html ` selected` : ""}>${label}</option>`;
79
81
  });
80
- return html `<span class="${cls("k-selw", options.sm && "sm", options.wide && "wide")}"><select class="k-sel"${attributes({ id: options.id, name: options.name, "aria-label": options.label, ...options.attrs })}>${opts}</select></span>`;
82
+ return html `<span class="${cls("k-selw", options.sm && "sm", options.wide && "wide", options.chip && "k-selw-chip")}"><select class="k-sel"${attributes({ id: options.id, name: options.name, "aria-label": options.label, ...options.attrs })}>${opts}</select></span>`;
81
83
  }
82
84
  /** An on/off setting in a form: a checkbox with role=switch and a visible label (posts `name=on` when on). */
83
85
  export function switchInput(options) {
84
86
  return html `<label class="k-switch"><input class="k-switch-input" type="checkbox" role="switch" id="${options.id}" name="${options.name}"${options.on ? html ` checked` : ""}${attributes(options.attrs ?? {})}><span class="k-trk" aria-hidden="true"></span><span>${options.label}</span></label>`;
85
87
  }
88
+ /** An on/off choice inside a composer, drawn as a filled pill chip whose mark fills when on: a checkbox with
89
+ * role=switch (posts `name=value` when on), so it works without script. Its longer words go under the box (`note`). */
90
+ export function toggleChip(options) {
91
+ return html `<label class="k-tchip"><input class="k-switch-input" type="checkbox" role="switch" id="${options.id}" name="${options.name}"${options.on ? html ` checked` : ""}${attributes(options.attrs ?? {})}><span class="k-tchip-mark" aria-hidden="true"></span><span>${options.label}</span></label>`;
92
+ }
86
93
  /** Two columns: label and one muted line | the control. `inline` keeps the control beside the label on a phone. */
87
94
  export function formRow(label, hint, control, variant = "") {
88
95
  return html `<div class="${cls("k-frow", variant)}"><div class="k-frow-l"><span class="k-frow-t">${label}</span>${hint ? html `<span class="k-frow-h">${hint}</span>` : ""}</div><div class="k-frow-c">${control}</div></div>`;
@@ -243,8 +250,9 @@ export function sendButton(options = {}) {
243
250
  ${options.stop ? html `<button class="k-send k-stop" type="button" aria-label="${options.stop.label ?? "Stop"}" title="${options.stop.label ?? "Stop"}"${attributes(options.stop.attrs ?? {})}>${icon("stop")}</button>` : ""}</span>`;
244
251
  }
245
252
  /**
246
- * The message box: one soft rounded container, the textarea grows from `rows` to `maxRows` lines then scrolls,
247
- * one bottom row inside (+ menu, the project as a quiet chip, small controls, the round send). Textarea
253
+ * The message box: one filled rounded object, the textarea grows from `rows` to `maxRows` lines then scrolls,
254
+ * one bottom row inside (+ menu, the project as a filled pill chip, toggle chips, the round send); its note and its
255
+ * refusal sit under the box (all inside `.k-composer-field`). Textarea
248
256
  * `<id>-text` (named `name`), refusal slot `<id>-err`, chosen project a hidden input `<id>-project` (named
249
257
  * `projectName`). A refused send keeps the words in the box with the reason in the error slot.
250
258
  */
@@ -252,16 +260,16 @@ export function composer(options) {
252
260
  const value = options.value ?? "";
253
261
  const textId = `${options.id}-text`;
254
262
  const project = options.project
255
- ? html `<button class="k-composer-chip" type="button" data-k-composer-project="${options.id}" aria-haspopup="dialog" aria-label="Project: ${options.project.current}. Change"${options.disabled ? html ` disabled` : ""}><span data-k-project-name>${options.project.current}</span>${icon("chevron", true)}</button>\
263
+ ? html `<button class="k-composer-chip" type="button" data-k-composer-project="${options.id}" aria-haspopup="dialog" aria-label="Project: ${options.project.current}. Change"${options.disabled ? html ` disabled` : ""}><span data-k-project-name>${options.project.current}</span>${options.project.sub ? html `<span class="k-chip-sub">${options.project.sub}</span>` : ""}</button>\
256
264
  <input type="hidden" id="${options.id}-project" name="${options.project.name ?? "project"}" value="${options.project.current}" data-items="${JSON.stringify(options.project.items)}">`
257
265
  : html ``;
258
266
  const plus = options.plus
259
267
  ? html `<button class="k-composer-plus" type="button" data-k-composer-plus="${options.id}" aria-haspopup="dialog" aria-label="Add to your message"${options.disabled ? html ` disabled` : ""}>${icon("plus")}</button>`
260
268
  : html ``;
261
- return html `<div class="${cls("k-composer", options.line && "line", options.disabled && "off", value.trim() && "has-text", options.working && "working")}" data-k-composer="${options.id}">\
269
+ return html `<div class="k-composer-field"><div class="${cls("k-composer", options.line && "line", options.disabled && "off", value.trim() && "has-text", options.working && "working")}" data-k-composer="${options.id}">\
262
270
  <label class="k-sr" for="${textId}">${options.label}</label><div class="k-composer-files" data-k-files="${options.id}"></div>\
263
271
  <textarea class="k-composer-ta" id="${textId}" name="${options.name}" rows="${options.rows ?? 1}" data-min="${options.rows ?? 1}" data-max="${options.maxRows ?? 8}"${attributes({ placeholder: options.placeholder, disabled: options.disabled === true, "aria-invalid": options.error ? "true" : undefined, "aria-describedby": `${options.id}-err`, ...options.textAttrs })}>${value}</textarea>\
264
- <div class="k-composer-bar">${plus}${project}${options.before ?? ""}<span class="k-grow"></span>${options.send}</div>\
272
+ <div class="k-composer-bar">${plus}${project}${options.before ?? ""}<span class="k-grow"></span>${options.send}</div></div>\
265
273
  ${options.note ? html `<p class="k-note k-composer-note">${options.note}</p>` : ""}<div class="k-composer-err k-err" id="${options.id}-err" role="alert">${options.error ?? ""}</div></div>`;
266
274
  }
267
275
  /** A floating panel, hidden until its trigger (`overlayTrigger`) opens it: role=dialog, a labelled Close, Escape
@@ -7,7 +7,7 @@
7
7
  import { html, joinHtml, postForm } from "../html.js";
8
8
  import { projectName } from "../project.js";
9
9
  import { screen } from "./chrome.js";
10
- import { btn, composer, disclose, doc, docHeader, dock, field, ideas, input, note, phoneHeader, radios, select, sendButton, switchInput, textarea } from "./kit.js";
10
+ import { btn, composer, disclose, doc, docHeader, dock, field, ideas, input, note, phoneHeader, radios, select, sendButton, textarea, toggleChip } from "./kit.js";
11
11
  const FORM_ID = "nt-form";
12
12
  const PLAN_ON = "The agent reads the code and asks you anything unclear, then writes the plan.";
13
13
  const PLAN_OFF = "The agent writes the plan straight from your words. Faster for small, obvious changes.";
@@ -51,8 +51,8 @@ suggestions = []) {
51
51
  const folder = !typed ? "" : field({ id: "nt-repo", label: "Project folder", hint: "No projects yet: name the folder of a git repository.",
52
52
  control: input({ id: "nt-repo", name: "repo", value: chosen, hint: true, attrs: { required: true, placeholder: "/path/to/repository", autocapitalize: "none", spellcheck: "false" } }) });
53
53
  const bar = joinHtml([
54
- typed ? "" : select({ id: "nt-project", name: "repo", label: "Project", items, current: chosen, sm: true }),
55
- switchInput({ id: "nt-plan", name: "plan-first", label: "Read code before planning", on: planOn, attrs: { value: "1" } }),
54
+ typed ? "" : select({ id: "nt-project", name: "repo", label: "Project", items, current: chosen, chip: true }),
55
+ toggleChip({ id: "nt-plan", name: "plan-first", label: "Read code before planning", on: planOn, attrs: { value: "1" } }),
56
56
  ]);
57
57
  const box = composer({
58
58
  id: "nt", name: "title", label: "What should change?", placeholder: "What should change? One or two sentences is enough.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.87",
3
+ "version": "0.9.88",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"