@joewinke/jatui 0.22.0 → 0.24.0

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.
Files changed (71) hide show
  1. package/CLAUDE.md +72 -2
  2. package/dist/actions/reveal.d.ts +37 -0
  3. package/dist/actions/reveal.js +42 -0
  4. package/dist/components/AudioRecorder.svelte +529 -0
  5. package/dist/components/AudioRecorder.svelte.d.ts +30 -0
  6. package/dist/components/BulkActionBar.svelte +40 -0
  7. package/dist/components/BulkActionBar.svelte.d.ts +9 -0
  8. package/dist/components/ConfirmDialog.svelte +39 -35
  9. package/dist/components/CopyButton.svelte +5 -2
  10. package/dist/components/FloatingActionBar.svelte +55 -1
  11. package/dist/components/FloatingActionBar.svelte.d.ts +8 -1
  12. package/dist/components/IconSelector.svelte +417 -0
  13. package/dist/components/IconSelector.svelte.d.ts +55 -0
  14. package/dist/components/InfiniteGrid.svelte +305 -0
  15. package/dist/components/InfiniteGrid.svelte.d.ts +71 -0
  16. package/dist/components/InfiniteSearchGrid.svelte +131 -0
  17. package/dist/components/InfiniteSearchGrid.svelte.d.ts +67 -0
  18. package/dist/components/InputDialog.svelte +49 -38
  19. package/dist/components/KeyboardShortcutsOverlay.svelte +94 -3
  20. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +61 -16
  21. package/dist/components/MediaCaptureIcon.svelte +23 -0
  22. package/dist/components/MediaCaptureIcon.svelte.d.ts +1 -1
  23. package/dist/components/ThumbnailGrid.svelte +137 -0
  24. package/dist/components/ThumbnailGrid.svelte.d.ts +42 -0
  25. package/dist/components/batch/BatchJobTable.svelte +549 -0
  26. package/dist/components/batch/BatchJobTable.svelte.d.ts +114 -0
  27. package/dist/components/batch/QuotaMeter.svelte +78 -0
  28. package/dist/components/batch/QuotaMeter.svelte.d.ts +16 -0
  29. package/dist/components/batch/batchQueue.d.ts +56 -0
  30. package/dist/components/batch/batchQueue.js +81 -0
  31. package/dist/components/batch/elapsedEstimate.d.ts +24 -0
  32. package/dist/components/batch/elapsedEstimate.js +25 -0
  33. package/dist/components/batch/pollOperation.d.ts +45 -0
  34. package/dist/components/batch/pollOperation.js +80 -0
  35. package/dist/components/batch/useElapsedEstimate.d.ts +10 -0
  36. package/dist/components/batch/useElapsedEstimate.js +38 -0
  37. package/dist/components/bulkActionBarKeys.d.ts +40 -0
  38. package/dist/components/bulkActionBarKeys.js +38 -0
  39. package/dist/components/infiniteGridLoader.d.ts +70 -0
  40. package/dist/components/infiniteGridLoader.js +141 -0
  41. package/dist/components/linked-columns/LinkedColumns.svelte +10 -22
  42. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +1 -9
  43. package/dist/components/linked-columns/types.d.ts +9 -0
  44. package/dist/components/linked-columns/types.js +1 -0
  45. package/dist/components/replay/ChapterTimeline.svelte +43 -25
  46. package/dist/components/replay/ChapterTimeline.svelte.d.ts +26 -1
  47. package/dist/components/table/columnResize.d.ts +38 -0
  48. package/dist/components/table/columnResize.js +117 -0
  49. package/dist/components/table/index.d.ts +2 -0
  50. package/dist/components/table/index.js +1 -0
  51. package/dist/components/toast/ToastItem.svelte +47 -2
  52. package/dist/components/variant/VariantMatrixPicker.svelte +181 -0
  53. package/dist/components/variant/VariantMatrixPicker.svelte.d.ts +37 -0
  54. package/dist/components/variant/variantCombinations.d.ts +75 -0
  55. package/dist/components/variant/variantCombinations.js +100 -0
  56. package/dist/index.d.ts +34 -3
  57. package/dist/index.js +30 -1
  58. package/dist/stores/toast.d.ts +8 -0
  59. package/dist/stores/toast.js +2 -0
  60. package/dist/styles/motion.css +100 -0
  61. package/dist/utils/audioRecorder.d.ts +28 -0
  62. package/dist/utils/audioRecorder.js +58 -0
  63. package/dist/utils/audioRecorderCore.d.ts +132 -0
  64. package/dist/utils/audioRecorderCore.js +447 -0
  65. package/dist/utils/clipboard.d.ts +25 -0
  66. package/dist/utils/clipboard.js +59 -0
  67. package/dist/utils/dateFormatters.d.ts +11 -2
  68. package/dist/utils/dateFormatters.js +22 -2
  69. package/dist/utils/keyboardShortcutRegistry.d.ts +152 -0
  70. package/dist/utils/keyboardShortcutRegistry.js +365 -0
  71. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Cursor-paged loader behind InfiniteGrid / InfiniteSearchGrid (jatui-m4uz3.4).
