taskchef 8.7.1 → 9.0.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/.codex-plugin/plugin.json +7 -10
- package/.mcp.json +3 -2
- package/README.md +31 -158
- package/docs/mcp-app.md +3 -3
- package/docs/spec.md +38 -746
- package/docs/taskchef-next-storage.md +2 -2
- package/index.js +2 -99
- package/package.json +12 -49
- package/skills/delegate/SKILL.md +44 -0
- package/skills/delegate/agents/openai.yaml +4 -0
- package/src/codex-app.js +3 -152
- package/src/codex-session-scanner.js +2 -2
- package/src/file-lock.js +25 -0
- package/src/mcp-app/react/NextTaskDetail.test.tsx +1 -1
- package/src/mcp-app/react/NextTaskDetail.tsx +8 -8
- package/src/mcp-app/react/main.test.tsx +3 -3
- package/src/mcp-app/react/main.tsx +7 -7
- package/src/mcp-app.js +2 -2
- package/src/mcp.js +4 -542
- package/src/next-cache.js +2 -2
- package/src/next-github.js +3 -3
- package/src/next-notifications.js +2 -2
- package/src/{dashboard/react → ui}/components/CardStatusIcons.tsx +1 -1
- package/src/{dashboard/react → ui}/components/GitHubLinks.tsx +1 -1
- package/src/{dashboard/react → ui}/components/LinkedText.tsx +1 -1
- package/src/{dashboard/react → ui}/components/RelativeTime.test.tsx +1 -1
- package/src/{dashboard/react → ui}/components/RelativeTime.tsx +1 -1
- package/src/{dashboard/react → ui}/components/ReplyImagePreview.test.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TaskBoard.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TaskCard.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TurnContent.tsx +1 -1
- package/src/{dashboard/react → ui}/presentation.ts +1 -1
- package/src/version.js +0 -1
- package/BACKLOG.md +0 -48
- package/assets/e2e-benchmark-example.json +0 -50
- package/assets/taskchef-dispatcher-instructions.md +0 -44
- package/bin/taskchef.js +0 -20
- package/docs/advanced-guide.md +0 -497
- package/docs/dashboard-lifecycle.md +0 -149
- package/docs/dashboard-ui.md +0 -40
- package/docs/firstmate-taskchef-comparison.md +0 -283
- package/docs/images/ccusage-token-consumption.png +0 -0
- package/docs/images/dashboard-identity.jpg +0 -0
- package/docs/images/dashboard-token-pending.jpg +0 -0
- package/docs/images/interrupted-turn-recovery.jpg +0 -0
- package/docs/images/notification-event-snapshots.jpg +0 -0
- package/docs/images/readme-board.png +0 -0
- package/docs/images/readme-completed.png +0 -0
- package/docs/images/readme-list.png +0 -0
- package/docs/images/readme-needs-input.png +0 -0
- package/docs/images/readme-roles.png +0 -0
- package/docs/images/reported-work-duration-desktop.jpg +0 -0
- package/docs/images/reported-work-duration-mobile.jpg +0 -0
- package/docs/images/result-history-dashboard.jpg +0 -0
- package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
- package/docs/images/taskchef-unified-status-popup.jpg +0 -0
- package/docs/workflows.md +0 -476
- package/mcp/dashboard-session.js +0 -51
- package/scripts/benchmark-dispatch-prepare.js +0 -90
- package/scripts/e2e-benchmark.js +0 -206
- package/scripts/roles/resolve_roles.py +0 -293
- package/skills/taskchef-bootstrap/SKILL.md +0 -96
- package/skills/taskchef-bootstrap/agents/openai.yaml +0 -4
- package/skills/taskchef-bootstrap/references/project-index.md +0 -154
- package/skills/taskchef-copilot/SKILL.md +0 -75
- package/skills/taskchef-copilot/agents/openai.yaml +0 -4
- package/skills/taskchef-dashboard/SKILL.md +0 -24
- package/skills/taskchef-dashboard/agents/openai.yaml +0 -4
- package/skills/taskchef-delegate/SKILL.md +0 -102
- package/skills/taskchef-delegate/agents/openai.yaml +0 -4
- package/skills/taskchef-delegate/references/model-roles.md +0 -29
- package/skills/taskchef-executor/SKILL.md +0 -154
- package/skills/taskchef-executor/agents/openai.yaml +0 -4
- package/skills/taskchef-executor/references/compatibility.md +0 -22
- package/skills/taskchef-executor/references/ending-actions.md +0 -26
- package/src/backups.js +0 -359
- package/src/cli.js +0 -733
- package/src/config-audit.js +0 -99
- package/src/config-mutations.js +0 -106
- package/src/dashboard/actions.js +0 -136
- package/src/dashboard/app.js +0 -1172
- package/src/dashboard/dist/app.js +0 -269
- package/src/dashboard/dist/app.js.map +0 -1
- package/src/dashboard/dist/index.html +0 -16
- package/src/dashboard/dist/styles.css +0 -1
- package/src/dashboard/index.html +0 -148
- package/src/dashboard/react/App.test.tsx +0 -346
- package/src/dashboard/react/App.tsx +0 -515
- package/src/dashboard/react/api.test.ts +0 -40
- package/src/dashboard/react/api.ts +0 -103
- package/src/dashboard/react/components/ActivityTimeline.tsx +0 -118
- package/src/dashboard/react/components/ModelSettings.test.tsx +0 -180
- package/src/dashboard/react/components/ModelSettings.tsx +0 -106
- package/src/dashboard/react/components/NotificationCenter.test.tsx +0 -76
- package/src/dashboard/react/components/NotificationCenter.tsx +0 -111
- package/src/dashboard/react/components/PullRequestBadges.tsx +0 -18
- package/src/dashboard/react/components/TaskDetail.test.tsx +0 -255
- package/src/dashboard/react/components/TaskDetail.tsx +0 -271
- package/src/dashboard/react/components/UsagePanel.tsx +0 -96
- package/src/dashboard/react/index.html +0 -15
- package/src/dashboard/react/main.tsx +0 -12
- package/src/dashboard/react/overlay-state.test.ts +0 -110
- package/src/dashboard/react/overlay-state.ts +0 -60
- package/src/dashboard/react/presentation.test.tsx +0 -571
- package/src/dashboard/styles.css +0 -261
- package/src/dashboard-control.js +0 -34
- package/src/dashboard-manager.js +0 -339
- package/src/dashboard-session-process.js +0 -88
- package/src/dashboard-session.js +0 -86
- package/src/dashboard.js +0 -1303
- package/src/delegation.js +0 -277
- package/src/github.js +0 -107
- package/src/model-roles.js +0 -70
- package/src/recorded-usage-tracker.js +0 -92
- package/src/usage-tracker.js +0 -650
- package/src/usage.js +0 -367
- package/src/workspace-path.js +0 -48
- package/src/workspace.js +0 -3629
- /package/src/{dashboard/react → ui}/components/AnimatedUsage.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/CardStatusIcons.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ImageGallery.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/OpenChatButton.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/OpenChatButton.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/PrGlyph.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/QueuedPrompts.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/QueuedPrompts.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyCover.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyCover.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyImagePreview.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyMarkdown.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyMarkdown.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/SavedUsageFormat.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/SavedUsageFormat.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ShimmerText.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/StatsGrid.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/StatusBadge.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskBoard.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskCardStats.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskCardStats.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TurnContent.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/fixtures.ts +0 -0
- /package/src/{dashboard → ui}/github-links.js +0 -0
- /package/src/{dashboard/react → ui}/image-cache.test.ts +0 -0
- /package/src/{dashboard/react → ui}/image-cache.ts +0 -0
- /package/src/{dashboard → ui}/state.js +0 -0
- /package/src/{dashboard/react → ui}/styles.css +0 -0
- /package/src/{dashboard/react → ui}/test-setup.ts +0 -0
- /package/src/{dashboard → ui}/time.js +0 -0
- /package/src/{dashboard/react → ui}/types.ts +0 -0
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { MantineProvider } from "@mantine/core";
|
|
2
|
-
import { act, cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { afterEach, expect, test, vi } from "vitest";
|
|
5
|
-
import { fixtureTask } from "../fixtures";
|
|
6
|
-
import { ActivityTimeline } from "./ActivityTimeline";
|
|
7
|
-
import { ManualTransitionConfirmation, TaskDetail, type TerminalStatus } from "./TaskDetail";
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
cleanup();
|
|
11
|
-
vi.restoreAllMocks();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("shows only metadata available from a DB-backed observed task", () => {
|
|
15
|
-
renderObservedDetail({
|
|
16
|
-
archive: false,
|
|
17
|
-
lastTurnEvent: "inProgress",
|
|
18
|
-
lastTurnEventAt: null,
|
|
19
|
-
recentFileActivity: true,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const metadata = screen.getByRole("heading", { name: "Metadata" }).closest("section")!;
|
|
23
|
-
expect(within(metadata).getByText("Archive status")).toBeVisible();
|
|
24
|
-
expect(within(metadata).getByText("Active")).toBeVisible();
|
|
25
|
-
expect(within(metadata).queryByText("Location")).not.toBeInTheDocument();
|
|
26
|
-
expect(within(metadata).getByText("inProgress")).toBeVisible();
|
|
27
|
-
expect(within(metadata).queryByText("Observed messages")).not.toBeInTheDocument();
|
|
28
|
-
expect(within(metadata).queryByText("Log bytes sampled")).not.toBeInTheDocument();
|
|
29
|
-
expect(within(metadata).getByText("Queue reason")).toBeVisible();
|
|
30
|
-
expect(metadata).not.toHaveTextContent("undefined");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("shows archived database chat status without claiming a file location", () => {
|
|
34
|
-
renderObservedDetail({
|
|
35
|
-
archive: true,
|
|
36
|
-
lastTurnEvent: "completed",
|
|
37
|
-
lastTurnEventAt: null,
|
|
38
|
-
recentFileActivity: false,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const metadata = screen.getByRole("heading", { name: "Metadata" }).closest("section")!;
|
|
42
|
-
expect(within(metadata).getByText("Archive status")).toBeVisible();
|
|
43
|
-
expect(within(metadata).getByText("Archived")).toBeVisible();
|
|
44
|
-
expect(within(metadata).queryByText("Location")).not.toBeInTheDocument();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("keeps log message and byte counts in observed task metadata", () => {
|
|
48
|
-
renderObservedDetail({
|
|
49
|
-
archive: true,
|
|
50
|
-
lastTurnEvent: "completed",
|
|
51
|
-
lastTurnEventAt: null,
|
|
52
|
-
recentFileActivity: false,
|
|
53
|
-
userMessages: 2,
|
|
54
|
-
assistantMessages: 3,
|
|
55
|
-
sampledBytes: 1024,
|
|
56
|
-
fileBytes: 4096,
|
|
57
|
-
}, "Local Codex log");
|
|
58
|
-
|
|
59
|
-
const metadata = screen.getByRole("heading", { name: "Metadata" }).closest("section")!;
|
|
60
|
-
expect(within(metadata).getByText("Location")).toBeVisible();
|
|
61
|
-
expect(within(metadata).getByText("Archived sessions")).toBeVisible();
|
|
62
|
-
expect(within(metadata).getByText("Observed messages")).toBeVisible();
|
|
63
|
-
expect(within(metadata).getByText("2 user, 3 assistant")).toBeVisible();
|
|
64
|
-
expect(within(metadata).getByText("Log bytes sampled")).toBeVisible();
|
|
65
|
-
expect(within(metadata).getByText("1024 of 4096")).toBeVisible();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
function renderObservedDetail(observed: NonNullable<ReturnType<typeof fixtureTask>["observed"]>, updatedBy = "Local Codex database") {
|
|
69
|
-
render(
|
|
70
|
-
<MantineProvider>
|
|
71
|
-
<TaskDetail
|
|
72
|
-
busy={false}
|
|
73
|
-
error={null}
|
|
74
|
-
highlightTurnRef={null}
|
|
75
|
-
onClose={() => {}}
|
|
76
|
-
onCopy={() => {}}
|
|
77
|
-
onOpenCodex={() => {}}
|
|
78
|
-
onTransition={async () => ({ ok: true })}
|
|
79
|
-
opened
|
|
80
|
-
readOnly
|
|
81
|
-
task={fixtureTask({ observed, updatedBy })}
|
|
82
|
-
/>
|
|
83
|
-
</MantineProvider>,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
test("clears a manual-transition confirmation only after success", async () => {
|
|
88
|
-
const onTransition = vi.fn().mockResolvedValue({ ok: true });
|
|
89
|
-
render(<ConfirmationHarness onTransition={onTransition} />);
|
|
90
|
-
expect(screen.getByRole("alert")).toHaveTextContent("Mark task completed?");
|
|
91
|
-
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
92
|
-
|
|
93
|
-
await waitFor(() => expect(onTransition).toHaveBeenCalledWith("completed", expect.any(String)));
|
|
94
|
-
await waitFor(() => expect(screen.queryByRole("alert")).not.toBeInTheDocument());
|
|
95
|
-
await waitFor(() => expect(screen.getByRole("heading", { name: "Task title" })).toHaveFocus());
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("retains a manual-transition confirmation after failure", async () => {
|
|
99
|
-
const onTransition = vi.fn().mockResolvedValue({ ok: false });
|
|
100
|
-
render(<ConfirmationHarness onTransition={onTransition} />);
|
|
101
|
-
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
102
|
-
|
|
103
|
-
await waitFor(() => expect(onTransition).toHaveBeenCalledWith("completed", expect.any(String)));
|
|
104
|
-
expect(screen.getByRole("alert")).toHaveTextContent("Mark task completed?");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("reuses an action ID for retryable failures and rotates it after a stale task", async () => {
|
|
108
|
-
const onTransition = vi.fn()
|
|
109
|
-
.mockResolvedValueOnce({ ok: false })
|
|
110
|
-
.mockResolvedValueOnce({ ok: false, rotateActionId: true })
|
|
111
|
-
.mockResolvedValueOnce({ ok: false });
|
|
112
|
-
render(<ConfirmationHarness onTransition={onTransition} />);
|
|
113
|
-
const confirm = screen.getByRole("button", { name: "Confirm" });
|
|
114
|
-
fireEvent.click(confirm);
|
|
115
|
-
await waitFor(() => expect(onTransition).toHaveBeenCalledTimes(1));
|
|
116
|
-
const firstActionId = onTransition.mock.calls[0][1];
|
|
117
|
-
fireEvent.click(confirm);
|
|
118
|
-
await waitFor(() => expect(onTransition).toHaveBeenCalledTimes(2));
|
|
119
|
-
expect(onTransition.mock.calls[1][1]).toBe(firstActionId);
|
|
120
|
-
fireEvent.click(confirm);
|
|
121
|
-
await waitFor(() => expect(onTransition).toHaveBeenCalledTimes(3));
|
|
122
|
-
expect(onTransition.mock.calls[2][1]).not.toBe(firstActionId);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("covers terminal, active, and unavailable timeline metrics", () => {
|
|
126
|
-
{
|
|
127
|
-
const task = fixtureTask({
|
|
128
|
-
status: "completed",
|
|
129
|
-
turns: [{
|
|
130
|
-
...fixtureTask().turns![0],
|
|
131
|
-
result: {
|
|
132
|
-
status: "completed",
|
|
133
|
-
summary: "Done.",
|
|
134
|
-
updatedAt: "2026-08-30T08:18:32.000Z",
|
|
135
|
-
},
|
|
136
|
-
}],
|
|
137
|
-
usage: {
|
|
138
|
-
status: "available",
|
|
139
|
-
turns: { "turn-one": { status: "available", totalTokens: 1_324_567, estimatedCostUsd: 0.12 } },
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
const { container } = render(
|
|
143
|
-
<MantineProvider><ActivityTimeline highlightTurnRef={null} task={task} /></MantineProvider>,
|
|
144
|
-
);
|
|
145
|
-
const stats = container.querySelector(".taskchef-turn-stats") as HTMLElement;
|
|
146
|
-
expect(stats).not.toBeNull();
|
|
147
|
-
expect(container.querySelector(".taskchef-turn-heading .taskchef-status-badge"))
|
|
148
|
-
.toHaveTextContent("completed");
|
|
149
|
-
expect(screen.queryByText(/^Turn ref:/)).not.toBeInTheDocument();
|
|
150
|
-
const cells = [...stats.children] as HTMLElement[];
|
|
151
|
-
expect(cells).toHaveLength(4);
|
|
152
|
-
expect(within(cells[0]).getByLabelText(/^Turn update time:/i)).toBeVisible();
|
|
153
|
-
expect(cells[0].querySelector(".tabler-icon-history")).not.toBeNull();
|
|
154
|
-
expect(within(cells[1]).getByLabelText(/completed turn reported wall-clock elapsed time/i))
|
|
155
|
-
.toHaveTextContent("18m 32s");
|
|
156
|
-
expect(cells[1].querySelector(".tabler-icon-hourglass")).not.toBeNull();
|
|
157
|
-
expect(within(cells[2]).getByLabelText("1,324,567 tokens")).toHaveTextContent("1.32M");
|
|
158
|
-
expect(cells[2].querySelector(".tabler-icon-stack-2")).not.toBeNull();
|
|
159
|
-
expect(within(cells[3]).getByLabelText("Estimated cost $0.12")).toHaveTextContent("0.12");
|
|
160
|
-
expect(within(cells[3]).getByLabelText("Estimated cost $0.12")).not.toHaveTextContent("$");
|
|
161
|
-
expect(cells[3].querySelector(".tabler-icon-currency-dollar")).not.toBeNull();
|
|
162
|
-
cleanup();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
{
|
|
166
|
-
let now = Date.parse("2026-08-30T08:18:32.000Z");
|
|
167
|
-
let tick: (() => void) | undefined;
|
|
168
|
-
vi.spyOn(Date, "now").mockImplementation(() => now);
|
|
169
|
-
vi.spyOn(window, "setInterval").mockImplementation(((handler: TimerHandler) => {
|
|
170
|
-
tick = handler as () => void;
|
|
171
|
-
return 1;
|
|
172
|
-
}) as typeof window.setInterval);
|
|
173
|
-
vi.spyOn(window, "clearInterval").mockImplementation(() => undefined);
|
|
174
|
-
const task = fixtureTask();
|
|
175
|
-
render(<MantineProvider><ActivityTimeline highlightTurnRef={null} task={task} /></MantineProvider>);
|
|
176
|
-
expect(screen.getByLabelText(/current turn reported wall-clock elapsed time/i))
|
|
177
|
-
.toHaveTextContent("18m 32s");
|
|
178
|
-
expect(screen.getByLabelText("Tokens pending")).toHaveTextContent("Pending");
|
|
179
|
-
expect(screen.getByLabelText("Estimated cost pending")).toHaveTextContent("Pending");
|
|
180
|
-
|
|
181
|
-
act(() => {
|
|
182
|
-
now += 2_000;
|
|
183
|
-
tick?.();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
expect(screen.getByLabelText(/current turn reported wall-clock elapsed time/i))
|
|
187
|
-
.toHaveTextContent("18m 34s");
|
|
188
|
-
cleanup();
|
|
189
|
-
vi.restoreAllMocks();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
const task = fixtureTask({
|
|
194
|
-
status: "completed",
|
|
195
|
-
turns: [{
|
|
196
|
-
...fixtureTask().turns![0],
|
|
197
|
-
startedAt: "2026-08-30T08:18:33.000Z",
|
|
198
|
-
result: {
|
|
199
|
-
status: "completed",
|
|
200
|
-
summary: "Done.",
|
|
201
|
-
updatedAt: "2026-08-30T08:18:32.000Z",
|
|
202
|
-
},
|
|
203
|
-
}],
|
|
204
|
-
usage: {
|
|
205
|
-
status: "unavailable",
|
|
206
|
-
turns: {
|
|
207
|
-
"turn-one": {
|
|
208
|
-
status: "unavailable",
|
|
209
|
-
reason: "Manual dashboard turns do not have usage boundaries.",
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
render(<MantineProvider><ActivityTimeline highlightTurnRef={null} task={task} /></MantineProvider>);
|
|
215
|
-
expect(screen.getByLabelText(/elapsed unavailable.*reported wall-clock/i))
|
|
216
|
-
.toHaveTextContent("n/a");
|
|
217
|
-
expect(screen.getByText("Manual dashboard turns do not have usage boundaries.")).toBeVisible();
|
|
218
|
-
expect(screen.getByLabelText(/tokens unavailable: manual dashboard turns/i)).toBeVisible();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
function ConfirmationHarness({ onTransition }: { onTransition: (status: TerminalStatus, actionId: string) => Promise<{ ok: boolean; rotateActionId?: boolean }> }) {
|
|
223
|
-
const [opened, setOpened] = useState(true);
|
|
224
|
-
return (
|
|
225
|
-
<MantineProvider>
|
|
226
|
-
<h2 id="task-detail-title" tabIndex={-1}>Task title</h2>
|
|
227
|
-
{opened && (
|
|
228
|
-
<ManualTransitionConfirmation
|
|
229
|
-
busy={false}
|
|
230
|
-
onCancel={() => setOpened(false)}
|
|
231
|
-
onTransition={onTransition}
|
|
232
|
-
status="completed"
|
|
233
|
-
/>
|
|
234
|
-
)}
|
|
235
|
-
</MantineProvider>
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
test("read-only Details allows copying an ID without exposing manual status actions", () => {
|
|
241
|
-
const onCopy = vi.fn();
|
|
242
|
-
render(<MantineProvider><TaskDetail busy={false} error={null} highlightTurnRef={null} onClose={vi.fn()} onCopy={onCopy} onOpenCodex={vi.fn()} onTransition={vi.fn()} opened readOnly task={fixtureTask()} /></MantineProvider>);
|
|
243
|
-
expect(screen.queryByRole("button", {name:"More task actions"})).not.toBeInTheDocument();
|
|
244
|
-
fireEvent.click(screen.getByRole("button", {name:"Copy Task ID"}));
|
|
245
|
-
expect(onCopy).toHaveBeenCalledOnce();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test("observed web details show the full same-turn request and response", () => {
|
|
249
|
-
const task=fixtureTask({promptExcerpt:'Short request',promptText:'Full recorded request',replyExcerpt:'Short response',replyText:'Full recorded response',
|
|
250
|
-
observed:{archive:false,lastTurnEvent:'completed',lastTurnEventAt:null,recentFileActivity:false}});
|
|
251
|
-
render(<MantineProvider><TaskDetail task={task} opened readOnly busy={false} error={null} highlightTurnRef={null} onClose={()=>{}} onCopy={()=>{}} onOpenCodex={()=>{}} onTransition={async()=>({ok:true})} /></MantineProvider>);
|
|
252
|
-
expect(screen.getByLabelText('Latest turn request')).toHaveTextContent('Full recorded request');
|
|
253
|
-
expect(screen.getByLabelText('Latest turn response')).toHaveTextContent('Full recorded response');
|
|
254
|
-
expect(screen.queryByText('Latest saved reply (excerpt)')).toBeNull();
|
|
255
|
-
});
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { PullRequestBadges } from "./PullRequestBadges";
|
|
2
|
-
import {
|
|
3
|
-
ActionIcon,
|
|
4
|
-
Alert,
|
|
5
|
-
Box,
|
|
6
|
-
Button,
|
|
7
|
-
Divider,
|
|
8
|
-
Drawer,
|
|
9
|
-
Group,
|
|
10
|
-
Menu,
|
|
11
|
-
Modal,
|
|
12
|
-
ScrollArea,
|
|
13
|
-
Stack,
|
|
14
|
-
Text,
|
|
15
|
-
Title,
|
|
16
|
-
Tooltip,
|
|
17
|
-
} from "@mantine/core";
|
|
18
|
-
import { useMediaQuery } from "@mantine/hooks";
|
|
19
|
-
import {
|
|
20
|
-
IconCheck,
|
|
21
|
-
IconClipboard,
|
|
22
|
-
IconDots,
|
|
23
|
-
IconX,
|
|
24
|
-
} from "@tabler/icons-react";
|
|
25
|
-
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
26
|
-
import type { Task } from "../types";
|
|
27
|
-
import { ActivityTimeline } from "./ActivityTimeline";
|
|
28
|
-
import { GitHubLinks } from "./GitHubLinks";
|
|
29
|
-
import { OpenChatButton } from "./OpenChatButton";
|
|
30
|
-
import { TurnContent } from "./TurnContent";
|
|
31
|
-
import { RelativeTime } from "./RelativeTime";
|
|
32
|
-
import { StatusBadge } from "./StatusBadge";
|
|
33
|
-
import { UsagePanel } from "./UsagePanel";
|
|
34
|
-
|
|
35
|
-
export type TerminalStatus = "completed" | "failed";
|
|
36
|
-
export interface ManualTransitionResult {
|
|
37
|
-
ok: boolean;
|
|
38
|
-
rotateActionId?: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function TaskDetail({
|
|
42
|
-
busy,
|
|
43
|
-
error,
|
|
44
|
-
highlightTurnRef,
|
|
45
|
-
onClose,
|
|
46
|
-
onCopy,
|
|
47
|
-
onOpenCodex,
|
|
48
|
-
onTransition,
|
|
49
|
-
opened,
|
|
50
|
-
task,
|
|
51
|
-
notifications,
|
|
52
|
-
notice,
|
|
53
|
-
readOnly = false,
|
|
54
|
-
extraActions,
|
|
55
|
-
}: {
|
|
56
|
-
busy: boolean;
|
|
57
|
-
error: string | null;
|
|
58
|
-
highlightTurnRef: string | null;
|
|
59
|
-
onClose: () => void;
|
|
60
|
-
onCopy: () => void;
|
|
61
|
-
onOpenCodex: () => void;
|
|
62
|
-
onTransition: (status: TerminalStatus, actionId: string) => Promise<ManualTransitionResult>;
|
|
63
|
-
opened: boolean;
|
|
64
|
-
task: Task | null;
|
|
65
|
-
notifications?: ReactNode;
|
|
66
|
-
notice?: string | null;
|
|
67
|
-
readOnly?: boolean;
|
|
68
|
-
extraActions?: React.ReactNode;
|
|
69
|
-
}) {
|
|
70
|
-
const mobile = useMediaQuery("(max-width: 48em)");
|
|
71
|
-
const [confirmStatus, setConfirmStatus] = useState<TerminalStatus | null>(null);
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (!opened) setConfirmStatus(null);
|
|
74
|
-
}, [opened]);
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (opened && highlightTurnRef) {
|
|
77
|
-
requestAnimationFrame(() => {
|
|
78
|
-
document.querySelector<HTMLElement>(`[data-turn-ref="${CSS.escape(highlightTurnRef)}"]`)?.focus();
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}, [highlightTurnRef, opened, task?.turns?.length, task?.updatedAt]);
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (confirmStatus && (!task || !manualTransitionTargets(task).includes(confirmStatus))) {
|
|
84
|
-
setConfirmStatus(null);
|
|
85
|
-
}
|
|
86
|
-
}, [confirmStatus, task?.status]);
|
|
87
|
-
|
|
88
|
-
if (!task) return null;
|
|
89
|
-
const transitionTargets = manualTransitionTargets(task);
|
|
90
|
-
const content = (
|
|
91
|
-
<Stack gap="lg">
|
|
92
|
-
{notifications}
|
|
93
|
-
<Box>
|
|
94
|
-
<Group align="flex-start" gap="sm" justify="space-between" wrap="nowrap">
|
|
95
|
-
<Box>
|
|
96
|
-
<Text c="teal" fw={700} size="xs" tt="uppercase">{task.project.name}</Text>
|
|
97
|
-
<Title id="task-detail-title" order={2} size="h3" tabIndex={-1}>{task.title}</Title>
|
|
98
|
-
</Box>
|
|
99
|
-
<StatusBadge status={task.status} label={task.statusLabel} />
|
|
100
|
-
</Group>
|
|
101
|
-
<Group gap="xs" mt="md">
|
|
102
|
-
<OpenChatButton loading={busy} onClick={onOpenCodex} taskTitle={task.title} />
|
|
103
|
-
{extraActions}
|
|
104
|
-
{readOnly && <Button leftSection={<IconClipboard size={14} />} onClick={onCopy} variant="default">Copy Task ID</Button>}
|
|
105
|
-
{!readOnly && <Menu position="bottom-start" shadow="md" withinPortal zIndex={360}>
|
|
106
|
-
<Menu.Target>
|
|
107
|
-
<Tooltip label="More task actions">
|
|
108
|
-
<ActionIcon aria-label="More task actions" className="taskchef-detail-more" disabled={busy} variant="default">
|
|
109
|
-
<IconDots aria-hidden size={17} />
|
|
110
|
-
</ActionIcon>
|
|
111
|
-
</Tooltip>
|
|
112
|
-
</Menu.Target>
|
|
113
|
-
<Menu.Dropdown>
|
|
114
|
-
<Menu.Item leftSection={<IconClipboard size={14} />} onClick={onCopy}>Copy Task ID</Menu.Item>
|
|
115
|
-
{transitionTargets.includes("completed") && (
|
|
116
|
-
<Menu.Item leftSection={<IconCheck size={14} />} onClick={() => setConfirmStatus("completed")}>Mark completed</Menu.Item>
|
|
117
|
-
)}
|
|
118
|
-
{transitionTargets.includes("failed") && (
|
|
119
|
-
<Menu.Item color="red" leftSection={<IconX size={14} />} onClick={() => setConfirmStatus("failed")}>Mark failed</Menu.Item>
|
|
120
|
-
)}
|
|
121
|
-
</Menu.Dropdown>
|
|
122
|
-
</Menu>}
|
|
123
|
-
</Group>
|
|
124
|
-
<Box mt="sm">
|
|
125
|
-
<PullRequestBadges pullRequests={task.pullRequests} />
|
|
126
|
-
<GitHubLinks task={task} />
|
|
127
|
-
</Box>
|
|
128
|
-
</Box>
|
|
129
|
-
|
|
130
|
-
{confirmStatus && (
|
|
131
|
-
<ManualTransitionConfirmation
|
|
132
|
-
key={confirmStatus}
|
|
133
|
-
busy={busy}
|
|
134
|
-
onCancel={() => setConfirmStatus(null)}
|
|
135
|
-
onTransition={onTransition}
|
|
136
|
-
status={confirmStatus}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
{notice && <Alert color="teal" role="status">{notice}</Alert>}
|
|
140
|
-
{error && <Alert color="red" role="alert">{error}</Alert>}
|
|
141
|
-
|
|
142
|
-
{!readOnly && <section aria-labelledby="usage-heading">
|
|
143
|
-
<Title id="usage-heading" mb="xs" order={3} size="h5">Usage</Title>
|
|
144
|
-
<UsagePanel task={task} />
|
|
145
|
-
</section>}
|
|
146
|
-
{!readOnly && <Divider />}
|
|
147
|
-
{!readOnly && <section aria-labelledby="activity-heading">
|
|
148
|
-
<Title id="activity-heading" mb="sm" order={3} size="h5">Activity timeline</Title>
|
|
149
|
-
<ActivityTimeline highlightTurnRef={highlightTurnRef} task={task} />
|
|
150
|
-
</section>}
|
|
151
|
-
{!readOnly && <section aria-labelledby="instruction-heading">
|
|
152
|
-
<Title id="instruction-heading" mb="xs" order={3} size="h5">Original instruction</Title>
|
|
153
|
-
<Box className="taskchef-code-panel" component="pre">{task.instruction}</Box>
|
|
154
|
-
</section>}
|
|
155
|
-
{task.observed && <section aria-label="Latest turn"><TurnContent task={task} /></section>}
|
|
156
|
-
<section aria-labelledby="metadata-heading">
|
|
157
|
-
<Title id="metadata-heading" mb="xs" order={3} size="h5">Metadata</Title>
|
|
158
|
-
<dl className="taskchef-metadata">
|
|
159
|
-
<dt>Task ID</dt><dd>{task.id}</dd>
|
|
160
|
-
<dt>Thread ID</dt><dd>{task.threadId ?? "—"}</dd>
|
|
161
|
-
<dt>Current turn ref</dt><dd>{task.turnRef ?? "—"}</dd>
|
|
162
|
-
<dt>Project path</dt><dd>{task.project.path}</dd>
|
|
163
|
-
<dt>Created</dt><dd><RelativeTime label="Created time" value={task.createdAt} /></dd>
|
|
164
|
-
<dt>Updated</dt><dd><RelativeTime label="Updated time" value={task.meaningfulUpdatedAt ?? task.updatedAt} /></dd>
|
|
165
|
-
<dt>Updated by</dt><dd>{task.updatedBy ?? "—"}</dd>
|
|
166
|
-
{task.observed?.directChildCount !== undefined && <><dt>Direct subagent chats (all history)</dt><dd>{task.observed.directChildCount}</dd></>}
|
|
167
|
-
{task.observed && <>
|
|
168
|
-
<dt>Queue reason</dt><dd>{task.summary}</dd>
|
|
169
|
-
{task.updatedBy === "Local Codex database" ? <>
|
|
170
|
-
<dt>Archive status</dt><dd>{task.observed.archive ? "Archived" : "Active"}</dd>
|
|
171
|
-
</> : <>
|
|
172
|
-
<dt>Location</dt><dd>{task.observed.archive ? "Archived sessions" : "Active sessions"}</dd>
|
|
173
|
-
</>}
|
|
174
|
-
{task.statusLabel && <><dt>Schedule</dt><dd>{task.scheduled ? "Active" : "None active"}</dd><dt>Latest input</dt><dd>{task.inputSource}</dd><dt>Done reason</dt><dd>{task.observed.archive ? "Archived" : task.manualDone ? "Manual mark" : "Not marked"}</dd></>}
|
|
175
|
-
<dt>Latest turn event</dt><dd>{task.observed.lastTurnEvent ?? "Unknown"}</dd>
|
|
176
|
-
{task.observed.userMessages !== undefined && task.observed.assistantMessages !== undefined && <>
|
|
177
|
-
<dt>Observed messages</dt><dd>{task.observed.userMessages} user, {task.observed.assistantMessages} assistant</dd>
|
|
178
|
-
</>}
|
|
179
|
-
{task.observed.sampledBytes !== undefined && task.observed.fileBytes !== undefined && <>
|
|
180
|
-
<dt>Log bytes sampled</dt><dd>{task.observed.sampledBytes} of {task.observed.fileBytes}</dd>
|
|
181
|
-
</>}
|
|
182
|
-
</>}
|
|
183
|
-
</dl>
|
|
184
|
-
</section>
|
|
185
|
-
</Stack>
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
if (mobile) {
|
|
189
|
-
return (
|
|
190
|
-
<Drawer
|
|
191
|
-
closeOnClickOutside={!busy}
|
|
192
|
-
closeOnEscape={!busy}
|
|
193
|
-
onClose={onClose}
|
|
194
|
-
opened={opened}
|
|
195
|
-
overlayProps={{ backgroundOpacity: 0.55, blur: 2 }}
|
|
196
|
-
position="bottom"
|
|
197
|
-
scrollAreaComponent={ScrollArea.Autosize}
|
|
198
|
-
size="92%"
|
|
199
|
-
title={<span className="taskchef-visually-hidden">Task details</span>}
|
|
200
|
-
trapFocus
|
|
201
|
-
zIndex={300}
|
|
202
|
-
>
|
|
203
|
-
{content}
|
|
204
|
-
</Drawer>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
return (
|
|
208
|
-
<Modal
|
|
209
|
-
aria-labelledby="task-detail-title"
|
|
210
|
-
closeOnClickOutside={!busy}
|
|
211
|
-
closeOnEscape={!busy}
|
|
212
|
-
onClose={onClose}
|
|
213
|
-
opened={opened}
|
|
214
|
-
overlayProps={{ backgroundOpacity: 0.55, blur: 2 }}
|
|
215
|
-
scrollAreaComponent={ScrollArea.Autosize}
|
|
216
|
-
size="min(600px, calc(100vw - 32px))"
|
|
217
|
-
title={<span className="taskchef-visually-hidden">Task details</span>}
|
|
218
|
-
trapFocus
|
|
219
|
-
zIndex={300}
|
|
220
|
-
>
|
|
221
|
-
{content}
|
|
222
|
-
</Modal>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function manualTransitionTargets(task: Task): TerminalStatus[] {
|
|
227
|
-
return task.status === "completed"
|
|
228
|
-
? ["failed"]
|
|
229
|
-
: task.status === "failed"
|
|
230
|
-
? ["completed"]
|
|
231
|
-
: task.status === "working" || task.status === "needs_input"
|
|
232
|
-
? ["completed", "failed"]
|
|
233
|
-
: [];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function ManualTransitionConfirmation({
|
|
237
|
-
busy,
|
|
238
|
-
onCancel,
|
|
239
|
-
onTransition,
|
|
240
|
-
status,
|
|
241
|
-
}: {
|
|
242
|
-
busy: boolean;
|
|
243
|
-
onCancel: () => void;
|
|
244
|
-
onTransition: (status: TerminalStatus, actionId: string) => Promise<ManualTransitionResult>;
|
|
245
|
-
status: TerminalStatus;
|
|
246
|
-
}) {
|
|
247
|
-
const actionId = useRef(crypto.randomUUID());
|
|
248
|
-
return (
|
|
249
|
-
<Alert color={status === "failed" ? "red" : "teal"} role="alert" title={`Mark task ${status}?`}>
|
|
250
|
-
<Text mb="sm" size="sm">This interrupts active work and appends an audited manual dashboard turn.</Text>
|
|
251
|
-
<Group gap="xs">
|
|
252
|
-
<Button
|
|
253
|
-
color={status === "failed" ? "red" : "teal"}
|
|
254
|
-
loading={busy}
|
|
255
|
-
onClick={async () => {
|
|
256
|
-
const result = await onTransition(status, actionId.current);
|
|
257
|
-
if (result.ok) {
|
|
258
|
-
onCancel();
|
|
259
|
-
requestAnimationFrame(() => document.getElementById("task-detail-title")?.focus());
|
|
260
|
-
}
|
|
261
|
-
else if (result.rotateActionId) actionId.current = crypto.randomUUID();
|
|
262
|
-
}}
|
|
263
|
-
size="compact-sm"
|
|
264
|
-
>
|
|
265
|
-
Confirm
|
|
266
|
-
</Button>
|
|
267
|
-
<Button disabled={busy} onClick={onCancel} size="compact-sm" variant="default">Cancel</Button>
|
|
268
|
-
</Group>
|
|
269
|
-
</Alert>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { Box, Paper, Text } from "@mantine/core";
|
|
2
|
-
import type { Task } from "../types";
|
|
3
|
-
import {
|
|
4
|
-
formatCompactTokens,
|
|
5
|
-
formatEstimatedCost,
|
|
6
|
-
formatFullTokens,
|
|
7
|
-
taskReportedWorkView,
|
|
8
|
-
usageView,
|
|
9
|
-
} from "../presentation";
|
|
10
|
-
import { ShimmerText } from "./ShimmerText";
|
|
11
|
-
|
|
12
|
-
export function UsagePanel({ task }: { task: Task }) {
|
|
13
|
-
const usage = usageView(task);
|
|
14
|
-
const reportedWork = taskReportedWorkView(task);
|
|
15
|
-
const ready = usage.kind === "ready" ? usage : null;
|
|
16
|
-
const models = Object.keys(ready?.usage.models ?? {}).join(", ");
|
|
17
|
-
const estimatedCost = ready?.usage.estimatedCostUsd;
|
|
18
|
-
const hasEstimatedCost = typeof estimatedCost === "number" && Number.isFinite(estimatedCost);
|
|
19
|
-
const stateValue = usage.kind === "pending"
|
|
20
|
-
? "Pending"
|
|
21
|
-
: usage.kind === "calculating"
|
|
22
|
-
? "Calculating"
|
|
23
|
-
: "n/a";
|
|
24
|
-
const accessibleState = usage.kind === "unavailable" ? "unavailable" : stateValue.toLowerCase();
|
|
25
|
-
const values: Array<{
|
|
26
|
-
accessibleValue?: string;
|
|
27
|
-
animated?: boolean;
|
|
28
|
-
label: string;
|
|
29
|
-
title?: string;
|
|
30
|
-
value: string;
|
|
31
|
-
}> = [
|
|
32
|
-
{
|
|
33
|
-
accessibleValue: ready
|
|
34
|
-
? `${formatFullTokens(ready.usage.totalTokens)} tokens`
|
|
35
|
-
: usage.label,
|
|
36
|
-
animated: usage.kind === "pending" || usage.kind === "calculating" || ready?.knownSoFar,
|
|
37
|
-
label: "Tokens",
|
|
38
|
-
title: ready ? `${formatFullTokens(ready.usage.totalTokens)} tokens` : usage.label,
|
|
39
|
-
value: ready ? `${ready.usage.partial ? "≥" : ""}${formatCompactTokens(ready.usage.totalTokens)}` : stateValue,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
accessibleValue: ready
|
|
43
|
-
? ready.usage.estimatedCostUsd == null
|
|
44
|
-
? "Estimated cost unavailable"
|
|
45
|
-
: `Estimated cost ${formatEstimatedCost(ready.usage.estimatedCostUsd)}`
|
|
46
|
-
: `Estimated cost ${accessibleState}${
|
|
47
|
-
usage.kind === "unavailable" ? `: ${usage.label}` : ""
|
|
48
|
-
}`,
|
|
49
|
-
animated: usage.kind === "pending"
|
|
50
|
-
|| usage.kind === "calculating"
|
|
51
|
-
|| (ready?.knownSoFar && hasEstimatedCost),
|
|
52
|
-
label: "Estimated cost",
|
|
53
|
-
title: ready?.usage.estimatedCostUsd == null
|
|
54
|
-
? "Estimated cost unavailable"
|
|
55
|
-
: `Unrounded estimate: $${ready.usage.estimatedCostUsd}`,
|
|
56
|
-
value: ready
|
|
57
|
-
? ready.usage.estimatedCostUsd == null ? "n/a" : `${ready.usage.costPartial ? "≥" : ""}${formatEstimatedCost(ready.usage.estimatedCostUsd)}`
|
|
58
|
-
: stateValue,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
accessibleValue: reportedWork.accessibleLabel,
|
|
62
|
-
label: "Duration",
|
|
63
|
-
title: reportedWork.title,
|
|
64
|
-
value: reportedWork.value,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
accessibleValue: models || "Models unavailable",
|
|
68
|
-
label: "Models",
|
|
69
|
-
title: models || "Models unavailable",
|
|
70
|
-
value: models || "n/a",
|
|
71
|
-
},
|
|
72
|
-
];
|
|
73
|
-
return (
|
|
74
|
-
<Box aria-live="polite">
|
|
75
|
-
<Box className="taskchef-metrics">
|
|
76
|
-
{values.map(({ accessibleValue, animated, label, title, value }) => (
|
|
77
|
-
<Paper className="taskchef-metric" key={label} p="sm" radius="md" withBorder>
|
|
78
|
-
<Text c="dimmed" size="xs">{label}</Text>
|
|
79
|
-
<Text
|
|
80
|
-
aria-label={accessibleValue}
|
|
81
|
-
className="taskchef-metric-value"
|
|
82
|
-
fw={650}
|
|
83
|
-
size="sm"
|
|
84
|
-
title={title}
|
|
85
|
-
>
|
|
86
|
-
{animated ? <ShimmerText>{value}</ShimmerText> : value}
|
|
87
|
-
</Text>
|
|
88
|
-
</Paper>
|
|
89
|
-
))}
|
|
90
|
-
</Box>
|
|
91
|
-
{usage.kind === "unavailable" && usage.label !== "Token usage unavailable" && (
|
|
92
|
-
<Text c="dimmed" mt="xs" size="xs">{usage.label}</Text>
|
|
93
|
-
)}
|
|
94
|
-
</Box>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<meta name="color-scheme" content="light dark" />
|
|
7
|
-
<meta name="theme-color" content="#f3f5f2" />
|
|
8
|
-
<meta name="taskchef-style-nonce" content="__TASKCHEF_STYLE_NONCE__" />
|
|
9
|
-
<title>TaskChef Dashboard</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div id="root"></div>
|
|
13
|
-
<script type="module" src="/main.tsx"></script>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import "@mantine/core/styles.css";
|
|
2
|
-
import "@mantine/notifications/styles.css";
|
|
3
|
-
import { StrictMode } from "react";
|
|
4
|
-
import { createRoot } from "react-dom/client";
|
|
5
|
-
import DashboardApp from "./App";
|
|
6
|
-
import "./styles.css";
|
|
7
|
-
|
|
8
|
-
createRoot(document.getElementById("root")!).render(
|
|
9
|
-
<StrictMode>
|
|
10
|
-
<DashboardApp />
|
|
11
|
-
</StrictMode>,
|
|
12
|
-
);
|