@anchrd/intel-ui 0.16.1 → 0.18.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 (38) hide show
  1. package/package.json +10 -2
  2. package/src/agent/agent-calendar/agent-calendar.tsx +27 -2
  3. package/src/agent/agent-profile/agent-profile.tsx +20 -2
  4. package/src/agent/agent.tsx +27 -2
  5. package/src/board/board-calendar/board-calendar.tsx +89 -0
  6. package/src/board/board-card/board-card.tsx +106 -0
  7. package/src/board/board-data/board-data.ts +241 -0
  8. package/src/board/board-data/board-data.types.ts +63 -0
  9. package/src/board/board-detail/board-detail.tsx +629 -0
  10. package/src/board/board-gantt/board-gantt.ts +545 -0
  11. package/src/board/board-gantt/board-gantt.tsx +286 -0
  12. package/src/board/board-graph/board-graph.ts +174 -0
  13. package/src/board/board-graph/board-graph.tsx +168 -0
  14. package/src/board/board-items/board-items.ts +183 -0
  15. package/src/board/board-kanban/board-kanban.ts +137 -0
  16. package/src/board/board-kanban/board-kanban.tsx +257 -0
  17. package/src/board/board-status/board-status.ts +59 -0
  18. package/src/board/board-statuses/board-statuses.ts +63 -0
  19. package/src/board/board-statuses/board-statuses.tsx +228 -0
  20. package/src/board/board-table/board-table.ts +33 -0
  21. package/src/board/board-table/board-table.tsx +413 -0
  22. package/src/board/board-views/board-views.tsx +68 -0
  23. package/src/board/board-views/board-views.types.ts +29 -0
  24. package/src/board/board.tsx +251 -0
  25. package/src/components/ui/dropdown-menu.tsx +25 -0
  26. package/src/components/ui/item-calendar.tsx +181 -0
  27. package/src/components/ui/item-gantt.tsx +463 -0
  28. package/src/components/ui/kanban.tsx +282 -0
  29. package/src/components/ui/switch.tsx +25 -0
  30. package/src/data/intel-data-provider/intel-data-provider.ts +52 -0
  31. package/src/data/intel-data-provider/intel-data-provider.types.ts +31 -0
  32. package/src/i18n/de.json +90 -1
  33. package/src/i18n/en.json +90 -1
  34. package/src/i18n/es.json +90 -1
  35. package/src/node-table/node-table.tsx +3 -2
  36. package/src/nodes/nodes.tsx +16 -0
  37. package/src/resource-menu/resource-menu.tsx +26 -6
  38. package/src/styles.css +33 -0