3
+ *
4
+ * Ported from chimaro's InfiniteMediaGrid/InfiniteSearchGrid, where the paging
5
+ * machinery was welded to Supabase + media types. This is the machinery alone,
6
+ * DOM-free and framework-free so it can be unit-tested under node:test:
7
+ *
8
+ * - ONE request in flight per generation (the fast-scroll double-fire guard —
9
+ * an IntersectionObserver can call back twice before `loading` flips).
10
+ * - `reset()` bumps a generation and aborts the in-flight request. A response
11
+ * that lands for an older generation is DROPPED, so a slow page for query
12
+ * "ca" can never be appended under the results for "cat" (the cursor race).
13
+ * - Items are de-duplicated by key across pages (offset paging over a list
14
+ * that grew between requests re-sends rows).
15
+ * - Empty and exhausted are distinct states: `empty` = done with zero items,
16
+ * `exhausted` = done with some. A page that returns no items AND repeats the
17
+ * cursor it was given is treated as the end, never as "try again".
18
+ * - A failed page parks in `error`; nothing auto-retries (the observer would
19
+ * hammer a dead endpoint). `retry()` re-requests the same cursor.
20
+ */
21
+ function defaultKey(item) {
22
+ if (item && typeof item === 'object' && 'id' in item)
23
+ return item.id;
24
+ return item;
25
+ }
26
+ function isAbort(err) {
27
+ return (typeof err === 'object' &&
28
+ err !== null &&
29
+ err.name === 'AbortError');
30
+ }
31
+ export function createInfiniteLoader(opts) {
32
+ let load = opts.load;
33
+ const getKey = opts.getKey ?? defaultKey;
34
+ let seen = new Set();
35
+ let controller = null;
36
+ let destroyed = false;
37
+ let state = fresh(0);
38
+ function fresh(generation) {
39
+ return {
40
+ items: [],
41
+ status: 'idle',
42
+ cursor: null,
43
+ initialized: false,
44
+ empty: false,
45
+ exhausted: false,
46
+ error: undefined,
47
+ total: undefined,
48
+ generation,
49
+ lastPageCount: 0
50
+ };
51
+ }
52
+ function commit(patch) {
53
+ const next = { ...state, ...patch };
54
+ next.empty = next.status === 'done' && next.items.length === 0;
55
+ next.exhausted = next.status === 'done' && next.items.length > 0;
56
+ state = next;
57
+ if (!destroyed)
58
+ opts.onChange?.(state);
59
+ }
60
+ async function fetchPage() {
61
+ const generation = state.generation;
62
+ const sentCursor = state.cursor;
63
+ const ctrl = new AbortController();
64
+ controller = ctrl;
65
+ commit({ status: 'loading', error: undefined });
66
+ let page;
67
+ try {
68
+ page = await load(sentCursor, { signal: ctrl.signal });
69
+ }
70
+ catch (err) {
71
+ if (generation !== state.generation || destroyed)
72
+ return; // superseded
73
+ if (controller === ctrl)
74
+ controller = null;
75
+ if (isAbort(err)) {
76
+ commit({ status: 'idle' });
77
+ return;
78
+ }
79
+ commit({ status: 'error', error: err, initialized: true });
80
+ return;
81
+ }
82
+ // The cursor race: this page belongs to a generation the grid has left.
83
+ if (generation !== state.generation || destroyed || ctrl.signal.aborted)
84
+ return;
85
+ if (controller === ctrl)
86
+ controller = null;
87
+ const incoming = Array.isArray(page?.items) ? page.items : [];
88
+ const added = [];
89
+ for (const item of incoming) {
90
+ const key = getKey(item);
91
+ if (seen.has(key))
92
+ continue;
93
+ seen.add(key);
94
+ added.push(item);
95
+ }
96
+ const next = page?.nextCursor ?? null;
97
+ // No progress = end. A server that keeps handing back the same cursor with
98
+ // nothing new would otherwise loop the observer forever.
99
+ const stalled = added.length === 0 && next !== null && Object.is(next, sentCursor);
100
+ const done = next === null || stalled;
101
+ commit({
102
+ items: added.length ? [...state.items, ...added] : state.items,
103
+ cursor: done ? sentCursor : next,
104
+ status: done ? 'done' : 'idle',
105
+ initialized: true,
106
+ total: page?.total ?? state.total,
107
+ lastPageCount: added.length
108
+ });
109
+ }
110
+ return {
111
+ get state() {
112
+ return state;
113
+ },
114
+ loadNext() {
115
+ if (destroyed || state.status !== 'idle')
116
+ return Promise.resolve();
117
+ return fetchPage();
118
+ },
119
+ retry() {
120
+ if (destroyed || state.status !== 'error')
121
+ return Promise.resolve();
122
+ return fetchPage();
123
+ },
124
+ reset() {
125
+ controller?.abort();
126
+ controller = null;
127
+ seen = new Set();
128
+ state = fresh(state.generation + 1);
129
+ if (!destroyed)
130
+ opts.onChange?.(state);
131
+ },
132
+ setLoad(next) {
133
+ load = next;
134
+ },
135
+ destroy() {
136
+ destroyed = true;
137
+ controller?.abort();
138
+ controller = null;
139
+ }
140
+ };
141
+ }
@@ -1,17 +1,3 @@
1
- <script module lang="ts">
2
- // Declared in the module script so `import type { ColumnLink } from
3
- // './LinkedColumns.svelte'` resolves (instance-script exports are not module members).
4
- /** Describes which left items a single right (anchor) item covers. */
5
- export interface ColumnLink {
6
- /** The right item's id this link belongs to. */
7
- rightId: string | number;
8
- /** First left item id in the covered range (inclusive). */
9
- leftStartId: string | number;
10
- /** Last left item id in the covered range (inclusive). */
11
- leftEndId: string | number;
12
- }
13
- </script>
14
-
15
1
  <script lang="ts" generics="L extends { id: string | number }, R extends { id: string | number }">
