@tangle-network/ui 11.30.2 → 11.31.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.
- package/CHANGELOG.md +11 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +7 -2
- package/dist/markdown/code-block.d.ts +11 -4
- package/dist/markdown/code-block.js +239 -107
- package/dist/markdown/code-meta.d.ts +11 -0
- package/dist/markdown/code-meta.js +98 -0
- package/dist/markdown/markdown-table.d.ts +8 -0
- package/dist/markdown/markdown-table.js +168 -0
- package/dist/markdown/markdown.d.ts +17 -4
- package/dist/markdown/markdown.js +96 -34
- package/dist/markdown.d.ts +5 -3
- package/dist/markdown.js +4 -2
- package/dist/run/approval-card.d.ts +46 -0
- package/dist/run/approval-card.js +162 -0
- package/dist/run/run-phase-list.d.ts +18 -0
- package/dist/run/run-phase-list.js +56 -0
- package/dist/run/task-list.d.ts +33 -0
- package/dist/run/task-list.js +138 -0
- package/dist/run.d.ts +4 -1
- package/dist/run.js +4 -1
- package/package.json +3 -4
- package/src/markdown/code-block.test.tsx +35 -26
- package/src/markdown/code-block.tsx +197 -110
- package/src/markdown/code-meta.ts +68 -0
- package/src/markdown/index.ts +10 -2
- package/src/markdown/markdown-table.test.tsx +61 -0
- package/src/markdown/markdown-table.tsx +191 -0
- package/src/markdown/markdown.test.tsx +27 -0
- package/src/markdown/markdown.tsx +126 -48
- package/src/run/approval-card.test.tsx +41 -0
- package/src/run/approval-card.tsx +146 -0
- package/src/run/index.ts +17 -0
- package/src/run/run-phase-list.test.tsx +25 -0
- package/src/run/run-phase-list.tsx +56 -0
- package/src/run/task-list.test.tsx +35 -0
- package/src/run/task-list.tsx +117 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, ChevronRight, Circle, FileMinus, FilePen, FilePlus } from "lucide-react";
|
|
4
|
+
//#region src/run/approval-card.tsx
|
|
5
|
+
const CHANGE_ICON = {
|
|
6
|
+
added: FilePlus,
|
|
7
|
+
modified: FilePen,
|
|
8
|
+
deleted: FileMinus
|
|
9
|
+
};
|
|
10
|
+
function count(value, sign) {
|
|
11
|
+
return value === void 0 ? `${sign}–` : `${sign}${value}`;
|
|
12
|
+
}
|
|
13
|
+
/** Files and +/− line counts for a write, with totals when every count is known. */
|
|
14
|
+
function ApprovalDiffSummary({ files, note }) {
|
|
15
|
+
const known = files.every((file) => file.additions !== void 0 && file.deletions !== void 0);
|
|
16
|
+
const additions = files.reduce((sum, file) => sum + (file.additions ?? 0), 0);
|
|
17
|
+
const deletions = files.reduce((sum, file) => sum + (file.deletions ?? 0), 0);
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
19
|
+
className: "rounded-lg border border-border",
|
|
20
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
21
|
+
className: "flex flex-wrap items-center justify-between gap-2 border-b border-border px-3 py-2 text-sm",
|
|
22
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
23
|
+
className: "font-medium text-foreground",
|
|
24
|
+
children: files.length === 1 ? "1 file" : `${files.length} files`
|
|
25
|
+
}), known ? /* @__PURE__ */ jsxs("span", {
|
|
26
|
+
className: "tabular-nums",
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ jsxs("span", {
|
|
29
|
+
className: "text-[var(--surface-success-text)]",
|
|
30
|
+
children: ["+", additions]
|
|
31
|
+
}),
|
|
32
|
+
" ",
|
|
33
|
+
/* @__PURE__ */ jsxs("span", {
|
|
34
|
+
className: "text-[var(--surface-danger-text)]",
|
|
35
|
+
children: ["−", deletions]
|
|
36
|
+
})
|
|
37
|
+
]
|
|
38
|
+
}) : note && /* @__PURE__ */ jsx("span", {
|
|
39
|
+
className: "text-muted-foreground",
|
|
40
|
+
children: note
|
|
41
|
+
})]
|
|
42
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
43
|
+
className: "divide-y divide-border/70",
|
|
44
|
+
children: files.map((file) => {
|
|
45
|
+
const Icon = CHANGE_ICON[file.change ?? "modified"];
|
|
46
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
47
|
+
className: "flex items-center justify-between gap-3 px-3 py-2 text-sm",
|
|
48
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
49
|
+
className: "flex min-w-0 items-center gap-2",
|
|
50
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
51
|
+
"aria-hidden": true,
|
|
52
|
+
className: "size-4 shrink-0 text-muted-foreground"
|
|
53
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
54
|
+
className: "min-w-0 truncate font-mono text-foreground",
|
|
55
|
+
title: file.path,
|
|
56
|
+
children: file.path
|
|
57
|
+
})]
|
|
58
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
59
|
+
className: "shrink-0 tabular-nums",
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ jsx("span", {
|
|
62
|
+
className: "text-[var(--surface-success-text)]",
|
|
63
|
+
children: count(file.additions, "+")
|
|
64
|
+
}),
|
|
65
|
+
" ",
|
|
66
|
+
/* @__PURE__ */ jsx("span", {
|
|
67
|
+
className: "text-[var(--surface-danger-text)]",
|
|
68
|
+
children: count(file.deletions, "−")
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
})]
|
|
72
|
+
}, file.path);
|
|
73
|
+
})
|
|
74
|
+
})]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function PlanMarker({ state }) {
|
|
78
|
+
if (state === "done") return /* @__PURE__ */ jsx(Check, {
|
|
79
|
+
"aria-hidden": true,
|
|
80
|
+
className: "size-4 shrink-0 text-[var(--surface-success-text)]"
|
|
81
|
+
});
|
|
82
|
+
if (state === "current") return /* @__PURE__ */ jsx(ChevronRight, {
|
|
83
|
+
"aria-hidden": true,
|
|
84
|
+
className: "size-4 shrink-0 text-foreground"
|
|
85
|
+
});
|
|
86
|
+
return /* @__PURE__ */ jsx(Circle, {
|
|
87
|
+
"aria-hidden": true,
|
|
88
|
+
className: "size-3.5 shrink-0 text-muted-foreground"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A request for a person to approve one action: what it does in plain words,
|
|
93
|
+
* where, its step in a plan, the files it writes, and the raw request behind
|
|
94
|
+
* a Details toggle. The caller owns the decision and supplies the actions.
|
|
95
|
+
*/
|
|
96
|
+
function ApprovalCard({ title, context, step, plan, diff, diffNote, details, status, actions, className, ...props }) {
|
|
97
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
98
|
+
role: "group",
|
|
99
|
+
"aria-label": "Approval request",
|
|
100
|
+
...props,
|
|
101
|
+
className: cn("space-y-3 rounded-xl border border-border bg-card p-4 shadow-sm", className),
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ jsxs("div", {
|
|
104
|
+
className: "space-y-1",
|
|
105
|
+
children: [(step || context) && /* @__PURE__ */ jsxs("p", {
|
|
106
|
+
className: "flex flex-wrap items-center gap-2 text-sm text-muted-foreground",
|
|
107
|
+
children: [step && /* @__PURE__ */ jsxs("span", {
|
|
108
|
+
className: "rounded-md bg-muted px-2 py-0.5 font-medium text-foreground",
|
|
109
|
+
children: [
|
|
110
|
+
"Step ",
|
|
111
|
+
step.index,
|
|
112
|
+
" of ",
|
|
113
|
+
step.total
|
|
114
|
+
]
|
|
115
|
+
}), context && /* @__PURE__ */ jsx("span", {
|
|
116
|
+
className: "min-w-0 truncate",
|
|
117
|
+
children: context
|
|
118
|
+
})]
|
|
119
|
+
}), /* @__PURE__ */ jsx("h3", {
|
|
120
|
+
className: "text-base font-semibold leading-6 text-foreground",
|
|
121
|
+
children: title
|
|
122
|
+
})]
|
|
123
|
+
}),
|
|
124
|
+
plan && plan.length > 0 && /* @__PURE__ */ jsx("ol", {
|
|
125
|
+
className: "space-y-1.5",
|
|
126
|
+
"aria-label": "Plan",
|
|
127
|
+
children: plan.map((item, index) => /* @__PURE__ */ jsxs("li", {
|
|
128
|
+
"aria-current": item.state === "current" ? "step" : void 0,
|
|
129
|
+
className: cn("flex items-start gap-2 text-sm leading-6", item.state === "current" ? "font-medium text-foreground" : "text-muted-foreground"),
|
|
130
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
131
|
+
className: "mt-1",
|
|
132
|
+
children: /* @__PURE__ */ jsx(PlanMarker, { state: item.state })
|
|
133
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
134
|
+
className: "min-w-0",
|
|
135
|
+
children: item.label
|
|
136
|
+
})]
|
|
137
|
+
}, index))
|
|
138
|
+
}),
|
|
139
|
+
diff && diff.length > 0 && /* @__PURE__ */ jsx(ApprovalDiffSummary, {
|
|
140
|
+
files: diff,
|
|
141
|
+
note: diffNote
|
|
142
|
+
}),
|
|
143
|
+
details !== void 0 && /* @__PURE__ */ jsxs("details", {
|
|
144
|
+
className: "group text-sm",
|
|
145
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
146
|
+
className: "cursor-pointer select-none font-medium text-muted-foreground hover:text-foreground",
|
|
147
|
+
children: "Details"
|
|
148
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
149
|
+
className: "mt-2",
|
|
150
|
+
children: details
|
|
151
|
+
})]
|
|
152
|
+
}),
|
|
153
|
+
status,
|
|
154
|
+
actions && /* @__PURE__ */ jsx("div", {
|
|
155
|
+
className: "flex flex-wrap gap-2",
|
|
156
|
+
children: actions
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { ApprovalCard, ApprovalDiffSummary };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/run/run-phase-list.d.ts
|
|
3
|
+
export interface RunPhase {
|
|
4
|
+
id: string;
|
|
5
|
+
/** The step or phase the work belongs to. */
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
/** One line on what happened, such as "Read 12 files · ran 3 commands". */
|
|
8
|
+
summary?: ReactNode;
|
|
9
|
+
status?: "running" | "done" | "error";
|
|
10
|
+
/** The phase's individual steps, shown when expanded. */
|
|
11
|
+
content: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/** A turn's tool runs grouped by step, each collapsed to one summarizing line. */
|
|
14
|
+
export declare function RunPhaseList({ phases, className }: {
|
|
15
|
+
phases: readonly RunPhase[];
|
|
16
|
+
className?: string;
|
|
17
|
+
}): import("react").JSX.Element | null;
|
|
18
|
+
//#endregion
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { focusRing } from "../lib/focus.js";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ChevronRight, CircleAlert, Loader2 } from "lucide-react";
|
|
6
|
+
//#region src/run/run-phase-list.tsx
|
|
7
|
+
function PhaseRow({ phase }) {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const id = `run-phase-${phase.id}`;
|
|
10
|
+
return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
"aria-expanded": open,
|
|
13
|
+
"aria-controls": id,
|
|
14
|
+
onClick: () => setOpen(!open),
|
|
15
|
+
className: cn("flex w-full items-start gap-2 rounded-md py-1 text-left text-sm transition-colors hover:text-foreground", focusRing),
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ jsx(ChevronRight, {
|
|
18
|
+
"aria-hidden": true,
|
|
19
|
+
className: cn("mt-1 size-4 shrink-0 text-muted-foreground transition-transform", open && "rotate-90")
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ jsxs("span", {
|
|
22
|
+
className: "min-w-0 flex-1 leading-6",
|
|
23
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
24
|
+
className: "font-medium text-foreground",
|
|
25
|
+
children: phase.title
|
|
26
|
+
}), phase.summary && /* @__PURE__ */ jsxs("span", {
|
|
27
|
+
className: "text-muted-foreground",
|
|
28
|
+
children: [" · ", phase.summary]
|
|
29
|
+
})]
|
|
30
|
+
}),
|
|
31
|
+
phase.status === "running" && /* @__PURE__ */ jsx(Loader2, {
|
|
32
|
+
"aria-label": "Running",
|
|
33
|
+
className: "mt-1 size-4 shrink-0 animate-spin text-muted-foreground"
|
|
34
|
+
}),
|
|
35
|
+
phase.status === "error" && /* @__PURE__ */ jsx(CircleAlert, {
|
|
36
|
+
"aria-label": "Had an error",
|
|
37
|
+
className: "mt-1 size-4 shrink-0 text-[var(--surface-danger-text)]"
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
}), open && /* @__PURE__ */ jsx("div", {
|
|
41
|
+
id,
|
|
42
|
+
className: "mb-2 ml-6 mt-1",
|
|
43
|
+
children: phase.content
|
|
44
|
+
})] });
|
|
45
|
+
}
|
|
46
|
+
/** A turn's tool runs grouped by step, each collapsed to one summarizing line. */
|
|
47
|
+
function RunPhaseList({ phases, className }) {
|
|
48
|
+
if (phases.length === 0) return null;
|
|
49
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
50
|
+
className: cn("space-y-0.5", className),
|
|
51
|
+
"aria-label": "Tool runs",
|
|
52
|
+
children: phases.map((phase) => /* @__PURE__ */ jsx(PhaseRow, { phase }, phase.id))
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { RunPhaseList };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/run/task-list.d.ts
|
|
3
|
+
/** The states an agent's working checklist reports; unknown values read as to-do. */
|
|
4
|
+
export type TaskStatus = "pending" | "in_progress" | "completed" | "cancelled" | "blocked" | "failed";
|
|
5
|
+
export interface TaskListItem {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: TaskStatus | (string & {});
|
|
9
|
+
priority?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TaskListProps {
|
|
13
|
+
items: readonly TaskListItem[];
|
|
14
|
+
/** Header title; "Tasks" by default. */
|
|
15
|
+
title?: ReactNode;
|
|
16
|
+
/** Trailing header content, such as a run state. */
|
|
17
|
+
meta?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function taskStatus(status: string): TaskStatus;
|
|
21
|
+
/** Priority is worth a label only when the tasks disagree about it. */
|
|
22
|
+
export declare function showsTaskPriority(items: readonly TaskListItem[]): boolean;
|
|
23
|
+
/** "2 of 3 done": cancelled tasks leave the count rather than counting as done. */
|
|
24
|
+
export declare function taskProgress(items: readonly TaskListItem[]): {
|
|
25
|
+
done: number;
|
|
26
|
+
total: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* An agent's working checklist: a header with progress, each task's state as
|
|
30
|
+
* a toned pill, and priority shown only when the tasks differ in it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function TaskList({ items, title, meta, className }: TaskListProps): import("react").JSX.Element;
|
|
33
|
+
//#endregion
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { StatusPill } from "../primitives/status-pill.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/run/task-list.tsx
|
|
5
|
+
const STATUS = {
|
|
6
|
+
pending: {
|
|
7
|
+
tone: "neutral",
|
|
8
|
+
label: "To do"
|
|
9
|
+
},
|
|
10
|
+
in_progress: {
|
|
11
|
+
tone: "running",
|
|
12
|
+
label: "In progress"
|
|
13
|
+
},
|
|
14
|
+
completed: {
|
|
15
|
+
tone: "success",
|
|
16
|
+
label: "Done"
|
|
17
|
+
},
|
|
18
|
+
cancelled: {
|
|
19
|
+
tone: "neutral",
|
|
20
|
+
label: "Cancelled"
|
|
21
|
+
},
|
|
22
|
+
blocked: {
|
|
23
|
+
tone: "warning",
|
|
24
|
+
label: "Blocked"
|
|
25
|
+
},
|
|
26
|
+
failed: {
|
|
27
|
+
tone: "danger",
|
|
28
|
+
label: "Failed"
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const STATUS_ALIASES = {
|
|
32
|
+
todo: "pending",
|
|
33
|
+
open: "pending",
|
|
34
|
+
in_progress: "in_progress",
|
|
35
|
+
"in-progress": "in_progress",
|
|
36
|
+
active: "in_progress",
|
|
37
|
+
running: "in_progress",
|
|
38
|
+
done: "completed",
|
|
39
|
+
complete: "completed",
|
|
40
|
+
completed: "completed",
|
|
41
|
+
success: "completed",
|
|
42
|
+
cancelled: "cancelled",
|
|
43
|
+
canceled: "cancelled",
|
|
44
|
+
skipped: "cancelled",
|
|
45
|
+
blocked: "blocked",
|
|
46
|
+
failed: "failed",
|
|
47
|
+
error: "failed"
|
|
48
|
+
};
|
|
49
|
+
function taskStatus(status) {
|
|
50
|
+
const key = status.trim().toLowerCase();
|
|
51
|
+
return STATUS[key] ? key : STATUS_ALIASES[key] ?? "pending";
|
|
52
|
+
}
|
|
53
|
+
/** Priority is worth a label only when the tasks disagree about it. */
|
|
54
|
+
function showsTaskPriority(items) {
|
|
55
|
+
return new Set(items.map((item) => item.priority?.trim().toLowerCase()).filter(Boolean)).size > 1;
|
|
56
|
+
}
|
|
57
|
+
/** "2 of 3 done": cancelled tasks leave the count rather than counting as done. */
|
|
58
|
+
function taskProgress(items) {
|
|
59
|
+
const counted = items.filter((item) => taskStatus(item.status) !== "cancelled");
|
|
60
|
+
return {
|
|
61
|
+
done: counted.filter((item) => taskStatus(item.status) === "completed").length,
|
|
62
|
+
total: counted.length
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function capitalized(value) {
|
|
66
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* An agent's working checklist: a header with progress, each task's state as
|
|
70
|
+
* a toned pill, and priority shown only when the tasks differ in it.
|
|
71
|
+
*/
|
|
72
|
+
function TaskList({ items, title = "Tasks", meta, className }) {
|
|
73
|
+
const showPriority = showsTaskPriority(items);
|
|
74
|
+
const { done, total } = taskProgress(items);
|
|
75
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
76
|
+
"aria-label": typeof title === "string" ? title : "Tasks",
|
|
77
|
+
className: cn("rounded-xl border border-border bg-card p-4 shadow-sm", className),
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("header", {
|
|
80
|
+
className: "mb-3 flex flex-wrap items-center justify-between gap-2",
|
|
81
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
82
|
+
className: "flex min-w-0 items-baseline gap-2",
|
|
83
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
84
|
+
className: "text-base font-semibold text-foreground",
|
|
85
|
+
children: title
|
|
86
|
+
}), total > 0 && /* @__PURE__ */ jsxs("span", {
|
|
87
|
+
className: "text-sm text-muted-foreground",
|
|
88
|
+
children: [
|
|
89
|
+
done,
|
|
90
|
+
" of ",
|
|
91
|
+
total,
|
|
92
|
+
" done"
|
|
93
|
+
]
|
|
94
|
+
})]
|
|
95
|
+
}), meta]
|
|
96
|
+
}),
|
|
97
|
+
total > 0 && /* @__PURE__ */ jsx("div", {
|
|
98
|
+
className: "mb-3 h-1.5 overflow-hidden rounded-full bg-muted",
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
101
|
+
className: "h-full rounded-full bg-[var(--surface-success-text)] transition-[width]",
|
|
102
|
+
style: { width: `${Math.round(done / total * 100)}%` }
|
|
103
|
+
})
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ jsx("ol", {
|
|
106
|
+
className: "divide-y divide-border/70",
|
|
107
|
+
children: items.map((item, index) => {
|
|
108
|
+
const status = STATUS[taskStatus(item.status)];
|
|
109
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
110
|
+
className: "flex items-start justify-between gap-3 py-2.5 first:pt-0 last:pb-0",
|
|
111
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
112
|
+
className: "min-w-0 flex-1",
|
|
113
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
114
|
+
className: cn("text-sm font-medium leading-6 text-foreground", taskStatus(item.status) === "cancelled" && "text-muted-foreground line-through"),
|
|
115
|
+
children: item.title
|
|
116
|
+
}), item.description && /* @__PURE__ */ jsx("p", {
|
|
117
|
+
className: "mt-0.5 text-sm leading-6 text-muted-foreground",
|
|
118
|
+
children: item.description
|
|
119
|
+
})]
|
|
120
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
121
|
+
className: "flex shrink-0 items-center gap-2",
|
|
122
|
+
children: [showPriority && item.priority && /* @__PURE__ */ jsx("span", {
|
|
123
|
+
className: "text-sm text-muted-foreground",
|
|
124
|
+
children: capitalized(item.priority.trim())
|
|
125
|
+
}), /* @__PURE__ */ jsx(StatusPill, {
|
|
126
|
+
tone: status.tone,
|
|
127
|
+
size: "md",
|
|
128
|
+
children: status.label
|
|
129
|
+
})]
|
|
130
|
+
})]
|
|
131
|
+
}, `${item.id}-${index}`);
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { TaskList, showsTaskPriority, taskProgress, taskStatus };
|
package/dist/run.d.ts
CHANGED
|
@@ -6,4 +6,7 @@ import { InlineToolItem, InlineToolItemProps } from "./run/inline-tool-item.js";
|
|
|
6
6
|
import { InlineThinkingItem, InlineThinkingItemProps } from "./run/inline-thinking-item.js";
|
|
7
7
|
import { ExpandedToolDetail, ExpandedToolDetailProps } from "./run/expanded-tool-detail.js";
|
|
8
8
|
import { LiveDuration } from "./run/run-item-primitives.js";
|
|
9
|
-
|
|
9
|
+
import { TaskList, TaskListItem, TaskListProps, TaskStatus, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
10
|
+
import { ApprovalCard, ApprovalCardProps, ApprovalDiffFile, ApprovalDiffSummary, ApprovalPlanStep } from "./run/approval-card.js";
|
|
11
|
+
import { RunPhase, RunPhaseList } from "./run/run-phase-list.js";
|
|
12
|
+
export { ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LiveDuration, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, parseToolEvent, showsTaskPriority, taskProgress, taskStatus };
|
package/dist/run.js
CHANGED
|
@@ -5,4 +5,7 @@ import { InlineThinkingItem } from "./run/inline-thinking-item.js";
|
|
|
5
5
|
import { RunGroup } from "./run/run-group.js";
|
|
6
6
|
import { LiveDuration } from "./run/run-item-primitives.js";
|
|
7
7
|
import { ToolCallFeed, parseToolEvent } from "./run/tool-call-feed.js";
|
|
8
|
-
|
|
8
|
+
import { TaskList, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
9
|
+
import { ApprovalCard, ApprovalDiffSummary } from "./run/approval-card.js";
|
|
10
|
+
import { RunPhaseList } from "./run/run-phase-list.js";
|
|
11
|
+
export { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, TaskList, ToolCallFeed, parseToolEvent, showsTaskPriority, taskProgress, taskStatus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.31.0",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -140,17 +140,17 @@
|
|
|
140
140
|
"lucide-react": "^1.48.0",
|
|
141
141
|
"marked": "^18.0.14",
|
|
142
142
|
"react-markdown": "^10.1.0",
|
|
143
|
-
"react-syntax-highlighter": "^16.1.1",
|
|
144
143
|
"rehype-sanitize": "^6.0.0",
|
|
145
144
|
"rehype-slug": "^6.0.0",
|
|
146
145
|
"remark-gfm": "^4.0.1",
|
|
146
|
+
"shiki": "^4.4.3",
|
|
147
147
|
"tailwind-merge": "^3.7.0",
|
|
148
148
|
"turndown": "^7.2.4"
|
|
149
149
|
},
|
|
150
150
|
"peerDependencies": {
|
|
151
151
|
"@hocuspocus/provider": ">=2.15.0 <5.0.0",
|
|
152
152
|
"@nanostores/react": ">=0.8.4 <3.0.0",
|
|
153
|
-
"@tangle-network/brand": "^1.19.
|
|
153
|
+
"@tangle-network/brand": "^1.19.5",
|
|
154
154
|
"@tiptap/core": ">=3.20.4 <4.0.0",
|
|
155
155
|
"@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
|
|
156
156
|
"@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
|
|
@@ -209,7 +209,6 @@
|
|
|
209
209
|
"@tiptap/starter-kit": "^3.31.3",
|
|
210
210
|
"@tiptap/y-tiptap": "^3.0.9",
|
|
211
211
|
"@types/react": "^19.3.0",
|
|
212
|
-
"@types/react-syntax-highlighter": "^15.5.13",
|
|
213
212
|
"@types/turndown": "^5.0.6",
|
|
214
213
|
"ajv": "8.17.1",
|
|
215
214
|
"nanostores": "^1.5.4",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import { renderToString } from "react-dom/server";
|
|
3
|
-
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
4
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
4
|
import { CodeBlock, CopyButton } from "./code-block";
|
|
6
5
|
|
|
@@ -16,12 +15,13 @@ afterEach(() => {
|
|
|
16
15
|
describe("CodeBlock scoped semantic colors", () => {
|
|
17
16
|
it("keeps code readable before the renderer loads, then applies live semantic colors and line numbers", async () => {
|
|
18
17
|
const { container } = render(<CodeBlock code={'const message = "hello";'} language="javascript" showLineNumbers />);
|
|
19
|
-
|
|
18
|
+
// Plain code already carries its line numbers, so highlighting shifts no layout.
|
|
19
|
+
expect(container.querySelector("pre code")?.textContent?.replace(/^1/, "")).toBe('const message = "hello";');
|
|
20
20
|
// The highlighter loads asynchronously; under a full parallel run it can take longer than waitFor's 1s default.
|
|
21
21
|
await waitFor(() => expect(container.innerHTML).toContain("var(--syntax-keyword, currentColor)"), { timeout: 10_000 });
|
|
22
22
|
expect(container.innerHTML).toContain("var(--syntax-string, currentColor)");
|
|
23
23
|
expect(container.innerHTML).toContain("var(--syntax-comment, currentColor)");
|
|
24
|
-
expect(container.querySelector("
|
|
24
|
+
expect(container.querySelector("[data-line-number]")).not.toBeNull();
|
|
25
25
|
expect(container.querySelector("pre code")?.textContent?.replace(/^1/, "")).toBe('const message = "hello";');
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -66,32 +66,41 @@ describe("CodeBlock", () => {
|
|
|
66
66
|
expect(container.querySelector("code")).not.toBeNull();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
it.each([
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
expect(
|
|
69
|
+
it.each(["javascript", "js", "ts", "tsx"])("highlights %s with Shiki into Brand syntax tokens", async (language) => {
|
|
70
|
+
const code = "const answer = 42; // the answer";
|
|
71
|
+
const { container } = render(<CodeBlock code={code} language={language} />);
|
|
72
|
+
await waitFor(() => expect(container.querySelector("[data-highlighted]")).not.toBeNull(), { timeout: 10_000 });
|
|
73
|
+
const tokens = Array.from(container.querySelectorAll("pre code span span"));
|
|
74
|
+
expect(tokens.length).toBeGreaterThan(3);
|
|
75
|
+
expect(container.innerHTML).toContain("var(--syntax-keyword, currentColor)");
|
|
76
|
+
expect(container.innerHTML).toContain("var(--syntax-comment, currentColor)");
|
|
77
|
+
expect(container.innerHTML).not.toContain("--shiki-");
|
|
78
|
+
expect(container.querySelector("pre code")?.textContent).toBe(code);
|
|
79
|
+
});
|
|
79
80
|
|
|
81
|
+
it.each(["brainfuck", "not-a-language", undefined])("keeps %s as readable plain text", (language) => {
|
|
82
|
+
const code = "++[>+++<-]";
|
|
80
83
|
const { container } = render(<CodeBlock code={code} language={language} />);
|
|
81
|
-
|
|
82
|
-
expect(Array.from(container.querySelectorAll("pre code span"), (span) => span.textContent)).toEqual(expected);
|
|
84
|
+
expect(container.querySelector("[data-highlighted]")).toBeNull();
|
|
83
85
|
expect(container.querySelector("pre code")?.textContent).toBe(code);
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
it("preserves whitespace and
|
|
88
|
+
it("preserves whitespace and numbers every line", async () => {
|
|
87
89
|
const code = "\tfirst \n second\n";
|
|
88
|
-
const baseline = document.createElement("div");
|
|
89
|
-
baseline.innerHTML = renderToString(<SyntaxHighlighter language="text" showLineNumbers>{code}</SyntaxHighlighter>);
|
|
90
90
|
const { container } = render(<CodeBlock code={code} language="text" showLineNumbers />);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
const numbers = Array.from(container.querySelectorAll("[data-line-number]"), (node) => node.textContent);
|
|
92
|
+
expect(numbers).toEqual(["1", "2", "3"]);
|
|
93
|
+
const text = Array.from(container.querySelectorAll("pre code > span"), (line) =>
|
|
94
|
+
Array.from(line.childNodes).filter((node) => !(node instanceof HTMLElement && node.hasAttribute("data-line-number")))
|
|
95
|
+
.map((node) => node.textContent).join("")).join("");
|
|
96
|
+
expect(text).toBe(code);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("names the file in the header beside the language", () => {
|
|
100
|
+
render(<CodeBlock code={"export {}"} language="ts" filename="src/lib/ctr.ts" />);
|
|
101
|
+
expect(screen.getByText("src/lib/ctr.ts")).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByText("TypeScript")).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByRole("region", { name: "src/lib/ctr.ts code" })).toBeInTheDocument();
|
|
95
104
|
});
|
|
96
105
|
|
|
97
106
|
it("uses `label` as the header text, overriding `language`", () => {
|
|
@@ -102,9 +111,9 @@ describe("CodeBlock", () => {
|
|
|
102
111
|
expect(queryByText("typescript")).toBeNull();
|
|
103
112
|
});
|
|
104
113
|
|
|
105
|
-
it("falls back to
|
|
114
|
+
it("falls back to the language's name for the header when no `label` is given", () => {
|
|
106
115
|
const { getByText } = render(<CodeBlock code={"x"} language="python" />);
|
|
107
|
-
expect(getByText("
|
|
116
|
+
expect(getByText("Python")).toBeInTheDocument();
|
|
108
117
|
});
|
|
109
118
|
|
|
110
119
|
it("renders no header when neither label nor language is set", () => {
|
|
@@ -161,9 +170,9 @@ describe("CodeBlock", () => {
|
|
|
161
170
|
expect(screen.getByRole("button", { name: "Copied" })).toBeTruthy();
|
|
162
171
|
});
|
|
163
172
|
|
|
164
|
-
it("renders the header label at the
|
|
173
|
+
it("renders the header label at the 14px text-sm step", () => {
|
|
165
174
|
const { getByText } = render(<CodeBlock code={"x"} label="YAML" />);
|
|
166
|
-
expect(getByText("YAML").className).toContain("text-
|
|
175
|
+
expect(getByText("YAML").className).toContain("text-sm");
|
|
167
176
|
expect(getByText("YAML").className).not.toContain("calc(");
|
|
168
177
|
});
|
|
169
178
|
|