@@ -0,0 +1,282 @@
1
+ import {
2
+ type Announcements,
3
+ closestCorners,
4
+ DndContext,
5
+ type DraggableAttributes,
6
+ DragOverlay,
7
+ type DragStartEvent,
8
+ KeyboardSensor,
9
+ PointerSensor,
10
+ useDroppable,
11
+ useSensor,
12
+ useSensors,
13
+ } from "@dnd-kit/core";
14
+ import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
15
+ import { CSS } from "@dnd-kit/utilities";
16
+ import { GripVertical } from "lucide-react";
17
+ import { type ReactNode, useState } from "react";
18
+ import { createPortal } from "react-dom";
19
+ import { cn } from "@/lib/utils";
20
+
21
+ /**
22
+ * A kanban board on dnd-kit, from the Kibo UI registry (MIT) and changed where it had to be.
23
+ *
24
+ * Registry code is our code, and four things about the original do not survive contact with a board
25
+ * whose truth is on a server:
26
+ *
27
+ * ⚠️ **It reported a drop as a new ARRAY, and it reported one twice.** The original called
28
+ * `onDataChange` in `onDragOver` for a cross-column move and again in `onDragEnd`, and both times
29
+ * with a reordered copy of the whole list. Against a server that is two writes for one drag — and
30
+ * the second one describes a state the first already changed. This version says nothing while the
31
+ * card is in the air and emits ONE drop at the end, naming what was dropped and what it was dropped
32
+ * on. What that means is the caller's to decide (`board-kanban.ts`), which is also where the single
33
+ * `board_task_move` is sent from.
34
+ *
35
+ * ⚠️ **It mutated its own props** — `newData[activeIndex].column = overColumn` writes into the
36
+ * object the caller passed in. With TanStack Query holding that object, a drag edited the cache in
37
+ * place and no re-render followed.
38
+ *
39
+ * ⚠️ **`tunnel-rat` is gone.** It existed to render the dragged card into the overlay from inside
40
+ * the card component; `DragOverlay` already does that, and the caller draws the overlay itself.
41
+ * One dependency fewer for something React has a portal for.
42
+ *
43
+ * ⚠️ **The screen reader announcements are handed in.** The originals are English string literals
44
+ * in the component; Intel speaks three languages and its text comes from `useI18n()`.
45
+ *
46
+ * ⚠️ **The card is not itself a `role="button"`, and it holds exactly ONE tab stop.** The original
47
+ * spread `useSortable().attributes` onto the card, which contributes `role="button"` and
48
+ * `tabIndex={0}` — and a card that also has to be OPENED then holds a real button inside that one:
49
+ * two tab stops computing the same accessible name from the same contents, and an Enter that means
50
+ * "start dragging" outside and "open this" inside (anchrd/intel#286). This version keeps the
51
+ * `listeners` on the card so a pointer still drags it from anywhere, and hands the ACTIVATOR to the
52
+ * caller's own control instead of minting a second button beside it (anchrd/intel#315). What
53
+ * separates the two meanings is then the key, not the element: see `KanbanProvider`.
54
+ */
55
+
56
+ export interface KanbanColumn {
57
+ id: string;
58
+ name: string;
59
+ }
60
+
61
+ export interface KanbanDrop {
62
+ activeId: string;
63
+ // The column or the card it was let go over. `null` means it was let go over nothing — a drag
64
+ // that ended outside the board, which is a cancel and not a move.
65
+ overId: string | null;
66
+ }
67
+
68
+ export function KanbanBoard({
69
+ id,
70
+ droppable = true,
71
+ children,
72
+ className,
73
+ }: {
74
+ id: string;
75
+ // A column that exists to be READ but must not be written into. It still draws and still holds
76
+ // cards; it simply never becomes a drop target, so nothing can be dropped where the answer would
77
+ // be a refusal (`board-kanban.tsx`).
78
+ droppable?: boolean;
79
+ children: ReactNode;
80
+ className?: string;
81
+ }) {
82
+ const { isOver, setNodeRef } = useDroppable({ id, disabled: !droppable });
83
+ return (
84
+ <div
85
+ ref={setNodeRef}
86
+ data-slot="kanban-column"
87
+ data-droppable={droppable}
88
+ className={cn(
89
+ "flex size-full min-h-40 flex-col divide-y overflow-hidden rounded-lg border bg-muted/40 text-xs ring-2 transition-colors",
90
+ isOver ? "ring-ring" : "ring-transparent",
91
+ className,
92
+ )}
93
+ >
94
+ {children}
95
+ </div>
96
+ );
97
+ }
98
+
99
+ export function KanbanHeader({ children }: { children: ReactNode }) {
100
+ return <div className="flex items-center gap-2 p-2 text-sm font-semibold">{children}</div>;
101
+ }
102
+
103
+ export function KanbanCards({ id, children }: { id: string; children: ReactNode }) {
104
+ return (
105
+ <div
106
+ // The column scrolls, not the page: four lanes of different lengths must not make the board
107
+ // as tall as its longest one.
108
+ className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-2"
109
+ data-slot="kanban-cards"
110
+ data-column={id}
111
+ >
112
+ {children}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ /**
118
+ * What a card hands to the one control inside it: the activator ref and dnd-kit's ARIA attributes.
119
+ *
120
+ * ⚠️ It is spread onto a control the CALLER renders, which is the whole point — the alternative is
121
+ * a handle of the primitive's own beside it, and that is the second tab stop anchrd/intel#315 is
122
+ * about. `role` and `tabIndex` are in here and are what a native `<button>` already is; what is not
123
+ * already there is `aria-roledescription` and the `aria-describedby` pointing at dnd-kit's keyboard
124
+ * instructions, and those are the half a reader needs to be told the control can also be picked up.
125
+ */
126
+ export type KanbanCardControl = DraggableAttributes & {
127
+ ref: (node: HTMLElement | null) => void;
128
+ };
129
+
130
+ export function KanbanCard({
131
+ id,
132
+ roleDescription,
133
+ children,
134
+ className,
135
+ }: {
136
+ id: string;
137
+ // What a reader is told this card IS, past its accessible name — dnd-kit's `aria-roledescription`,
138
+ // whose own default is the English literal "draggable". Handed in because text comes from
139
+ // `useI18n()` and only the caller has it.
140
+ roleDescription: string;
141
+ // A function, not a node: the one control in a card has to receive `control`, and a primitive that
142
+ // rendered its own control instead would be back to two tab stops.
143
+ children: (control: KanbanCardControl) => ReactNode;
144
+ className?: string;
145
+ }) {
146
+ const {
147
+ attributes,
148
+ listeners,
149
+ setNodeRef,
150
+ setActivatorNodeRef,
151
+ transition,
152
+ transform,
153
+ isDragging,
154
+ } = useSortable({ id, attributes: { roleDescription } });
155
+ return (
156
+ <div
157
+ ref={setNodeRef}
158
+ style={{ transition, transform: CSS.Transform.toString(transform) }}
159
+ // ⚠️ `listeners` here, `attributes` NOT. The pointer sensor has to see a press anywhere on the
160
+ // card — a kanban whose cards can only be dragged by a grip is a kanban nobody drags — but
161
+ // `attributes` carries `role="button"` and `tabIndex`, and those on the card would wrap the
162
+ // control that OPENS it in a second button.
163
+ //
164
+ // ⚠️ Handing the activator down only holds because something claims to BE it. dnd-kit's
165
+ // `KeyboardSensor` opens with `if (activator && event.target !== activator) return false` — a
166
+ // guard that does not exist until `setActivatorNodeRef` has been given a node. Unclaimed, the
167
+ // keyboard sensor accepts a key press from anywhere under these listeners, which is how a card
168
+ // ended up impossible to open by keyboard (anchrd/intel#286). The pointer drag is unaffected
169
+ // either way: `PointerSensor` never consults the activator node.
170
+ {...listeners}
171
+ data-slot="kanban-card"
172
+ className={cn(
173
+ "flex cursor-grab items-start gap-1 rounded-lg border bg-card p-3 text-left shadow-sm",
174
+ // Left where it was, faded, rather than removed: a lane that reflows while a card is over
175
+ // it moves the drop target out from under the pointer.
176
+ isDragging && "opacity-40",
177
+ className,
178
+ )}
179
+ >
180
+ {/* Decoration, and deliberately not a control: it says "this can be taken hold of" to whoever
181
+ uses a pointer, and a pointer can take hold of the whole card anyway. As a button it was a
182
+ tab stop of its own on every card in the lane. */}
183
+ <GripVertical aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
184
+ <div className="min-w-0 flex-1">{children({ ...attributes, ref: setActivatorNodeRef })}</div>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ /**
190
+ * ⚠️ Space picks a card up, Enter does not — and that is the whole of anchrd/intel#315.
191
+ *
192
+ * dnd-kit's default is `start: ['Space', 'Enter']`, which forces the activator to be an element of
193
+ * its own: a control that is also the card's "open" button cannot answer both keys with two
194
+ * different things. With the start key narrowed to Space, the two meanings are told apart by the
195
+ * KEY instead of by the element, and a card costs one tab stop rather than two — fifty cards, fifty
196
+ * stops instead of a hundred.
197
+ *
198
+ * ⚠️ `start` is the ONLY list that is narrowed. `end` and `cancel` are dnd-kit's defaults spelled
199
+ * out — Space, Enter and **Tab** put a card down, Escape leaves it where it was — and dropping one
200
+ * of them here is not a smaller change than it looks. Tab is neither an end key nor a cancel key
201
+ * once it is missing from this list, and `defaultKeyboardCoordinateGetter` answers nothing for it,
202
+ * so nothing calls `preventDefault()`: the focus walks on to the next card while the first one is
203
+ * still in the air and still following the arrow keys.
204
+ *
205
+ * Enter stays an END key, and it cannot open the card while ending a move: `handleEnd` calls
206
+ * `preventDefault()` before the browser turns the key press into a click. Somebody holding a card
207
+ * who presses the key they associate with "yes" should put it down, not be told nothing happened.
208
+ */
209
+ const kanbanKeyboardCodes = {
210
+ start: ["Space"],
211
+ cancel: ["Escape"],
212
+ end: ["Space", "Enter", "Tab"],
213
+ };
214
+
215
+ export function KanbanProvider({
216
+ columns,
217
+ itemsByColumn,
218
+ children,
219
+ announcements,
220
+ instructions,
221
+ overlay,
222
+ onDrop,
223
+ className,
224
+ }: {
225
+ columns: KanbanColumn[];
226
+ // Which cards stand in which column, in the order they are drawn. The provider needs it for the
227
+ // sortable contexts and asks for it rather than deriving it, so the caller stays the only place
228
+ // that knows how a board is ordered.
229
+ itemsByColumn: Map<string, string[]>;
230
+ children: (column: KanbanColumn) => ReactNode;
231
+ announcements: Announcements;
232
+ // ⚠️ Not optional, and not a nicety. dnd-kit's built-in text is an English literal naming the
233
+ // default keys, and this board answers different ones. A card whose `aria-describedby` says
234
+ // "press space or enter" while Enter opens it describes a widget that is not there.
235
+ instructions: string;
236
+ overlay(activeId: string): ReactNode;
237
+ onDrop(drop: KanbanDrop): void;
238
+ className?: string;
239
+ }) {
240
+ const [activeId, setActiveId] = useState<string | null>(null);
241
+ const sensors = useSensors(
242
+ // ⚠️ A distance before a drag starts, and it is not decoration: without it every click on a
243
+ // card is a drag of zero pixels, and the card can then no longer be opened by clicking it.
244
+ useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
245
+ useSensor(KeyboardSensor, { keyboardCodes: kanbanKeyboardCodes }),
246
+ );
247
+
248
+ return (
249
+ <DndContext
250
+ accessibility={{ announcements, screenReaderInstructions: { draggable: instructions } }}
251
+ // Corners rather than centres: a tall card over a short one wins on centre distance even
252
+ // when the pointer is nowhere near it.
253
+ collisionDetection={closestCorners}
254
+ sensors={sensors}
255
+ onDragStart={(event: DragStartEvent) => setActiveId(String(event.active.id))}
256
+ onDragCancel={() => setActiveId(null)}
257
+ onDragEnd={(event) => {
258
+ setActiveId(null);
259
+ const over = event.over === null ? null : String(event.over.id);
260
+ onDrop({ activeId: String(event.active.id), overId: over });
261
+ }}
262
+ >
263
+ <div className={cn("grid size-full auto-cols-fr grid-flow-col gap-3", className)}>
264
+ {columns.map((column) => (
265
+ <SortableContext
266
+ key={column.id}
267
+ items={itemsByColumn.get(column.id) ?? []}
268
+ strategy={verticalListSortingStrategy}
269
+ >
270
+ {children(column)}
271
+ </SortableContext>
272
+ ))}
273
+ </div>
274
+ {typeof document === "undefined"
275
+ ? null
276
+ : createPortal(
277
+ <DragOverlay>{activeId === null ? null : overlay(activeId)}</DragOverlay>,
278
+ document.body,
279
+ )}
280
+ </DndContext>
281
+ );
282
+ }
@@ -0,0 +1,25 @@
1
+ import { Switch as SwitchPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
6
+ return (
7
+ <SwitchPrimitive.Root
8
+ data-slot="switch"
9
+ className={cn(
10
+ "peer inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
11
+ className,
12
+ )}
13
+ {...props}
14
+ >
15
+ <SwitchPrimitive.Thumb
16
+ data-slot="switch-thumb"
17
+ className={cn(
18
+ "pointer-events-none block size-4 rounded-full bg-background ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
19
+ )}
20
+ />
21
+ </SwitchPrimitive.Root>
22
+ );
23
+ }
24
+
25
+ export { Switch };
@@ -1,17 +1,23 @@
1
1
  import {
2
+ AddBoardTaskInput,
2
3
  AgentCosts,
3
4
  AgentKeyRotated,
4
5
  AppendTableRowsInput,
5
6
  AppendTableRowsResult,
6
7
  ArchiveFlowInput,
7
8
  ArchiveNodeInput,
9
+ BoardTaskResult,
8
10
  BundleImportResult,
9
11
  CompleteFlowRunStepInput,
12
+ ConfigureBoardInput,
13
+ ConfigureBoardResult,
10
14
  CreateAgentInput,
11
15
  CreatedAgent,
12
16
  CreateFlowInput,
13
17
  CreateNodeInput,
14
18
  DefineTableInput,
19
+ DeleteBoardTaskInput,
20
+ DeleteBoardTaskResult,
15
21
  Flow,
16
22
  FlowDocument,
17
23
  FlowList,
@@ -26,8 +32,10 @@ import {
26
32
  ListFlowsInput,
27
33
  ListNodesInput,
28
34
  ModelCatalog,
35
+ MoveBoardTaskInput,
29
36
  Node,
30
37
  NodeAgent,
38
+ NodeBoard,
31
39
  NodeDocument,
32
40
  NodeGraph,
33
41
  NodeLinkList,
@@ -56,6 +64,7 @@ import {
56
64
  StartFlowRunInput,
57
65
  ToolCatalog,
58
66
  ToolServerCatalog,
67
+ UpdateBoardTaskInput,
59
68
  UpdateFlowInput,
60
69
  UpdateNodeInput,
61
70
  } from "@anchrd/intel-contract";
@@ -327,6 +336,49 @@ export function createIntelDataProvider(
327
336
  { method: "POST", body: JSON.stringify(parsed) },
328
337
  );
329
338
  },
339
+ async getBoard(nodeId) {
340
+ return await request(`/nodes/${encodeURIComponent(nodeId)}/board`, NodeBoard);
341
+ },
342
+ async configureBoard(input) {
343
+ const parsed = ConfigureBoardInput.parse(input);
344
+ return await request(
345
+ `/nodes/${encodeURIComponent(parsed.nodeId)}/board/configure`,
346
+ ConfigureBoardResult,
347
+ { method: "POST", body: JSON.stringify(parsed) },
348
+ );
349
+ },
350
+ async addBoardTask(input) {
351
+ const parsed = AddBoardTaskInput.parse(input);
352
+ return await request(
353
+ `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks`,
354
+ BoardTaskResult,
355
+ { method: "POST", body: JSON.stringify(parsed) },
356
+ );
357
+ },
358
+ async updateBoardTask(input) {
359
+ const parsed = UpdateBoardTaskInput.parse(input);
360
+ return await request(
361
+ `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/update`,
362
+ BoardTaskResult,
363
+ { method: "POST", body: JSON.stringify(parsed) },
364
+ );
365
+ },
366
+ async moveBoardTask(input) {
367
+ const parsed = MoveBoardTaskInput.parse(input);
368
+ return await request(
369
+ `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/move`,
370
+ BoardTaskResult,
371
+ { method: "POST", body: JSON.stringify(parsed) },
372
+ );
373
+ },
374
+ async deleteBoardTask(input) {
375
+ const parsed = DeleteBoardTaskInput.parse(input);
376
+ return await request(
377
+ `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/delete`,
378
+ DeleteBoardTaskResult,
379
+ { method: "POST", body: JSON.stringify(parsed) },
380
+ );
381
+ },
330
382
  async listNodeVersions(nodeId) {
331
383
  return await request(`/nodes/${encodeURIComponent(nodeId)}/versions`, NodeVersionList);
332
384
  },
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ AddBoardTaskInput,
2
3
  AgentCosts,
3
4
  AgentKeyRotated,
4
5
  AgentScheduleTarget,
@@ -6,13 +7,18 @@ import type {
6
7
  AppendTableRowsResult,
7
8
  ArchiveFlowInput,
8
9
  ArchiveNodeInput,
10
+ BoardTaskResult,
9
11
  BundleImportResult,
10
12
  CompleteFlowRunStepInput,
13
+ ConfigureBoardInput,
14
+ ConfigureBoardResult,
11
15
  CreateAgentInput,
12
16
  CreatedAgent,
13
17
  CreateFlowInput,
14
18
  CreateNodeInput,
15
19
  DefineTableInput,
20
+ DeleteBoardTaskInput,
21
+ DeleteBoardTaskResult,
16
22
  Flow,
17
23
  FlowDocument,
18
24
  FlowList,
@@ -27,8 +33,10 @@ import type {
27
33
  ListFlowsInput,
28
34
  ListNodesInput,
29
35
  ModelCatalog,
36
+ MoveBoardTaskInput,
30
37
  Node,
31
38
  NodeAgent,
39
+ NodeBoard,
32
40
  NodeDocument,
33
41
  NodeGraph,
34
42
  NodeKind,
@@ -57,6 +65,7 @@ import type {
57
65
  StartFlowRunInput,
58
66
  ToolCatalog,
59
67
  ToolServerCatalog,
68
+ UpdateBoardTaskInput,
60
69
  UpdateFlowInput,
61
70
  UpdateNodeInput,
62
71
  } from "@anchrd/intel-contract";
@@ -127,6 +136,28 @@ export interface IntelDataProvider {
127
136
  getNodeTable(nodeId: string): Promise<NodeTable>;
128
137
  defineTable(input: DefineTableInput): Promise<NodeTable>;
129
138
  appendTableRows(input: AppendTableRowsInput): Promise<AppendTableRowsResult>;
139
+
140
+ // A board's whole document — the status list and every task — as one read (#285). There is no
141
+ // per-task GET and there is not meant to be: the file IS the board, and every view of it
142
+ // (anchrd/intel#286) draws the same list.
143
+ getBoard(nodeId: string): Promise<NodeBoard>;
144
+ // The status list, written whole and in the order it should be drawn. Adding, renaming and
145
+ // reordering are all this one call.
146
+ configureBoard(input: ConfigureBoardInput): Promise<ConfigureBoardResult>;
147
+ // ⚠️ The five task operations answer with the ONE task that was written, never the whole board.
148
+ // A board holds up to five thousand tasks, and a one-card edit that answered with all of them
149
+ // would make every write pay for a full read — so the cache is patched from the answer here
150
+ // (`board-data.ts`) rather than refetched.
151
+ addBoardTask(input: AddBoardTaskInput): Promise<BoardTaskResult>;
152
+ updateBoardTask(input: UpdateBoardTaskInput): Promise<BoardTaskResult>;
153
+ // Where a task sits: its column, its parent, its place among its neighbours. ⚠️ A move names
154
+ // its NEIGHBOURS and never an index — the server mints the order key, so one drag writes one
155
+ // task and renumbers nothing.
156
+ moveBoardTask(input: MoveBoardTaskInput): Promise<BoardTaskResult>;
157
+ // ⚠️ Deleting cascades to every descendant and `deleted` counts them, so a surface can say what
158
+ // it is about to do before it does it rather than afterwards.
159
+ deleteBoardTask(input: DeleteBoardTaskInput): Promise<DeleteBoardTaskResult>;
160
+
130
161
  // Walks the browser through the portal's OAuth flow without asking anybody anything: Gate is the
131
162
  // identity provider Cloudflare Access consumes, so a signed-in person is already known there
132
163
  // (#60). It navigates away — the caller renders no button for it and gets no answer back.
package/src/i18n/de.json CHANGED
@@ -373,6 +373,7 @@
373
373
  "agent.calendarZones": "Jede Zeit steht in der Zeitzone ihres eigenen Zeitplans, denn dann läuft der Agent. Weicht deine eigene Zeitzone ab, steht sie daneben.",
374
374
  "agent.calendarYourTime": "{when} bei dir",
375
375
  "agent.calendarUnreadable": "Keiner der Zeitpläne dieses Agenten lässt sich als Cron-Ausdruck lesen, es ist also nichts geplant.",
376
+ "agent.calendarPaused": "Dieser Agent ist pausiert, keiner dieser Läufe findet statt. Sie zeigen, was er täte, sobald er fortgesetzt wird.",
376
377
  "agent.log": "Läufe",
377
378
  "agent.logEmpty": "Dieser Agent ist noch nicht gelaufen.",
378
379
  "agent.logFailed": "Die Läufe konnten nicht gelesen werden. Vielleicht ist die Agenten-Laufzeit von hier aus nicht erreichbar.",
@@ -424,5 +425,93 @@
424
425
  "common.loading": "Wird geladen …",
425
426
  "common.retry": "Erneut versuchen",
426
427
  "common.close": "Schließen",
427
- "common.unavailable": "Intel ist derzeit nicht verfügbar."
428
+ "common.unavailable": "Intel ist derzeit nicht verfügbar.",
429
+ "board.summary": "{tasks} Aufgaben in {statuses} Status",
430
+ "board.views": "Ansichten dieses Boards",
431
+ "board.view.kanban": "Board",
432
+ "board.view.table": "Tabelle",
433
+ "board.view.calendar": "Kalender",
434
+ "board.view.graph": "Graph",
435
+ "board.view.gantt": "Gantt",
436
+ "board.statuses": "Status",
437
+ "board.statusLabel": "Statusname",
438
+ "board.statusUp": "Status nach oben",
439
+ "board.statusDown": "Status nach unten",
440
+ "board.statusRemove": "Status {label} entfernen",
441
+ "board.addStatus": "Neuer Status",
442
+ "board.shelfFixed": "Immer der letzte",
443
+ "board.showArchived": "Archivierte zeigen",
444
+ "board.addTask": "Neue Aufgabe",
445
+ "board.empty": "Auf diesem Board steht noch nichts. Lege oben die erste Aufgabe an.",
446
+ "board.noTasks": "Hier passt nichts.",
447
+ "board.laneEmpty": "Leer",
448
+ "board.archivedEmpty": "Nichts archiviert.",
449
+ "board.blocked": "Blockiert",
450
+ "board.open": "Offen",
451
+ "board.subtaskCount": "{done}/{total} Teilaufgaben",
452
+ "board.unassigned": "Nicht zugewiesen",
453
+ "board.assignee.me": "Ich",
454
+ "board.assignee.unnamed": "Zugewiesen",
455
+ "board.unknownTask": "Aufgabe für dich nicht verfügbar",
456
+ "board.column.title": "Titel",
457
+ "board.column.status": "Status",
458
+ "board.column.assignee": "Zuständig",
459
+ "board.column.startDate": "Startdatum",
460
+ "board.column.dueDate": "Fällig am",
461
+ "board.column.labels": "Labels",
462
+ "board.columns": "Spalten",
463
+ "board.filterTitle": "Nach Titel filtern",
464
+ "board.groupBy": "Gruppieren nach",
465
+ "board.groupBy.none": "Nichts",
466
+ "board.groupBy.status": "Status",
467
+ "board.groupBy.assignee": "Zuständig",
468
+ "board.toggleSubtasks": "Teilaufgaben von {task} ein- oder ausblenden",
469
+ "board.calendar.previous": "Vorheriger Monat",
470
+ "board.calendar.next": "Nächster Monat",
471
+ "board.calendar.today": "Heute",
472
+ "board.calendar.more": "+{count} weitere",
473
+ "board.calendar.undated": "{count} ohne Fälligkeitsdatum — nicht im Raster, aber auf dem Board und in der Tabelle.",
474
+ "board.gantt.scale": "Maßstab",
475
+ "board.gantt.scale.days": "Tage",
476
+ "board.gantt.scale.weeks": "Wochen",
477
+ "board.gantt.scale.months": "Monate",
478
+ "board.gantt.today": "Heute",
479
+ "board.gantt.move": "{task} verschieben",
480
+ "board.gantt.resizeStart": "Beginn von {task} ändern",
481
+ "board.gantt.resizeEnd": "Fälligkeit von {task} ändern",
482
+ "board.gantt.spanLabel": "{start} bis {end}",
483
+ "board.gantt.waitsFor": "wartet auf {tasks}",
484
+ "board.gantt.hiddenLinks": "{count} Abhängigkeiten sind nicht gezeichnet: die erwartete Aufgabe hat hier keinen Balken.",
485
+ "board.gantt.undated": "Ohne Datum",
486
+ "board.gantt.undatedCount": "{count} ohne Datum — auf dem Board, aber auf keiner Zeitachse.",
487
+ "board.gantt.nothingDated": "Nichts auf diesem Board hat ein Datum. Gib einer Aufgabe ein Fälligkeitsdatum, dann erscheint sie hier.",
488
+ "board.graph.subtree": "Teilbaum",
489
+ "board.graph.wholeBoard": "Ganzes Board",
490
+ "board.graph.parentEdge": "enthält",
491
+ "board.graph.dependsEdge": "wartet auf",
492
+ "board.detail": "Aufgabe",
493
+ "board.description": "Beschreibung",
494
+ "board.subtasks": "Teilaufgaben",
495
+ "board.addSubtask": "Neue Teilaufgabe",
496
+ "board.dependencies": "Wartet auf",
497
+ "board.addDependency": "Abhängigkeit hinzufügen",
498
+ "board.removeDependency": "Diese Abhängigkeit entfernen",
499
+ "board.references": "Referenzen",
500
+ "board.addReference": "Referenz hinzufügen",
501
+ "board.removeReference": "Diese Referenz entfernen",
502
+ "board.addLabel": "Neues Label",
503
+ "board.removeLabel": "Label {label} entfernen",
504
+ "board.deleteTask": "Aufgabe löschen",
505
+ "board.deleteConfirm": "„{task}“ zu löschen entfernt die Aufgabe und alles darunter: insgesamt {count}. Abhängigkeiten darauf werden entfernt.",
506
+ "board.deleted": "Gelöscht: {count}.",
507
+ "board.archivedHint": "Diese Aufgabe liegt im Archiv. Ein Wechsel in einen anderen Status holt sie zurück.",
508
+ "board.dnd.start": "{task} aufgenommen",
509
+ "board.dnd.over": "{task} über {lane}",
510
+ "board.dnd.end": "{task} in {lane} abgelegt",
511
+ "board.dnd.endUnchanged": "{task} wurde nicht verschoben und steht weiterhin in {lane}",
512
+ "board.dnd.endRefused": "{lane} nimmt keine Karten mehr auf, {task} wurde nicht verschoben und steht weiterhin in {from}",
513
+ "board.dnd.cancel": "Ablegen von {task} abgebrochen",
514
+ "board.dnd.instructions": "Mit der Eingabetaste diese Aufgabe öffnen. Zum Verschieben stattdessen die Leertaste darauf drücken und sie mit den Pfeiltasten an eine andere Position oder in eine andere Spalte bringen, dann mit Leertaste, Eingabetaste oder Tabulator ablegen oder mit Escape dort lassen, wo sie war.",
515
+ "board.card.roleDescription": "Aufgabe, verschiebbar",
516
+ "board.statusTerminal": "Gilt als erledigt"
428
517
  }