16
2
  /**
17
3
  * LinkedColumns — generic two-column ink-up ribbon primitive.
@@ -64,8 +50,7 @@
64
50
  */
65
51
 
66
52
  import type { Snippet } from 'svelte';
67
-
68
- // ColumnLink is declared in the module <script> above so it's importable as a module member.
53
+ import type { ColumnLink } from './types.js';
69
54
 
70
55
  let {
71
56
  leftItems,
@@ -410,12 +395,15 @@
410
395
  </div>
411
396
 
412
397
  <style>
398
+ /* Theme tokens (jatui-is2ow.3): fallbacks chain token → token (never a
399
+ hard-coded oklch). --n-16 is a jat-IDE neutral; its inline fallback keeps
400
+ standalone consumers (no app.css) rendering the same value. */
413
401
  .lc-wrap {
414
402
  position: relative;
415
403
  display: flex;
416
404
  height: 100%;
417
405
  min-height: 0;
418
- background: var(--color-base-100, oklch(0.16 0.01 250));
406
+ background: var(--color-base-100, var(--color-base-300));
419
407
  border-radius: 0.75rem;
420
408
  overflow: hidden;
421
409
  }
@@ -431,7 +419,7 @@
431
419
  transition: stroke-opacity 0.25s ease, stroke-width 0.25s ease;
432
420
  }
433
421
  .lc-ribbon-active {
434
- filter: drop-shadow(0 0 6px var(--color-success, oklch(0.7 0.18 145)));
422
+ filter: drop-shadow(0 0 6px var(--color-success));
435
423
  }
436
424
 
437
425
  /* Columns */
@@ -473,10 +461,10 @@
473
461
  font-weight: 700;
474
462
  letter-spacing: 0.08em;
475
463
  text-transform: uppercase;
476
- color: var(--color-base-content, oklch(0.85 0.02 250));
464
+ color: var(--color-base-content);
477
465
  opacity: 0.55;
