@anchrd/intel-ui 0.18.0 → 0.19.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 (63) hide show
  1. package/package.json +2 -9
  2. package/src/app/app-tree/app-tree.tsx +59 -147
  3. package/src/app/settings-dialog/settings-dialog.tsx +10 -32
  4. package/src/data/intel-data-provider/intel-data-provider.ts +0 -163
  5. package/src/data/intel-data-provider/intel-data-provider.types.ts +0 -100
  6. package/src/entry-picker/entry-picker.tsx +6 -6
  7. package/src/i18n/de.json +6 -217
  8. package/src/i18n/en.json +6 -217
  9. package/src/i18n/es.json +6 -217
  10. package/src/import-bundle/import-bundle.tsx +152 -0
  11. package/src/kind-icon.ts +1 -15
  12. package/src/main.tsx +7 -10
  13. package/src/node-table/node-table.tsx +2 -2
  14. package/src/nodes/nodes.tsx +0 -34
  15. package/src/resource-menu/resource-menu.tsx +42 -14
  16. package/src/section-hint/section-hint.tsx +2 -2
  17. package/src/title-row/title-row.tsx +5 -22
  18. package/src/tools/tools.tsx +0 -2
  19. package/vite.config.ts +0 -4
  20. package/src/agent/agent-avatar/agent-avatar.tsx +0 -34
  21. package/src/agent/agent-calendar/agent-calendar.tsx +0 -160
  22. package/src/agent/agent-chat/agent-chat.tsx +0 -118
  23. package/src/agent/agent-costs/agent-costs.ts +0 -95
  24. package/src/agent/agent-cron/agent-cron.ts +0 -68
  25. package/src/agent/agent-definition/agent-definition.ts +0 -96
  26. package/src/agent/agent-delegation-notice/agent-delegation-notice.ts +0 -48
  27. package/src/agent/agent-entry-title/agent-entry-title.ts +0 -72
  28. package/src/agent/agent-log/agent-log.tsx +0 -308
  29. package/src/agent/agent-models/agent-models.ts +0 -166
  30. package/src/agent/agent-models/agent-models.types.ts +0 -24
  31. package/src/agent/agent-profile/agent-profile.tsx +0 -1006
  32. package/src/agent/agent-state/agent-state.ts +0 -85
  33. package/src/agent/agent-status-dot/agent-status-dot.ts +0 -73
  34. package/src/agent/agent.tsx +0 -507
  35. package/src/board/board-calendar/board-calendar.tsx +0 -89
  36. package/src/board/board-card/board-card.tsx +0 -106
  37. package/src/board/board-data/board-data.ts +0 -241
  38. package/src/board/board-data/board-data.types.ts +0 -63
  39. package/src/board/board-detail/board-detail.tsx +0 -629
  40. package/src/board/board-gantt/board-gantt.ts +0 -545
  41. package/src/board/board-gantt/board-gantt.tsx +0 -286
  42. package/src/board/board-graph/board-graph.ts +0 -174
  43. package/src/board/board-graph/board-graph.tsx +0 -168
  44. package/src/board/board-items/board-items.ts +0 -183
  45. package/src/board/board-kanban/board-kanban.ts +0 -137
  46. package/src/board/board-kanban/board-kanban.tsx +0 -257
  47. package/src/board/board-status/board-status.ts +0 -59
  48. package/src/board/board-statuses/board-statuses.ts +0 -63
  49. package/src/board/board-statuses/board-statuses.tsx +0 -228
  50. package/src/board/board-table/board-table.ts +0 -33
  51. package/src/board/board-table/board-table.tsx +0 -413
  52. package/src/board/board-views/board-views.tsx +0 -68
  53. package/src/board/board-views/board-views.types.ts +0 -29
  54. package/src/board/board.tsx +0 -251
  55. package/src/components/ui/item-calendar.tsx +0 -181
  56. package/src/components/ui/item-gantt.tsx +0 -463
  57. package/src/components/ui/kanban.tsx +0 -282
  58. package/src/data/agent-runtime/agent-runtime.ts +0 -131
  59. package/src/hooks/use-capabilities.ts +0 -22
  60. package/src/hooks/use-model-catalog.ts +0 -26
  61. package/src/timezone/timezone-combobox/timezone-combobox.tsx +0 -149
  62. package/src/timezone/timezone-context.tsx +0 -65
  63. package/src/timezone/timezone.ts +0 -174
