@echothink-ui/project 0.1.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/README.md +5 -0
- package/dist/components/ProjectActivityTimeline.d.ts +5 -0
- package/dist/components/ProjectAppDomainPanel.d.ts +8 -0
- package/dist/components/ProjectCard.d.ts +8 -0
- package/dist/components/ProjectCreateForm.d.ts +7 -0
- package/dist/components/ProjectDashboardTemplate.d.ts +11 -0
- package/dist/components/ProjectManagementPage.d.ts +17 -0
- package/dist/components/ProjectMembersPanel.d.ts +9 -0
- package/dist/components/ProjectModelConfigPanel.d.ts +9 -0
- package/dist/components/ProjectPermissionPanel.d.ts +9 -0
- package/dist/components/ProjectResourcePanel.d.ts +6 -0
- package/dist/components/ProjectScopeSelector.d.ts +7 -0
- package/dist/components/ProjectSettingsPanel.d.ts +6 -0
- package/dist/components/ProjectStatusSummary.d.ts +6 -0
- package/dist/components/ProjectSummaryPanel.d.ts +5 -0
- package/dist/components/ProjectTab.d.ts +3 -0
- package/dist/components/ProjectTabGroup.d.ts +12 -0
- package/dist/components/ProjectTable.d.ts +9 -0
- package/dist/index.cjs +2112 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2059 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +2098 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +99 -0
- package/dist/utils.d.ts +288 -0
- package/package.json +45 -0
- package/src/components/ProjectActivityTimeline.test.tsx +43 -0
- package/src/components/ProjectActivityTimeline.tsx +118 -0
- package/src/components/ProjectAppDomainPanel.tsx +147 -0
- package/src/components/ProjectCard.tsx +117 -0
- package/src/components/ProjectCreateForm.test.tsx +45 -0
- package/src/components/ProjectCreateForm.tsx +176 -0
- package/src/components/ProjectDashboardTemplate.tsx +107 -0
- package/src/components/ProjectManagementPage.tsx +112 -0
- package/src/components/ProjectMembersPanel.tsx +181 -0
- package/src/components/ProjectModelConfigPanel.tsx +294 -0
- package/src/components/ProjectPermissionPanel.tsx +174 -0
- package/src/components/ProjectResourcePanel.tsx +154 -0
- package/src/components/ProjectScopeSelector.test.tsx +50 -0
- package/src/components/ProjectScopeSelector.tsx +92 -0
- package/src/components/ProjectSettingsPanel.test.tsx +25 -0
- package/src/components/ProjectSettingsPanel.tsx +244 -0
- package/src/components/ProjectStatusSummary.tsx +165 -0
- package/src/components/ProjectSummaryPanel.test.tsx +37 -0
- package/src/components/ProjectSummaryPanel.tsx +85 -0
- package/src/components/ProjectTab.tsx +8 -0
- package/src/components/ProjectTabGroup.tsx +38 -0
- package/src/components/ProjectTable.tsx +138 -0
- package/src/index.test.tsx +337 -0
- package/src/index.tsx +41 -0
- package/src/styles.css +2431 -0
- package/src/types.ts +111 -0
- package/src/utils.ts +96 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
ProjectAppDomainPanel,
|
|
5
|
+
ProjectComponentNames,
|
|
6
|
+
ProjectMembersPanel,
|
|
7
|
+
ProjectModelConfigPanel,
|
|
8
|
+
ProjectPermissionPanel,
|
|
9
|
+
ProjectResourcePanel,
|
|
10
|
+
ProjectStatusSummary,
|
|
11
|
+
ProjectTab,
|
|
12
|
+
ProjectTabGroup,
|
|
13
|
+
ProjectTable
|
|
14
|
+
} from "./index";
|
|
15
|
+
|
|
16
|
+
const instances = [
|
|
17
|
+
{
|
|
18
|
+
id: "mailbox",
|
|
19
|
+
appDomainLabel: "Mailbox",
|
|
20
|
+
status: "running" as const,
|
|
21
|
+
health: "healthy" as const
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "documents",
|
|
25
|
+
appDomainLabel: "Documents",
|
|
26
|
+
status: "in-progress" as const,
|
|
27
|
+
health: "warning" as const
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
describe("@echothink-ui/project ProjectTable", () => {
|
|
32
|
+
it("renders a named table with stable project name and description blocks", () => {
|
|
33
|
+
render(
|
|
34
|
+
<ProjectTable
|
|
35
|
+
projects={[
|
|
36
|
+
{
|
|
37
|
+
id: "p1",
|
|
38
|
+
name: "Marketing Campaign",
|
|
39
|
+
status: "in-progress",
|
|
40
|
+
description: "Q3 product launch and lifecycle messaging.",
|
|
41
|
+
ownerLabel: "JD",
|
|
42
|
+
updatedAt: "May 27",
|
|
43
|
+
appDomainsCount: 4,
|
|
44
|
+
openTasksCount: 12
|
|
45
|
+
}
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const table = screen.getByRole("table", { name: "Project list" });
|
|
51
|
+
const nameGroup = screen.getByText("Marketing Campaign").closest(".eth-project-table__name");
|
|
52
|
+
|
|
53
|
+
expect(table.className).toContain("eth-project-table__table");
|
|
54
|
+
expect(nameGroup?.querySelector(".eth-project-table__title")?.textContent).toBe(
|
|
55
|
+
"Marketing Campaign"
|
|
56
|
+
);
|
|
57
|
+
expect(nameGroup?.querySelector(".eth-project-table__description")?.textContent).toBe(
|
|
58
|
+
"Q3 product launch and lifecycle messaging."
|
|
59
|
+
);
|
|
60
|
+
expect(screen.getByRole("columnheader", { name: "App domains" })).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("@echothink-ui/project ProjectAppDomainPanel", () => {
|
|
65
|
+
it("omits the actions column when no action handlers are available", () => {
|
|
66
|
+
render(<ProjectAppDomainPanel instances={instances} />);
|
|
67
|
+
|
|
68
|
+
expect(screen.queryByText("Actions")).toBeNull();
|
|
69
|
+
expect(screen.queryByRole("button", { name: "Open" })).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("shows configured row actions when handlers are provided", () => {
|
|
73
|
+
render(
|
|
74
|
+
<ProjectAppDomainPanel
|
|
75
|
+
instances={instances}
|
|
76
|
+
onOpen={() => undefined}
|
|
77
|
+
onConfigure={() => undefined}
|
|
78
|
+
onRemove={() => undefined}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(screen.getByText("Actions")).toBeTruthy();
|
|
83
|
+
expect(screen.getAllByRole("button", { name: "Open" })).toHaveLength(2);
|
|
84
|
+
expect(screen.getAllByRole("button", { name: "Configure" })).toHaveLength(2);
|
|
85
|
+
expect(screen.getAllByRole("button", { name: "Remove" })).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("@echothink-ui/project ProjectModelConfigPanel", () => {
|
|
90
|
+
const providers = [
|
|
91
|
+
{
|
|
92
|
+
id: "anthropic",
|
|
93
|
+
label: "Anthropic",
|
|
94
|
+
models: [{ id: "claude-opus-4-7", label: "Claude Opus 4.7" }]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "openai",
|
|
98
|
+
label: "OpenAI",
|
|
99
|
+
models: [{ id: "gpt-4o", label: "GPT-4o" }]
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
it("renders selected model summary and generation parameter context", () => {
|
|
104
|
+
render(
|
|
105
|
+
<ProjectModelConfigPanel
|
|
106
|
+
providers={providers}
|
|
107
|
+
selectedProvider="anthropic"
|
|
108
|
+
model="claude-opus-4-7"
|
|
109
|
+
params={{ temperature: 0.4, topP: 0.95, maxTokens: 4096 }}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(screen.getByRole("region", { name: "Project model configuration" })).toBeTruthy();
|
|
114
|
+
expect(screen.getByRole("heading", { name: "Project model configuration" })).toBeTruthy();
|
|
115
|
+
expect(screen.getAllByText("Anthropic").length).toBeGreaterThan(0);
|
|
116
|
+
expect(screen.getAllByText("Claude Opus 4.7").length).toBeGreaterThan(0);
|
|
117
|
+
expect(screen.getByText("Controls response randomness.")).toBeTruthy();
|
|
118
|
+
expect(screen.getByText("Caps the response length for project agent runs.")).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("emits the first available model when the provider changes", () => {
|
|
122
|
+
const handleChange = vi.fn();
|
|
123
|
+
|
|
124
|
+
render(
|
|
125
|
+
<ProjectModelConfigPanel
|
|
126
|
+
providers={providers}
|
|
127
|
+
selectedProvider="anthropic"
|
|
128
|
+
model="claude-opus-4-7"
|
|
129
|
+
params={{ temperature: 0.4, topP: 0.95, maxTokens: 4096 }}
|
|
130
|
+
onChange={handleChange}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
fireEvent.change(screen.getByLabelText("Provider"), { target: { value: "openai" } });
|
|
135
|
+
|
|
136
|
+
expect(handleChange).toHaveBeenCalledWith({
|
|
137
|
+
provider: "openai",
|
|
138
|
+
model: "gpt-4o",
|
|
139
|
+
params: { temperature: 0.4, topP: 0.95, maxTokens: 4096 }
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("@echothink-ui/project ProjectMembersPanel", () => {
|
|
145
|
+
it("renders members as active by default and keeps roles read-only without a change handler", () => {
|
|
146
|
+
render(<ProjectMembersPanel members={[{ id: "m1", label: "Jane Doe", role: "owner" }]} />);
|
|
147
|
+
|
|
148
|
+
expect(screen.getByText("Active")).toBeTruthy();
|
|
149
|
+
expect(screen.queryByText("-")).toBeNull();
|
|
150
|
+
expect(screen.queryByRole("combobox", { name: "Role for Jane Doe" })).toBeNull();
|
|
151
|
+
expect(screen.getByText("Owner")).toBeTruthy();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("shows editable role controls and row actions when handlers are provided", () => {
|
|
155
|
+
render(
|
|
156
|
+
<ProjectMembersPanel
|
|
157
|
+
members={[{ id: "m1", label: "Jane Doe", role: "owner", status: "active" }]}
|
|
158
|
+
onChangeRole={() => undefined}
|
|
159
|
+
onRemove={() => undefined}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(screen.getByRole("combobox", { name: "Role for Jane Doe" })).toBeTruthy();
|
|
164
|
+
expect(screen.getByRole("button", { name: "Remove" })).toBeTruthy();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("@echothink-ui/project ProjectPermissionPanel", () => {
|
|
169
|
+
const subjects = [
|
|
170
|
+
{ id: "jd", label: "Jane Doe" },
|
|
171
|
+
{ id: "mk", label: "Mark K." }
|
|
172
|
+
];
|
|
173
|
+
const resources = [
|
|
174
|
+
{ id: "brief", label: "Brief.md" },
|
|
175
|
+
{ id: "tasks", label: "Tasks" }
|
|
176
|
+
];
|
|
177
|
+
const actions = [
|
|
178
|
+
{ id: "read", label: "Read" },
|
|
179
|
+
{ id: "write", label: "Write" }
|
|
180
|
+
];
|
|
181
|
+
const assignments = {
|
|
182
|
+
"jd:brief:read": true,
|
|
183
|
+
"jd:brief:write": true,
|
|
184
|
+
"mk:brief:read": true
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
it("summarizes the matrix and announces editable permission controls", () => {
|
|
188
|
+
const handleChange = vi.fn();
|
|
189
|
+
|
|
190
|
+
render(
|
|
191
|
+
<ProjectPermissionPanel
|
|
192
|
+
subjects={subjects}
|
|
193
|
+
resources={resources}
|
|
194
|
+
actions={actions}
|
|
195
|
+
assignments={assignments}
|
|
196
|
+
onChange={handleChange}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(screen.getByRole("heading", { name: "Project permissions" })).toBeTruthy();
|
|
201
|
+
expect(
|
|
202
|
+
within(screen.getByLabelText("Project permission summary")).getByText("3")
|
|
203
|
+
).toBeTruthy();
|
|
204
|
+
expect(
|
|
205
|
+
within(screen.getByLabelText("Project permission summary")).getByText("5")
|
|
206
|
+
).toBeTruthy();
|
|
207
|
+
|
|
208
|
+
fireEvent.click(
|
|
209
|
+
screen.getByRole("checkbox", { name: "Write permission for Mark K. on Tasks" })
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(handleChange).toHaveBeenCalledWith("mk:tasks:write", true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("uses read-only permission states when no change handler is provided", () => {
|
|
216
|
+
render(
|
|
217
|
+
<ProjectPermissionPanel
|
|
218
|
+
subjects={subjects.slice(0, 1)}
|
|
219
|
+
resources={resources.slice(0, 1)}
|
|
220
|
+
actions={actions}
|
|
221
|
+
assignments={assignments}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(screen.queryByRole("checkbox")).toBeNull();
|
|
226
|
+
expect(within(screen.getByRole("table")).getAllByText("Granted")).toHaveLength(2);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("@echothink-ui/project ProjectResourcePanel", () => {
|
|
231
|
+
const resources = [
|
|
232
|
+
{
|
|
233
|
+
id: "brief",
|
|
234
|
+
kind: "document",
|
|
235
|
+
label: "Brief.md",
|
|
236
|
+
status: "synced" as const,
|
|
237
|
+
updatedAt: "May 27"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "hero",
|
|
241
|
+
kind: "image",
|
|
242
|
+
label: "hero.png",
|
|
243
|
+
status: "in-progress" as const,
|
|
244
|
+
updatedAt: "May 26"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "dataset",
|
|
248
|
+
kind: "table",
|
|
249
|
+
label: "Audience segments",
|
|
250
|
+
status: "stale" as const,
|
|
251
|
+
updatedAt: "May 24"
|
|
252
|
+
}
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
it("summarizes resource counts and attention states", () => {
|
|
256
|
+
render(<ProjectResourcePanel resources={resources} />);
|
|
257
|
+
const summary = screen.getByLabelText("Resource summary");
|
|
258
|
+
|
|
259
|
+
expect(screen.getByRole("region", { name: "Project resources" })).toBeTruthy();
|
|
260
|
+
expect(within(summary).getByText("Resources")).toBeTruthy();
|
|
261
|
+
expect(within(summary).getByText("Synced")).toBeTruthy();
|
|
262
|
+
expect(within(summary).getByText("Attention")).toBeTruthy();
|
|
263
|
+
expect(screen.queryByText("Actions")).toBeNull();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("shows an open action only when selection is available", () => {
|
|
267
|
+
render(<ProjectResourcePanel resources={resources} onSelect={() => undefined} />);
|
|
268
|
+
|
|
269
|
+
expect(screen.getByText("Actions")).toBeTruthy();
|
|
270
|
+
expect(screen.getAllByRole("button", { name: "Open" })).toHaveLength(3);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("@echothink-ui/project ProjectStatusSummary", () => {
|
|
275
|
+
it("includes attention statuses in the distribution legend", () => {
|
|
276
|
+
render(
|
|
277
|
+
<ProjectStatusSummary
|
|
278
|
+
summary={{
|
|
279
|
+
byStatus: { running: 1, "approval-required": 1 },
|
|
280
|
+
healthyPercent: 50,
|
|
281
|
+
blockedCount: 1
|
|
282
|
+
}}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(screen.getByText("Approval Required 1")).toBeTruthy();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("@echothink-ui/project ProjectTab", () => {
|
|
291
|
+
it("exports a project-scoped browser tab with tab semantics and status", () => {
|
|
292
|
+
render(<ProjectTab id="marketing" label="Marketing Campaign" status="active" active />);
|
|
293
|
+
|
|
294
|
+
expect(ProjectComponentNames).toContain("ProjectTab");
|
|
295
|
+
const tab = screen.getByRole("tab", { name: /Marketing Campaign/ });
|
|
296
|
+
expect(tab.getAttribute("aria-selected")).toBe("true");
|
|
297
|
+
expect(screen.getByText("Active")).toBeTruthy();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("@echothink-ui/project ProjectTabGroup", () => {
|
|
302
|
+
it("renders project-scoped tabs with status, active state, and close affordances", () => {
|
|
303
|
+
const handleActivate = vi.fn();
|
|
304
|
+
const handleClose = vi.fn();
|
|
305
|
+
|
|
306
|
+
const { container } = render(
|
|
307
|
+
<ProjectTabGroup
|
|
308
|
+
projectId="marketing"
|
|
309
|
+
projectLabel="Marketing Campaign"
|
|
310
|
+
activeTabId="dashboard"
|
|
311
|
+
tabs={[
|
|
312
|
+
{ id: "dashboard", label: "Dashboard", status: "active" },
|
|
313
|
+
{ id: "tasks", label: "Tasks", status: "running", dirty: true },
|
|
314
|
+
{ id: "brief", label: "Brief.md", status: "in-progress" }
|
|
315
|
+
]}
|
|
316
|
+
onActivateTab={handleActivate}
|
|
317
|
+
onCloseTab={handleClose}
|
|
318
|
+
/>
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(container.querySelector('[data-eth-component="ProjectTabGroup"]')).toBeTruthy();
|
|
322
|
+
expect(screen.getByRole("heading", { name: "Marketing Campaign" })).toBeTruthy();
|
|
323
|
+
expect(screen.getByRole("tablist", { name: "Marketing Campaign" })).toBeTruthy();
|
|
324
|
+
|
|
325
|
+
const activeTab = screen.getByRole("tab", { name: /Dashboard/ });
|
|
326
|
+
expect(activeTab.getAttribute("aria-selected")).toBe("true");
|
|
327
|
+
expect(screen.getByText("Active")).toBeTruthy();
|
|
328
|
+
expect(screen.getByText("Running")).toBeTruthy();
|
|
329
|
+
expect(screen.getByLabelText("Unsaved changes")).toBeTruthy();
|
|
330
|
+
|
|
331
|
+
fireEvent.click(screen.getByRole("tab", { name: /Tasks/ }));
|
|
332
|
+
expect(handleActivate).toHaveBeenCalledWith("tasks");
|
|
333
|
+
|
|
334
|
+
fireEvent.click(screen.getByRole("button", { name: "Close Brief.md" }));
|
|
335
|
+
expect(handleClose).toHaveBeenCalledWith("brief");
|
|
336
|
+
});
|
|
337
|
+
});
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "./styles.css";
|
|
2
|
+
|
|
3
|
+
export * from "./types";
|
|
4
|
+
export * from "./components/ProjectManagementPage";
|
|
5
|
+
export * from "./components/ProjectCard";
|
|
6
|
+
export * from "./components/ProjectTable";
|
|
7
|
+
export * from "./components/ProjectTab";
|
|
8
|
+
export * from "./components/ProjectStatusSummary";
|
|
9
|
+
export * from "./components/ProjectCreateForm";
|
|
10
|
+
export * from "./components/ProjectSummaryPanel";
|
|
11
|
+
export * from "./components/ProjectSettingsPanel";
|
|
12
|
+
export * from "./components/ProjectScopeSelector";
|
|
13
|
+
export * from "./components/ProjectResourcePanel";
|
|
14
|
+
export * from "./components/ProjectActivityTimeline";
|
|
15
|
+
export * from "./components/ProjectTabGroup";
|
|
16
|
+
export * from "./components/ProjectMembersPanel";
|
|
17
|
+
export * from "./components/ProjectPermissionPanel";
|
|
18
|
+
export * from "./components/ProjectAppDomainPanel";
|
|
19
|
+
export * from "./components/ProjectModelConfigPanel";
|
|
20
|
+
export * from "./components/ProjectDashboardTemplate";
|
|
21
|
+
|
|
22
|
+
export const ProjectComponentNames = [
|
|
23
|
+
"ProjectManagementPage",
|
|
24
|
+
"ProjectCard",
|
|
25
|
+
"ProjectTable",
|
|
26
|
+
"ProjectTab",
|
|
27
|
+
"ProjectStatusSummary",
|
|
28
|
+
"ProjectCreateForm",
|
|
29
|
+
"ProjectSummaryPanel",
|
|
30
|
+
"ProjectSettingsPanel",
|
|
31
|
+
"ProjectScopeSelector",
|
|
32
|
+
"ProjectResourcePanel",
|
|
33
|
+
"ProjectActivityTimeline",
|
|
34
|
+
"ProjectTabGroup",
|
|
35
|
+
"ProjectMembersPanel",
|
|
36
|
+
"ProjectPermissionPanel",
|
|
37
|
+
"ProjectAppDomainPanel",
|
|
38
|
+
"ProjectModelConfigPanel",
|
|
39
|
+
"ProjectDashboardTemplate"
|
|
40
|
+
] as const;
|
|
41
|
+
export type ProjectComponentName = (typeof ProjectComponentNames)[number];
|