478
466
  background: linear-gradient(
479
- var(--color-base-100, oklch(0.16 0.01 250)) 70%,
467
+ var(--color-base-100, var(--n-16, oklch(0.16 0.01 250))) 70%,
480
468
  transparent
481
469
  );
482
470
  }
@@ -489,8 +477,8 @@
489
477
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
490
478
  }
491
479
  .lc-left-item-active {
492
- background: color-mix(in oklch, var(--color-success, oklch(0.7 0.18 145)) 8%, transparent);
493
- box-shadow: inset 2px 0 0 var(--color-success, oklch(0.7 0.18 145));
480
+ background: color-mix(in oklch, var(--color-success) 8%, transparent);
481
+ box-shadow: inset 2px 0 0 var(--color-success);
494
482
  }
495
483
 
496
484
  /* Right item wrapper — consumers style via snippet; provides CSS vars */
@@ -1,12 +1,3 @@
1
- /** Describes which left items a single right (anchor) item covers. */
2
- export interface ColumnLink {
3
- /** The right item's id this link belongs to. */
4
- rightId: string | number;
5
- /** First left item id in the covered range (inclusive). */
6
- leftStartId: string | number;
7
- /** Last left item id in the covered range (inclusive). */
8
- leftEndId: string | number;
9
- }
10
1
  /**
11
2
  * LinkedColumns — generic two-column ink-up ribbon primitive.
12
3
  *
@@ -57,6 +48,7 @@ export interface ColumnLink {
57
48
  * ```
58
49
  */
59
50
  import type { Snippet } from 'svelte';