@@ -1,463 +0,0 @@
1
- import { DndContext, PointerSensor, useDraggable, useSensor, useSensors } from "@dnd-kit/core";
2
- import { type ReactNode, useEffect, useId, useRef, useState } from "react";
3
- import { cn } from "@/lib/utils";
4
-
5
- /**
6
- * A timeline with bars, arrows between them, and edges you can drag — from the Kibo UI Gantt (MIT)
7
- * and changed where it had to be (anchrd/intel#293).
8
- *
9
- * Registry code is our code. Four dependencies came with the original and none of them survived
10
- * contact with this board; the reasoning is below, because "we dropped it" is not a reason anybody
11
- * can check later.
12
- *
13
- * ⚠️ **`jotai` is gone, for the same fault the calendar had.** The original keeps `draggingAtom` and
14
- * `scrollXAtom` at MODULE scope. That is not component state stored elsewhere — it is one scroll
15
- * position and one drag flag shared by every Gantt the bundle renders, outliving unmount. Two boards
16
- * side by side would report each other's scroll, and here that is worse than the calendar's shared
17
- * month was: the original computes a resize from `mouseX - ganttLeft + scrollX`, so a second chart
18
- * scrolled elsewhere would make the first one commit a date the reader never dragged to. A wrong
19
- * picture is a bug; a wrong picture that WRITES is a different kind. The drag state below is one
20
- * `useState` in one component.
21
- *
22
- * ⚠️ **`lodash.throttle` is gone with it.** It threw the scroll handler, and that handler was the
23
- * only caller. The original also wraps it in `useCallback(throttle(...), [])` over an empty
24
- * dependency list while reading `timelineData` inside — so the infinite-scroll extension it exists
25
- * for reads the first render's timeline for ever. This chart has no infinite scroll to throttle: a
26
- * board's own dates say how wide it is, which is a bounded window and one that arrows can be drawn
27
- * across (the whole point of the ticket) without their coordinates moving underneath them.
28
- *
29
- * ⚠️ **`@uidotdev/usehooks` is gone.** It supplied `useMouse` for the drag arithmetic — a global
30
- * pointer position, corrected afterwards by the container's rectangle and the scroll offset. dnd-kit
31
- * already reports the displacement of the drag itself (`delta.x`), which needs no correcting and is
32
- * right inside a scrolled container. Reading only `x` is also what `@dnd-kit/modifiers`'
33
- * `restrictToHorizontalAxis` was in the dependency list for, so that package is not needed either.
34
- *
35
- * ⚠️ **`date-fns` is gone**, as it went from the calendar. The dates here are days (`BoardTaskDate`
36
- * carries no time and no zone), so every offset is a day count times a day's width — arithmetic that
37
- * lives in `board-gantt.ts` where it can be asserted without rendering anything.
38
- *
39
- * Two more differences from the original, both about writing rather than drawing:
40
- *
41
- * ⚠️ **A drag reports ONCE, when it is let go.** The original moves the bar by writing to component
42
- * state on every pointer move and calls `onMove` at the end with whatever that state reached. Against
43
- * a server the risk is the same one #286 found in the date fields: a handler on the wrong event turns
44
- * one gesture into a write per frame. The preview here is local, `onEdit` fires once, and the day it
45
- * reports is the day the reader saw — the preview snaps to whole days for exactly that reason.
46
- *
47
- * ⚠️ **`Card` and `ContextMenu` are not pulled in.** They are registry dependencies of the original
48
- * for a bar wrapper and a right-click menu this board does not have. Two shadcn primitives added for
49
- * decoration are two more files to keep in step with the rest.
50
- */
51
-
52
- export interface GanttChartBar {
53
- // Where the bar starts and how long it is, in whole days from the axis start. Pixels are this
54
- // times `dayWidth`, and nothing here computes a date.
55
- from: number;
56
- days: number;
57
- // The status colour, as a class. A colour is never a value in this package (`lint:tokens`), so the
58
- // caller resolves it from the board's own ramp and hands the class in.
59
- className: string;
60
- /**
61
- * A bar rolled up from a subtree rather than a task's own dates.
62
- *
63
- * ⚠️ It is drawn and never dragged. One gesture on a summary bar would have to write every task
64
- * under it — several versions, several audit entries, and no way to say which of them was meant.
65
- */
66
- derived: boolean;
67
- }
68
-
69
- export interface GanttChartRow {
70
- id: string;
71
- // The left column of this row. The caller draws it: only it knows what a status, a blocked chip or
72
- // a click on a title means.
73
- sidebar: ReactNode;
74
- bar: GanttChartBar | null;
75
- }
76
-
77
- export interface GanttChartLink {
78
- id: string;
79
- // Already in the timeline's own pixel space — the caller owns the geometry (`board-gantt.ts`), so
80
- // the shape of an arrow can be asserted without a browser.
81
- path: string;
82
- blocking: boolean;
83
- }
84
-
85
- export type GanttChartEdit = "move" | "start" | "end";
86
-
87
- interface DragState {
88
- rowId: string;
89
- edit: GanttChartEdit;
90
- days: number;
91
- }
92
-
93
- export function ItemGantt({
94
- rows,
95
- links,
96
- groups,
97
- ticks,
98
- days,
99
- dayWidth,
100
- rowHeight,
101
- today,
102
- labels,
103
- onEdit,
104
- footer,
105
- }: {
106
- rows: GanttChartRow[];
107
- links: GanttChartLink[];
108
- // The two header rows, each tick measured in days so a month keeps its own width.
109
- groups: { key: string; label: string; days: number }[];
110
- ticks: { key: string; label: string; days: number }[];
111
- days: number;
112
- dayWidth: number;
113
- rowHeight: number;
114
- today: number | null;
115
- labels: {
116
- today: string;
117
- move(row: string): string;
118
- start(row: string): string;
119
- end(row: string): string;
120
- };
121
- onEdit(rowId: string, edit: GanttChartEdit, deltaDays: number): void;
122
- footer?: ReactNode;
123
- }) {
124
- const scroller = useRef<HTMLDivElement>(null);
125
- const [drag, setDrag] = useState<DragState | null>(null);
126
- const markerId = useId();
127
- const width = days * dayWidth;
128
- // ⚠️ A distance before a drag counts, the same four pixels the kanban needs and for the same
129
- // reason: without it every press on a bar is a drag of zero days, and `ganttWrite` would be asked
130
- // what a gesture nobody made is worth.
131
- const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }));
132
-
133
- // Opening on today rather than on the first day of the window, which is a month of deliberate
134
- // air. Once, on mount: a chart that scrolled itself back every time the board changed would take
135
- // the reader's place away mid-read.
136
- useEffect(() => {
137
- const node = scroller.current;
138
- if (node === null || today === null) return;
139
- node.scrollLeft = Math.max(0, today * dayWidth - node.clientWidth / 3);
140
- }, [today, dayWidth]);
141
-
142
- return (
143
- <div className="flex min-h-0 flex-1 flex-col">
144
- <DndContext
145
- sensors={sensors}
146
- onDragStart={(event) => {
147
- const [edit, rowId] = splitDragId(String(event.active.id));
148
- if (edit !== null) setDrag({ rowId, edit, days: 0 });
149
- }}
150
- // ⚠️ Only `x`. A timeline moves in one direction, and reading `y` would let a bar drift into
151
- // a row that means another task.
152
- onDragMove={(event) =>
153
- setDrag((current) =>
154
- current === null ? current : { ...current, days: Math.round(event.delta.x / dayWidth) },
155
- )
156
- }
157
- onDragCancel={() => setDrag(null)}
158
- onDragEnd={(event) => {
159
- const [edit, rowId] = splitDragId(String(event.active.id));
160
- setDrag(null);
161
- if (edit === null) return;
162
- // The day count the READER saw, recomputed from the same rounding the preview used rather
163
- // than carried out of state — so what is written and what was shown cannot come apart.
164
- onEdit(rowId, edit, Math.round(event.delta.x / dayWidth));
165
- }}
166
- >
167
- <div ref={scroller} className="min-h-0 flex-1 overflow-auto rounded-lg border">
168
- <div className="w-max min-w-full">
169
- <div className="sticky top-0 z-20 flex bg-card">
170
- <div className="sticky left-0 z-10 w-64 shrink-0 border-r border-b bg-card" />
171
- <div style={{ width }}>
172
- <div className="flex border-b">
173
- {groups.map((group) => (
174
- <div
175
- key={group.key}
176
- style={{ width: group.days * dayWidth }}
177
- className="shrink-0 border-r py-1 text-xs font-medium"
178
- >
179
- {/* ⚠️ The label sticks to the left edge of what is VISIBLE, not to the left
180
- edge of its own cell. A month is up to 31 columns wide, so scrolled into
181
- the middle of one the reader would see a ruler of day numbers with no
182
- month named anywhere — and a day number without a month is not a date.
183
- The offset clears the sidebar, which is sticky over the same coordinates.
184
-
185
- ⚠️ No `overflow-hidden` on the cell around it, and that is not a style
186
- choice. An element with a clipping overflow becomes the scrollport its
187
- sticky descendants are measured against, and that box never scrolls — so
188
- the label would sit at a fixed inset instead of following, which is worse
189
- than leaving it alone, because the inset then hides it under the sidebar.
190
- The cell still bounds it: sticky cannot leave its containing block, so the
191
- label stops at the end of its own month. */}
192
- <span className="sticky left-[16.5rem] inline-block max-w-full truncate px-2 align-top">
193
- {group.label}
194
- </span>
195
- </div>
196
- ))}
197
- </div>
198
- <div className="flex border-b">
199
- {ticks.map((tick) => (
200
- <div
201
- key={tick.key}
202
- style={{ width: tick.days * dayWidth }}
203
- className="shrink-0 truncate border-r px-1 py-1 text-center text-xs text-muted-foreground"
204
- >
205
- {tick.label}
206
- </div>
207
- ))}
208
- </div>
209
- </div>
210
- </div>
211
-
212
- <div className="flex">
213
- <div className="sticky left-0 z-10 w-64 shrink-0 border-r bg-card">
214
- {rows.map((row) => (
215
- <div
216
- key={row.id}
217
- style={{ height: rowHeight }}
218
- className="flex items-center gap-1 border-b px-2 last:border-b-0"
219
- >
220
- {row.sidebar}
221
- </div>
222
- ))}
223
- </div>
224
-
225
- <div className="relative" style={{ width, height: rows.length * rowHeight }}>
226
- {/* The day grid, drawn from the ticks so a month's own width is what is striped. */}
227
- <div className="absolute inset-0 flex">
228
- {ticks.map((tick) => (
229
- <div
230
- key={tick.key}
231
- style={{ width: tick.days * dayWidth }}
232
- className="h-full shrink-0 border-r border-border/60"
233
- />
234
- ))}
235
- </div>
236
- {rows.map((row, index) => (
237
- <div
238
- key={row.id}
239
- style={{ top: index * rowHeight, height: rowHeight }}
240
- className="absolute right-0 left-0 border-b border-border/60"
241
- />
242
- ))}
243
- {today === null ? null : (
244
- <div
245
- aria-hidden="true"
246
- style={{ left: today * dayWidth }}
247
- className="absolute top-0 bottom-0 w-px bg-primary"
248
- title={labels.today}
249
- />
250
- )}
251
-
252
- {/* ⚠️ The arrows are decoration for a screen reader and information for everybody
253
- else. What they SAY — that this task waits for that one — is in the row beside
254
- them in words, so nothing is only in the picture. */}
255
- <svg
256
- aria-hidden="true"
257
- className={cn(
258
- "pointer-events-none absolute inset-0 transition-opacity",
259
- // Stale while a bar is in the air: the bars move and these do not, and a line
260
- // still touching where a bar WAS is a claim about a schedule that no longer
261
- // holds. Faded rather than hidden, so the reader can see they will come back.
262
- drag === null ? "opacity-100" : "opacity-20",
263
- )}
264
- width={width}
265
- height={rows.length * rowHeight}
266
- >
267
- <defs>
268
- <marker
269
- id={`${markerId}-open`}
270
- viewBox="0 0 8 8"
271
- refX="7"
272
- refY="4"
273
- markerWidth="6"
274
- markerHeight="6"
275
- orient="auto-start-reverse"
276
- >
277
- <path d="M0 0 L8 4 L0 8 z" className="fill-destructive" />
278
- </marker>
279
- <marker
280
- id={`${markerId}-settled`}
281
- viewBox="0 0 8 8"
282
- refX="7"
283
- refY="4"
284
- markerWidth="6"
285
- markerHeight="6"
286
- orient="auto-start-reverse"
287
- >
288
- <path d="M0 0 L8 4 L0 8 z" className="fill-muted-foreground" />
289
- </marker>
290
- </defs>
291
- {links.map((link) => (
292
- <path
293
- key={link.id}
294
- data-slot="gantt-link"
295
- data-blocking={link.blocking}
296
- d={link.path}
297
- fill="none"
298
- strokeWidth={link.blocking ? 2 : 1}
299
- markerEnd={`url(#${markerId}-${link.blocking ? "open" : "settled"})`}
300
- className={link.blocking ? "stroke-destructive" : "stroke-muted-foreground"}
301
- />
302
- ))}
303
- </svg>
304
-
305
- {rows.map((row, index) =>
306
- row.bar === null ? null : (
307
- <Bar
308
- key={row.id}
309
- rowId={row.id}
310
- bar={row.bar}
311
- top={index * rowHeight}
312
- rowHeight={rowHeight}
313
- dayWidth={dayWidth}
314
- drag={drag?.rowId === row.id ? drag : null}
315
- labels={labels}
316
- />
317
- ),
318
- )}
319
- </div>
320
- </div>
321
- </div>
322
- </div>
323
- </DndContext>
324
- {footer}
325
- </div>
326
- );
327
- }
328
-
329
- // `move:task-1` and back. The edit is part of the draggable's id because one context holds three
330
- // kinds of grab and the difference between them is what gets written.
331
- function splitDragId(id: string): [GanttChartEdit | null, string] {
332
- const at = id.indexOf(":");
333
- const edit = id.slice(0, at);
334
- if (edit !== "move" && edit !== "start" && edit !== "end") return [null, id];
335
- return [edit, id.slice(at + 1)];
336
- }
337
-
338
- function Bar({
339
- rowId,
340
- bar,
341
- top,
342
- rowHeight,
343
- dayWidth,
344
- drag,
345
- labels,
346
- }: {
347
- rowId: string;
348
- bar: GanttChartBar;
349
- top: number;
350
- rowHeight: number;
351
- dayWidth: number;
352
- drag: DragState | null;
353
- labels: { move(row: string): string; start(row: string): string; end(row: string): string };
354
- }) {
355
- // ⚠️ The preview clamps at one day, and `ganttWrite` clamps at the same place. A bar that stopped
356
- // shrinking while the write carried on past it would save a span the reader never saw.
357
- const shift = drag === null ? 0 : drag.days;
358
- const from = drag?.edit === "move" || drag?.edit === "start" ? bar.from + shift : bar.from;
359
- const length =
360
- drag?.edit === "start"
361
- ? Math.max(1, bar.days - shift)
362
- : drag?.edit === "end"
363
- ? Math.max(1, bar.days + shift)
364
- : bar.days;
365
- // Shrinking from the left cannot push the left edge past the right one.
366
- const left = drag?.edit === "start" ? Math.min(from, bar.from + bar.days - 1) : from;
367
-
368
- return (
369
- <div
370
- data-slot="gantt-bar"
371
- data-row={rowId}
372
- data-derived={bar.derived}
373
- style={{
374
- top: top + 4,
375
- height: rowHeight - 8,
376
- left: left * dayWidth + 1,
377
- width: length * dayWidth - 2,
378
- }}
379
- className="absolute"
380
- >
381
- <div
382
- className={cn(
383
- "size-full rounded-md opacity-90",
384
- bar.className,
385
- // A summary bar reads as a bracket over the work rather than as work of its own, and it
386
- // has no grab handles at all — see `derived` above.
387
- bar.derived && "h-2 self-center rounded-sm opacity-60",
388
- )}
389
- />
390
- {bar.derived ? null : (
391
- <>
392
- <Grip
393
- id={`move:${rowId}`}
394
- edit="move"
395
- label={labels.move(rowId)}
396
- className="inset-0 cursor-grab"
397
- />
398
- {/* ⚠️ The two edge grips only exist while there is a bar left between them.
399
- They are eight pixels wide and hang a little over each end, so on a bar narrower than
400
- that they overlap each other and cover the whole of it — and since the last one drawn
401
- wins the pointer, a short bar could then only ever be RESIZED, never moved. At month
402
- scale a one-day task is four pixels, so this is the ordinary case there rather than a
403
- corner of one. Below the threshold the bar can still be dragged as a whole, and its
404
- dates are still typed in the detail panel; above it, both edges are reachable. A
405
- control that quietly does something other than what it looks like is worse than one
406
- that is not offered. */}
407
- {length * dayWidth < 24 ? null : (
408
- <>
409
- <Grip
410
- id={`start:${rowId}`}
411
- edit="start"
412
- label={labels.start(rowId)}
413
- className="top-0 bottom-0 -left-1 w-2 cursor-col-resize rounded-l bg-foreground/0 hover:bg-foreground/20"
414
- />
415
- <Grip
416
- id={`end:${rowId}`}
417
- edit="end"
418
- label={labels.end(rowId)}
419
- className="top-0 -right-1 bottom-0 w-2 cursor-col-resize rounded-r bg-foreground/0 hover:bg-foreground/20"
420
- />
421
- </>
422
- )}
423
- </>
424
- )}
425
- </div>
426
- );
427
- }
428
-
429
- /**
430
- * One place a bar can be taken hold of.
431
- *
432
- * ⚠️ Out of the tab order and hidden from a screen reader on purpose, and this is the one decision
433
- * here that needs defending rather than explaining. A row already has a tab stop — its title, which
434
- * opens the detail panel — and the panel is where a date is TYPED, with the whole keyboard contract
435
- * #286 built for it (one write when the field is left, not one per keystroke). Three more stops per
436
- * row would put four controls with four names on every task, three of which a keyboard cannot use
437
- * for anything the second one does not already do. The drag is a pointer shortcut for an edit that
438
- * has a keyboard route, which is the arrangement the kanban settled on for the same reason.
439
- */
440
- function Grip({
441
- id,
442
- edit,
443
- label,
444
- className,
445
- }: {
446
- id: string;
447
- edit: GanttChartEdit;
448
- label: string;
449
- className: string;
450
- }) {
451
- const { listeners, setNodeRef } = useDraggable({ id });
452
- return (
453
- <div
454
- ref={setNodeRef}
455
- data-slot="gantt-grip"
456
- data-edit={edit}
457
- aria-hidden="true"
458
- title={label}
459
- {...listeners}
460
- className={cn("absolute touch-none", className)}
461
- />
462
- );
463
- }