51
+ import type { ColumnLink } from './types.js';
60
52
  declare function $$render<L extends {
61
53
  id: string | number;
62
54
  }, R extends {
@@ -0,0 +1,9 @@
1
+ /** Describes which left items a single right (anchor) item covers. */
2
+ export interface ColumnLink {
3
+ /** The right item's id this link belongs to. */
4
+ rightId: string | number;
5
+ /** First left item id in the covered range (inclusive). */
6
+ leftStartId: string | number;
7
+ /** Last left item id in the covered range (inclusive). */
8
+ leftEndId: string | number;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -11,7 +11,12 @@
11
11
  * on the full IDE codebase (jat-jj79f.16 G1).
12
12
  *
13
13
  * The IDE-specific `SESSION_STATE_VISUALS` import has been replaced with
14
- * an inline `SIGNAL_COLORS` map that covers the same canonical signal types.
14
+ * an inline `SIGNAL_COLORS` map that covers the same canonical signal types,
15
+ * plus an optional `stateVisual(state)` resolver prop (jatui-is2ow.4): a
16
+ * caller — e.g. the jat IDE with its own SESSION_STATE_VISUALS — can supply
17
+ * its own colour resolver and the built-in palette is only used as the
18
+ * default / fallback. `ChapterBlock.itemRange` matches the IDE's WorkBlock
19
+ * shape so IDE callers can pass blocks straight through without remapping.
15
20
  */
16
21
 
17
22
  export interface ChapterBlock {
@@ -22,17 +27,20 @@
22
27
  filesTouched: { path: string; how: string }[];
23
28
  checkpoint: { timestamp: string | null };
24
29
  summary: string | null;
30
+ /** Index span of the transcript items this block owns (IDE WorkBlock shape). */
31
+ itemRange?: { start: number; end: number };
25
32
  }
26
33
 
27
- /** Minimal color descriptors for each canonical signal state. */
28
- interface SignalColor {
34
+ /** Colour descriptors the timeline needs per canonical signal state. */
35
+ export interface ChapterStateVisual {
29
36
  accent: string;
30
37
  bgTint: string;
31
38
  glow: string;
32
39
  shortLabel: string;
33
40
  }
34
41
 
35
- const SIGNAL_COLORS: Record<string, SignalColor> = {
42
+ /** Default palette for each canonical signal state — kept for standalone consumers without a resolver. */
43
+ const SIGNAL_COLORS: Record<string, ChapterStateVisual> = {
36
44
  starting: { accent: 'oklch(0.70 0.16 250)', bgTint: 'oklch(0.70 0.16 250 / 0.10)', glow: 'oklch(0.70 0.16 250 / 0.45)', shortLabel: '🚀 Starting' },
37
45
  working: { accent: 'oklch(0.75 0.16 85)', bgTint: 'oklch(0.75 0.16 85 / 0.08)', glow: 'oklch(0.75 0.16 85 / 0.45)', shortLabel: '🔧 Working' },
38
46
  'needs-input': { accent: 'oklch(0.68 0.19 25)', bgTint: 'oklch(0.68 0.19 25 / 0.08)', glow: 'oklch(0.68 0.19 25 / 0.4)', shortLabel: '❓ Needs Input' },
@@ -49,10 +57,19 @@
49
57
  blocks = [],
50
58
  activeBlockId = null,
51
59
  onSeek = (_blockId: string) => {},
60
+ stateVisual,
52
61
  }: {
53
62
  blocks: ChapterBlock[];
54
63
  activeBlockId: string | null;
55
64
  onSeek?: (blockId: string) => void;
65
+ /**
66
+ * Optional colour resolver: canonical state name → visual. Receives the
67
+ * NORMALIZED state (e.g. `'needs-input'`, not the raw `'needs_input'`),
68
+ * so a caller can pass `(state) => SESSION_STATE_VISUALS[state]` directly.
69
+ * Return `undefined` for unknown states — the built-in palette (then
70
+ * idle) is used as the fallback, matching the IDE's `?? idle` behaviour.
71
+ */
72
+ stateVisual?: (state: string) => ChapterStateVisual | undefined;
56
73
  } = $props();
57
74
 
58
75
  function rawTypeToState(raw: string): string {
@@ -65,8 +82,9 @@
65
82
  }
66
83
  }
67
84
 
68
- function visual(signalType: string): SignalColor {
69
- return SIGNAL_COLORS[rawTypeToState(signalType)] ?? DEFAULT_COLOR;
85
+ function visual(signalType: string): ChapterStateVisual {
86
+ const state = rawTypeToState(signalType);
87
+ return stateVisual?.(state) ?? SIGNAL_COLORS[state] ?? DEFAULT_COLOR;
70
88
  }
71
89
 
72
90
  function glyph(signalType: string): string {
@@ -183,7 +201,7 @@
183
201
 
184
202
  .empty {
185
203
  font-size: 0.75rem;
186
- color: oklch(0.55 0.02 250);
204
+ color: oklch(from var(--color-base-content) l c h / 60%);
187
205
  padding: 0.5rem;
188
206
  text-align: center;
189
207
  }
@@ -193,8 +211,8 @@
193
211
  height: 2.25rem;
194
212
  border-radius: 0.5rem;
195
213
  overflow: hidden;
196
- background: oklch(0.14 0.01 250);
197
- border: 1px solid oklch(0.22 0.02 250);
214
+ background: var(--color-base-300);
215
+ border: 1px solid var(--color-base-100);
198
216
  gap: 1px;
199
217
  }
200
218
 
@@ -203,9 +221,9 @@
203
221
  align-items: center;
204
222
  gap: 0.3rem;
205
223
  padding: 0 0.5rem;
206
- background: var(--chapter-tint, oklch(0.18 0.01 250));
224
+ background: var(--chapter-tint, var(--color-base-200));
207
225
  border: none;
208
- border-right: 1px solid oklch(0.20 0.02 250);
226
+ border-right: 1px solid var(--color-base-100);
209
227
  cursor: pointer;
210
228
  transition: background 0.12s ease, box-shadow 0.12s ease;
211
229
  overflow: hidden;
@@ -218,12 +236,12 @@
218
236
  }
219
237
 
220
238
  .chapter:hover {
221
- background: color-mix(in oklch, var(--chapter-accent) 18%, oklch(0.16 0.01 250));
239
+ background: color-mix(in oklch, var(--chapter-accent) 18%, var(--color-base-300));
222
240
  box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--chapter-accent) 40%, transparent);
223
241
  }
224
242
 
225
243
  .chapter.active {
226
- background: color-mix(in oklch, var(--chapter-accent) 22%, oklch(0.16 0.01 250));
244
+ background: color-mix(in oklch, var(--chapter-accent) 22%, var(--color-base-300));
227
245
  box-shadow:
228
246
  inset 0 0 0 2px color-mix(in oklch, var(--chapter-accent) 70%, transparent),
229
247
  0 0 12px color-mix(in oklch, var(--chapter-glow) 30%, transparent);
@@ -247,20 +265,20 @@
247
265
 
248
266
  .ch-label {
249
267
  font-size: 0.7rem;
250
- color: oklch(0.78 0.04 250);
268
+ color: oklch(from var(--color-base-content) l c h / 90%);
251
269
  overflow: hidden;
252
270
  text-overflow: ellipsis;
253
271
  min-width: 0;
254
272
  }
255
273
 
256
274
  .chapter.active .ch-label {
257
- color: oklch(0.92 0.06 250);
275
+ color: oklch(from var(--color-primary) 0.92 0.06 h);
258
276
  font-weight: 600;
259
277
  }
260
278
 
261
279
  .ch-meta {
262
280
  font-size: 0.6rem;
263
- color: oklch(0.50 0.02 250);
281
+ color: oklch(from var(--color-base-content) l c h / 55%);
264
282
  margin-left: auto;
265
283
  flex-shrink: 0;
266
284
  font-family: monospace;
@@ -273,10 +291,10 @@
273
291
  gap: 0.6rem;
274
292
  padding: 0.25rem 0.5rem;
275
293
  font-size: 0.72rem;
276
- color: oklch(0.70 0.04 250);
277
- background: oklch(0.15 0.01 250);
294
+ color: oklch(from var(--color-base-content) l c h / 80%);
295
+ background: var(--color-base-300);
278
296
  border-radius: 0.375rem;
279
- border: 1px solid oklch(0.22 0.02 250);
297
+ border: 1px solid var(--color-base-100);
280
298
  min-height: 1.75rem;
281
299
  flex-wrap: wrap;
282
300
  gap-y: 0.15rem;
@@ -285,7 +303,7 @@
285
303
  .ch-detail-idx {
286
304
  font-size: 0.65rem;
287
305
  font-family: monospace;
288
- color: oklch(0.50 0.02 250);
306
+ color: oklch(from var(--color-base-content) l c h / 55%);
289
307
  flex-shrink: 0;
290
308
  }
291
309
 
@@ -297,7 +315,7 @@
297
315
  }
298
316
 
299
317
  .ch-detail-summary.muted {
300
- color: oklch(0.55 0.02 250);
318
+ color: oklch(from var(--color-base-content) l c h / 60%);
301
319
  font-style: italic;
302
320
  }
303
321
 
@@ -310,17 +328,17 @@
310
328
  .ch-file {
311
329
  font-size: 0.64rem;
312
330
  font-family: monospace;
313
- background: oklch(0.19 0.02 250);
314
- border: 1px solid oklch(0.25 0.02 250);
331
+ background: var(--color-base-200);
332
+ border: 1px solid var(--color-base-100);
315
333
  border-radius: 0.2rem;
316
334
  padding: 0.1rem 0.35rem;
317
- color: oklch(0.70 0.08 200);
335
+ color: var(--color-info);
318
336
  }
319
337
 
320
338
  .ch-detail-ts {
321
339
  font-size: 0.62rem;
322
340
  font-family: monospace;
323
- color: oklch(0.48 0.02 250);
341
+ color: oklch(from var(--color-base-content) l c h / 50%);
324
342
  flex-shrink: 0;
325
343
  }
326
344
  </style>
@@ -10,7 +10,12 @@
10
10
  * on the full IDE codebase (jat-jj79f.16 G1).
11
11
  *
12
12
  * The IDE-specific `SESSION_STATE_VISUALS` import has been replaced with
13
- * an inline `SIGNAL_COLORS` map that covers the same canonical signal types.
13
+ * an inline `SIGNAL_COLORS` map that covers the same canonical signal types,
14
+ * plus an optional `stateVisual(state)` resolver prop (jatui-is2ow.4): a
15
+ * caller — e.g. the jat IDE with its own SESSION_STATE_VISUALS — can supply
16
+ * its own colour resolver and the built-in palette is only used as the
17
+ * default / fallback. `ChapterBlock.itemRange` matches the IDE's WorkBlock
18
+ * shape so IDE callers can pass blocks straight through without remapping.
14
19
  */
15
20
  export interface ChapterBlock {
16
21
  id: string;
@@ -28,11 +33,31 @@ export interface ChapterBlock {
28
33
  timestamp: string | null;
29
34
  };
30
35
  summary: string | null;
36
+ /** Index span of the transcript items this block owns (IDE WorkBlock shape). */
37
+ itemRange?: {
38
+ start: number;
39
+ end: number;
40
+ };
41
+ }
42
+ /** Colour descriptors the timeline needs per canonical signal state. */
43
+ export interface ChapterStateVisual {
44
+ accent: string;
45
+ bgTint: string;
46
+ glow: string;
47
+ shortLabel: string;
31
48
  }
32
49
  type $$ComponentProps = {
33
50
  blocks: ChapterBlock[];
34
51
  activeBlockId: string | null;
35
52
  onSeek?: (blockId: string) => void;
53
+ /**
54
+ * Optional colour resolver: canonical state name → visual. Receives the
55
+ * NORMALIZED state (e.g. `'needs-input'`, not the raw `'needs_input'`),
56
+ * so a caller can pass `(state) => SESSION_STATE_VISUALS[state]` directly.
57
+ * Return `undefined` for unknown states — the built-in palette (then
58
+ * idle) is used as the fallback, matching the IDE's `?? idle` behaviour.
59
+ */
60
+ stateVisual?: (state: string) => ChapterStateVisual | undefined;
36
61
  };
37
62
  declare const ChapterTimeline: import("svelte").Component<$$ComponentProps, {}, "">;
38
63
  type ChapterTimeline = ReturnType<typeof ChapterTimeline>;
@@ -0,0 +1,38 @@
1
+ export interface ColumnResizeOptions {
2
+ /** When true, the resize handle is not added and nothing happens. Default: false */
3
+ disabled?: boolean;
4
+ /** Minimum column width in pixels. Default: 40 */
5
+ minWidth?: number;
6
+ /** Called on mousedown — use this to show the guide line (e.g. set resizeGuideX state) */
7
+ onResizeStart?: (initialX: number) => void;
8
+ /** Called on every mousemove with the new width */
9
+ onResize: (width: number) => void;
10
+ /** Called on mouseup with the final width — use this to save settings and hide the guide */
11
+ onResizeEnd?: (width: number) => void;
12
+ /**
13
+ * Return the container element that holds the `.resize-guide` element.
14
+ * If provided, the guide line position is updated on each mousemove via direct DOM manipulation,
15
+ * avoiding reactive overhead.
16
+ */
17
+ getGuideContainer?: () => HTMLElement | null;
18
+ }
19
+ /**
20
+ * Svelte action that attaches a column-resize handle to a `<th>` (or any element).
21
+ *
22
+ * Usage:
23
+ * ```svelte
24
+ * <th use:columnResize={{
25
+ * minWidth: col.minWidth,
26
+ * onResize: (w) => { columnWidths[col.id] = w; },
27
+ * onResizeEnd: (w) => { saveColumnSettings(); },
28
+ * getGuideContainer: () => tableContainerEl,
29
+ * }}>
30
+ * ```
31
+ *
32
+ * The action appends a 4px-wide resize handle div at the right edge of the host element.
33
+ * Dragging it fires `onResize` continuously and `onResizeEnd` on mouse-up.
34
+ */
35
+ export declare function columnResize(node: HTMLElement, options: ColumnResizeOptions): {
36
+ update(opts: ColumnResizeOptions): void;
37
+ destroy(): void;
38
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Svelte action that attaches a column-resize handle to a `<th>` (or any element).
3
+ *
4
+ * Usage:
5
+ * ```svelte
6
+ * <th use:columnResize={{
7
+ * minWidth: col.minWidth,
8
+ * onResize: (w) => { columnWidths[col.id] = w; },
9
+ * onResizeEnd: (w) => { saveColumnSettings(); },
10
+ * getGuideContainer: () => tableContainerEl,
11
+ * }}>
12
+ * ```
13
+ *
14
+ * The action appends a 4px-wide resize handle div at the right edge of the host element.
15
+ * Dragging it fires `onResize` continuously and `onResizeEnd` on mouse-up.
16
+ */
17
+ export function columnResize(node, options) {
18
+ let opts = options;
19
+ // If disabled, return a no-op handle
20
+ if (opts.disabled) {
21
+ return {
22
+ update(newOptions) { opts = newOptions; },
23
+ destroy() { },
24
+ };
25
+ }
26
+ // Build the resize handle element
27
+ const handle = document.createElement('div');
28
+ handle.className = 'col-resize-handle';
29
+ handle.style.cssText = [
30
+ 'position: absolute',
31
+ 'right: 0',
32
+ 'top: 0',
33
+ 'bottom: 0',
34
+ 'width: 5px',
35
+ 'cursor: col-resize',
36
+ 'z-index: 2',
37
+ ].join(';');
38
+ // Make the host element position-relative if it isn't already
39
+ const hostPosition = getComputedStyle(node).position;
40
+ if (hostPosition === 'static') {
41
+ node.style.position = 'relative';
42
+ }
43
+ node.appendChild(handle);
44
+ // Resize tracking state
45
+ let startX = 0;
46
+ let startWidth = 0;
47
+ let currentWidth = 0;
48
+ let resizing = false;
49
+ function updateGuide(clientX) {
50
+ const container = opts.getGuideContainer?.();
51
+ if (!container)
52
+ return;
53
+ const guide = container.querySelector('.resize-guide');
54
+ if (!guide)
55
+ return;
56
+ const rect = container.getBoundingClientRect();
57
+ guide.style.left = `${clientX - rect.left + container.scrollLeft}px`;
58
+ }
59
+ function onMouseMove(e) {
60
+ const minWidth = opts.minWidth ?? 40;
61
+ const diff = e.clientX - startX;
62
+ const newWidth = Math.max(minWidth, startWidth + diff);
63
+ currentWidth = newWidth;
64
+ opts.onResize(newWidth);
65
+ updateGuide(e.clientX);
66
+ }
67
+ function onMouseUp() {
68
+ document.body.style.cursor = '';
69
+ document.body.style.userSelect = '';
70
+ // Call onResizeEnd — the parent is responsible for clearing the guide line
71
+ // (typically by setting resizeGuideX = null in its onResizeEnd handler)
72
+ opts.onResizeEnd?.(currentWidth);
73
+ document.removeEventListener('mousemove', onMouseMove);
74
+ document.removeEventListener('mouseup', onMouseUp);
75
+ // Suppress the click event that fires after mouseup on the host <th>,
76
+ // which would otherwise trigger a column sort.
77
+ resizing = true;
78
+ requestAnimationFrame(() => { resizing = false; });
79
+ }
80
+ function onMouseDown(e) {
81
+ e.preventDefault();
82
+ e.stopPropagation();
83
+ startX = e.clientX;
84
+ startWidth = node.offsetWidth;
85
+ currentWidth = startWidth;
86
+ document.body.style.cursor = 'col-resize';
87
+ document.body.style.userSelect = 'none';
88
+ opts.onResizeStart?.(e.clientX);
89
+ updateGuide(e.clientX);
90
+ document.addEventListener('mousemove', onMouseMove);
91
+ document.addEventListener('mouseup', onMouseUp);
92
+ }
93
+ // Suppress click events on the host element that fire right after a resize ends.
94
+ // Without this, mouseup after dragging the resize handle triggers the host's onclick (e.g. sort).
95
+ function onHostClick(e) {
96
+ if (resizing) {
97
+ e.stopImmediatePropagation();
98
+ e.preventDefault();
99
+ }
100
+ }
101
+ handle.addEventListener('mousedown', onMouseDown);
102
+ node.addEventListener('click', onHostClick, true);
103
+ return {
104
+ update(newOptions) {
105
+ opts = newOptions;
106
+ },
107
+ destroy() {
108
+ handle.removeEventListener('mousedown', onMouseDown);
109
+ node.removeEventListener('click', onHostClick, true);
110
+ document.removeEventListener('mousemove', onMouseMove);
111
+ document.removeEventListener('mouseup', onMouseUp);
112
+ if (handle.parentNode === node) {
113
+ node.removeChild(handle);
114
+ }
115
+ },
116
+ };
117
+ }
@@ -1,3 +1,5 @@
1
1
  export { default as DataTable } from './DataTable.svelte';
2
2
  export { flexRender } from './flexRender';
3
+ export { columnResize } from './columnResize';
4
+ export type { ColumnResizeOptions } from './columnResize';
3
5
  export type { DataTableProps, SelectionEvent, ColumnDef, Row } from './types';
@@ -1,3 +1,4 @@
1
1
  // Subpath entry: @joewinke/jatui/table — requires peer @tanstack/table-core.
2
2
  export { default as DataTable } from './DataTable.svelte';
3
3
  export { flexRender } from './flexRender';
4
+ export { columnResize } from './columnResize';