@nextclaw/ui 0.24.0-beta.0 → 0.24.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 +40 -0
- package/dist/assets/api-BqVIq0lF.js +13 -0
- package/dist/assets/{appearance-settings-page-CPoFZLw8.js → appearance-settings-page-CTWn1eAT.js} +1 -1
- package/dist/assets/{book-open-jcUt0rr9.js → book-open-o2x7OLiO.js} +1 -1
- package/dist/assets/{channels-list-page-anMJAkfK.js → channels-list-page-Bzn-pzyJ.js} +1 -1
- package/dist/assets/{config-split-page-AcuFnbIC.js → config-split-page-CnRbXXlh.js} +1 -1
- package/dist/assets/{confirm-dialog-B-atCfC5.js → confirm-dialog-B_Hbcra2.js} +1 -1
- package/dist/assets/{createLucideIcon-Blz0FzrV.js → createLucideIcon-rwwpqyks.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-8lKF_Ob-.js → desktop-capabilities-page-C2ayYQTr.js} +1 -1
- package/dist/assets/{desktop-update-config-DABXDpbj.js → desktop-update-config-C13czpyL.js} +1 -1
- package/dist/assets/{dist-eH-_j8yU.js → dist-BsF3hfOi.js} +1 -1
- package/dist/assets/{dist-D8kcEpTf.js → dist-aNN3iQxV.js} +1 -1
- package/dist/assets/{doc-browser-DzXRehlW.js → doc-browser-D8dIOo9E.js} +1 -1
- package/dist/assets/doc-browser-DcuOOtpP.js +1 -0
- package/dist/assets/{doc-browser-context-BlbUcg2J.js → doc-browser-context-Dhsit2Ma.js} +1 -1
- package/dist/assets/{extensions-mquwAJ2Z.js → extensions-CD6xvKF_.js} +1 -1
- package/dist/assets/{form-actions-CaZDGEmB.js → form-actions-CC4Wz_lL.js} +1 -1
- package/dist/assets/index-BTsbdtv6.js +109 -0
- package/dist/assets/index-Bcfy2kTh.css +1 -0
- package/dist/assets/loader-circle-B6aFbB-8.js +1 -0
- package/dist/assets/mcp-marketplace-page-BtUAypA7.js +1 -0
- package/dist/assets/{mcp-marketplace-page-gFtgyueL.js → mcp-marketplace-page-r2MmD9xO.js} +1 -1
- package/dist/assets/{model-config-page-hFBpeRAO.js → model-config-page-DboxoT-I.js} +1 -1
- package/dist/assets/{navigation-link-Hb5Z0UxZ.js → navigation-link-C_6oj3zz.js} +1 -1
- package/dist/assets/plus-DRsq9Kvs.js +1 -0
- package/dist/assets/{privacy-settings-page-C18zo3ox.js → privacy-settings-page-C2sDBqhb.js} +1 -1
- package/dist/assets/projects-BcPcx-uD.js +1 -0
- package/dist/assets/{providers-config-page-YU0-AOXJ.js → providers-config-page-CmUFl9RF.js} +1 -1
- package/dist/assets/react-BjO9I4WZ.js +8 -0
- package/dist/assets/{refresh-cw-ZHKfE3yx.js → refresh-cw-DsT2kvXi.js} +1 -1
- package/dist/assets/remote-Ci6x91ES.js +1 -0
- package/dist/assets/{rotate-cw-CLhOXGwE.js → rotate-cw-BCAs6c3R.js} +1 -1
- package/dist/assets/{runtime-config-page-Dmy1A_0Y.js → runtime-config-page-cQKNeAjL.js} +1 -1
- package/dist/assets/{save-CWuuZs77.js → save-CadRMTMQ.js} +1 -1
- package/dist/assets/{search-DcmsKAn2.js → search-B6tB4fzB.js} +1 -1
- package/dist/assets/{search-config-page-CxBacx6W.js → search-config-page-C8lOIkxn.js} +1 -1
- package/dist/assets/{secrets-config-page-B4py8-KR.js → secrets-config-page-8clzAag2.js} +1 -1
- package/dist/assets/security-config-DPbM6IVT.js +1 -0
- package/dist/assets/{select-BEr0Iy_f.js → select-CO9S_C2C.js} +1 -1
- package/dist/assets/skeleton-IHILNhIW.js +1 -0
- package/dist/assets/{tag-chip-ByqDkn6g.js → tag-chip-BZKk0UkK.js} +1 -1
- package/dist/assets/x-BuvHIb1d.js +1 -0
- package/dist/index.html +21 -21
- package/package.json +6 -6
- package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +362 -241
- package/src/features/apps/components/app-document-access-section.tsx +187 -0
- package/src/features/apps/components/app-package-card.tsx +150 -275
- package/src/features/apps/hooks/use-app-packages.ts +91 -35
- package/src/features/chat/index.ts +5 -1
- package/src/features/projects/AGENTS.md +3 -2
- package/src/features/projects/components/project-overview.tsx +70 -139
- package/src/features/projects/components/work/project-work-item-drawer.test.tsx +65 -0
- package/src/features/projects/components/work/project-work-item-drawer.tsx +367 -0
- package/src/features/projects/components/work/project-work-items.test.tsx +98 -0
- package/src/features/projects/components/work/project-work-items.tsx +199 -0
- package/src/features/projects/components/work/project-work-state-settings.tsx +285 -0
- package/src/features/projects/hooks/use-project-observation.ts +56 -26
- package/src/features/projects/hooks/use-project-work.test.tsx +58 -0
- package/src/features/projects/hooks/use-project-work.ts +215 -0
- package/src/features/projects/pages/project-home-page.tsx +119 -138
- package/src/features/projects/pages/projects-page.test.tsx +79 -308
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +16 -1
- package/src/shared/lib/i18n/locales/en-US/projects.json +60 -1
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +16 -1
- package/src/shared/lib/i18n/locales/zh-CN/projects.json +60 -1
- package/dist/assets/api-CoqPRKmF.js +0 -13
- package/dist/assets/doc-browser-DJEMr7Zk.js +0 -1
- package/dist/assets/index-DyR5iR0O.css +0 -1
- package/dist/assets/index-XxXj8WDS.js +0 -109
- package/dist/assets/loader-circle-0qPOOYQ5.js +0 -1
- package/dist/assets/mcp-marketplace-page-uanamJ6L.js +0 -1
- package/dist/assets/plus-CYOvNvcV.js +0 -1
- package/dist/assets/projects-C8Gm9RS8.js +0 -1
- package/dist/assets/react-DF-kP7iN.js +0 -8
- package/dist/assets/remote-B_utkBra.js +0 -1
- package/dist/assets/security-config-CTP4IQWw.js +0 -1
- package/dist/assets/skeleton-B2NHluWf.js +0 -1
- package/dist/assets/x-CENkFyK5.js +0 -1
- package/src/features/projects/components/project-work-items.test.tsx +0 -78
- package/src/features/projects/components/project-work-items.tsx +0 -272
|
@@ -2,14 +2,12 @@ import { fireEvent, render, screen } from "@testing-library/react";
|
|
|
2
2
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
3
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
4
4
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import type { ProjectObservationSnapshot } from "@nextclaw/client-sdk";
|
|
6
5
|
import { ProjectsPage } from "./project-home-page";
|
|
7
6
|
|
|
8
7
|
const mocks = vi.hoisted(() => ({
|
|
9
|
-
|
|
8
|
+
drawerProps: vi.fn(),
|
|
10
9
|
useProjectObservation: vi.fn(),
|
|
11
|
-
|
|
12
|
-
createSession: vi.fn(),
|
|
10
|
+
useProjects: vi.fn(),
|
|
13
11
|
}));
|
|
14
12
|
|
|
15
13
|
vi.mock("@/shared/hooks/use-projects", () => ({
|
|
@@ -22,345 +20,118 @@ vi.mock("@/features/projects/hooks/use-project-observation", () => ({
|
|
|
22
20
|
],
|
|
23
21
|
useProjectObservation: mocks.useProjectObservation,
|
|
24
22
|
}));
|
|
25
|
-
vi.mock("@/features/chat
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
chatSessionListManager: { createSession: mocks.createSession },
|
|
29
|
-
}),
|
|
23
|
+
vi.mock("@/features/chat", () => ({
|
|
24
|
+
ChatConversationWorkspaceSection: () => <div data-testid="workspace" />,
|
|
25
|
+
usePresenter: () => ({ chatThreadManager: { openFilePreview: vi.fn() } }),
|
|
30
26
|
}));
|
|
31
|
-
vi.mock("@/
|
|
32
|
-
|
|
27
|
+
vi.mock("@/app/hooks/use-viewport-layout", () => ({
|
|
28
|
+
useViewportLayout: () => ({ isMobile: false }),
|
|
33
29
|
}));
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
vi.mock("@/features/projects/components/project-overview", () => ({
|
|
31
|
+
ProjectOverview: ({
|
|
32
|
+
onOpenWorkItem,
|
|
33
|
+
}: {
|
|
34
|
+
onOpenWorkItem: (id: string) => void;
|
|
35
|
+
}) => (
|
|
36
|
+
<button onClick={() => onOpenWorkItem("work-overview")}>
|
|
37
|
+
Overview work
|
|
38
|
+
</button>
|
|
39
|
+
),
|
|
40
|
+
}));
|
|
41
|
+
vi.mock("@/features/projects/components/work/project-work-items", () => ({
|
|
42
|
+
ProjectWorkItems: ({
|
|
43
|
+
onOpenWorkItem,
|
|
44
|
+
}: {
|
|
45
|
+
onOpenWorkItem: (id: string) => void;
|
|
46
|
+
}) => <button onClick={() => onOpenWorkItem("work-list")}>List work</button>,
|
|
47
|
+
}));
|
|
48
|
+
vi.mock("@/features/projects/components/work/project-work-item-drawer", () => ({
|
|
49
|
+
ProjectWorkItemDrawer: ({ workItemId }: { workItemId: string | null }) => {
|
|
50
|
+
mocks.drawerProps({ workItemId });
|
|
51
|
+
return <div data-testid="work-drawer">{workItemId}</div>;
|
|
53
52
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
id: "skills",
|
|
81
|
-
label: "Project Skills",
|
|
82
|
-
status: "empty",
|
|
83
|
-
itemCount: 0,
|
|
84
|
-
observedAt,
|
|
85
|
-
diagnosticIds: [],
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
workflows: [],
|
|
89
|
-
runs: [],
|
|
90
|
-
workItems: [],
|
|
91
|
-
artifactCategories: [
|
|
92
|
-
{ id: "designs", label: "Designs" },
|
|
93
|
-
{ id: "plans", label: "Plans" },
|
|
94
|
-
],
|
|
95
|
-
artifacts: [
|
|
96
|
-
{
|
|
97
|
-
id: "designs:docs/designs/observer.md",
|
|
98
|
-
path: "docs/designs/observer.md",
|
|
99
|
-
categoryId: "designs",
|
|
100
|
-
categoryLabel: "Designs",
|
|
101
|
-
exists: true,
|
|
102
|
-
references: [],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: "plans:docs/plans/delivery.md",
|
|
106
|
-
path: "docs/plans/delivery.md",
|
|
107
|
-
categoryId: "plans",
|
|
108
|
-
categoryLabel: "Plans",
|
|
109
|
-
exists: true,
|
|
110
|
-
references: [],
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
signals: [],
|
|
114
|
-
requests: [],
|
|
115
|
-
activity: [],
|
|
116
|
-
skills: [
|
|
117
|
-
{
|
|
118
|
-
ref: "project:research-skill",
|
|
119
|
-
name: "Research skill",
|
|
120
|
-
description: "Collects and organizes source material.",
|
|
121
|
-
source: "project",
|
|
122
|
-
path: "/tmp/research/.agents/skills/research/SKILL.md",
|
|
123
|
-
readable: true,
|
|
124
|
-
reference: {
|
|
125
|
-
kind: "project-config",
|
|
126
|
-
label: "Project config",
|
|
127
|
-
observedAt,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
ref: "project:unavailable-skill",
|
|
132
|
-
name: "Unavailable skill",
|
|
133
|
-
source: "project",
|
|
134
|
-
path: "/tmp/research/.agents/skills/unavailable/SKILL.md",
|
|
135
|
-
readable: false,
|
|
136
|
-
reference: {
|
|
137
|
-
kind: "project-config",
|
|
138
|
-
label: "Project config",
|
|
139
|
-
observedAt,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
diagnostics: [
|
|
144
|
-
{
|
|
145
|
-
id: "config:broken",
|
|
146
|
-
source: "config",
|
|
147
|
-
level: "error",
|
|
148
|
-
code: "PROJECT_CONFIG_PARSE_FAILED",
|
|
149
|
-
message: "The project configuration is invalid.",
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
dataQuality: "partial",
|
|
153
|
-
};
|
|
53
|
+
}));
|
|
54
|
+
vi.mock("@/features/projects/components/project-artifacts", () => ({
|
|
55
|
+
ProjectArtifacts: () => <div>Artifact content</div>,
|
|
56
|
+
}));
|
|
57
|
+
vi.mock("@/features/projects/components/project-skills", () => ({
|
|
58
|
+
ProjectSkills: () => <div>Skills content</div>,
|
|
59
|
+
}));
|
|
60
|
+
vi.mock("@/features/projects/components/project-agreement", () => ({
|
|
61
|
+
ProjectAgreement: () => <div>Agreement content</div>,
|
|
62
|
+
}));
|
|
63
|
+
vi.mock("@/features/projects/components/project-requests", () => ({
|
|
64
|
+
ProjectRequests: () => null,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
function renderPage(path: string) {
|
|
68
|
+
return render(
|
|
69
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
70
|
+
<MemoryRouter initialEntries={[path]}>
|
|
71
|
+
<Routes>
|
|
72
|
+
<Route path="/projects" element={<ProjectsPage />} />
|
|
73
|
+
<Route path="/projects/:projectId/:tab" element={<ProjectsPage />} />
|
|
74
|
+
</Routes>
|
|
75
|
+
</MemoryRouter>
|
|
76
|
+
</QueryClientProvider>,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
154
79
|
|
|
155
80
|
describe("ProjectsPage", () => {
|
|
156
81
|
beforeEach(() => {
|
|
157
|
-
mocks.
|
|
82
|
+
mocks.drawerProps.mockReset();
|
|
158
83
|
mocks.useProjects.mockReturnValue({
|
|
159
84
|
data: {
|
|
160
85
|
projects: [
|
|
161
|
-
{
|
|
162
|
-
id: "project-research",
|
|
163
|
-
name: "Research",
|
|
164
|
-
rootPath: "/tmp/research",
|
|
165
|
-
createdAt: observedAt,
|
|
166
|
-
updatedAt: observedAt,
|
|
167
|
-
},
|
|
86
|
+
{ id: "project-1", name: "Research", rootPath: "/tmp/research" },
|
|
168
87
|
],
|
|
169
88
|
},
|
|
170
89
|
isLoading: false,
|
|
171
90
|
isError: false,
|
|
172
91
|
});
|
|
173
92
|
mocks.useProjectObservation.mockReturnValue({
|
|
174
|
-
data:
|
|
93
|
+
data: undefined,
|
|
175
94
|
isLoading: false,
|
|
176
95
|
isError: false,
|
|
177
96
|
});
|
|
178
97
|
});
|
|
179
98
|
|
|
180
|
-
it("
|
|
181
|
-
|
|
182
|
-
<QueryClientProvider client={new QueryClient()}>
|
|
183
|
-
<MemoryRouter initialEntries={["/projects/project-research/overview"]}>
|
|
184
|
-
<Routes>
|
|
185
|
-
<Route path="/projects/:projectId/:tab" element={<ProjectsPage />} />
|
|
186
|
-
</Routes>
|
|
187
|
-
</MemoryRouter>
|
|
188
|
-
</QueryClientProvider>,
|
|
189
|
-
);
|
|
99
|
+
it("keeps overview scan-free and opens its work item in the unified drawer", () => {
|
|
100
|
+
renderPage("/projects/project-1/overview");
|
|
190
101
|
|
|
191
102
|
expect(mocks.useProjectObservation).toHaveBeenCalledWith(
|
|
192
|
-
"project-
|
|
103
|
+
"project-1",
|
|
193
104
|
"/tmp/research",
|
|
105
|
+
false,
|
|
194
106
|
);
|
|
195
|
-
expect(screen.queryByRole("combobox")).toBeNull();
|
|
196
107
|
expect(screen.getAllByRole("tab")).toHaveLength(5);
|
|
197
|
-
|
|
198
|
-
expect(
|
|
199
|
-
screen.queryByText(
|
|
200
|
-
/This project has no configured summary|项目没有配置摘要/,
|
|
201
|
-
),
|
|
202
|
-
).toBeNull();
|
|
203
|
-
expect(screen.queryByText(/Partially available|部分可用/)).toBeNull();
|
|
204
|
-
expect(screen.queryByText(observedAt)).toBeNull();
|
|
205
|
-
expect(
|
|
206
|
-
screen.getByText(
|
|
207
|
-
/Set up project observation first|先建立项目观察配置/,
|
|
208
|
-
),
|
|
209
|
-
).toBeTruthy();
|
|
210
|
-
fireEvent.click(
|
|
211
|
-
screen.getByRole("button", {
|
|
212
|
-
name: /Let AI set up project observation|让 AI 帮我建立项目观察/,
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
expect(mocks.createSession).toHaveBeenCalledWith({
|
|
216
|
-
projectRoot: "/tmp/research",
|
|
217
|
-
prompt: expect.stringMatching(/project observation|项目观察/),
|
|
218
|
-
});
|
|
219
|
-
expect(mocks.createSession).toHaveBeenCalledWith(
|
|
220
|
-
expect.objectContaining({
|
|
221
|
-
prompt: expect.stringContaining("AGENTS.md"),
|
|
222
|
-
}),
|
|
223
|
-
);
|
|
224
|
-
fireEvent.click(screen.getByRole("button", { name: /docs\/designs\/observer\.md/ }));
|
|
225
|
-
expect(mocks.openFilePreview).toHaveBeenCalledWith({
|
|
226
|
-
path: "docs/designs/observer.md",
|
|
227
|
-
label: "docs/designs/observer.md",
|
|
228
|
-
viewMode: "preview",
|
|
229
|
-
previewViewer: "rendered",
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
fireEvent.click(screen.getByRole("tab", { name: /Work items|工作项/ }));
|
|
233
|
-
expect(
|
|
234
|
-
screen.queryByRole("heading", { name: /Work items|工作项/ }),
|
|
235
|
-
).toBeNull();
|
|
236
|
-
|
|
237
|
-
fireEvent.click(screen.getByRole("tab", { name: /Artifacts|产物/ }));
|
|
238
|
-
expect(
|
|
239
|
-
screen.queryByRole("heading", { name: /Artifacts|产物/ }),
|
|
240
|
-
).toBeNull();
|
|
241
|
-
expect(screen.getByRole("button", { name: /Designs 1/ })).toBeTruthy();
|
|
242
|
-
expect(screen.getByRole("button", { name: /docs\/plans\/delivery\.md/ })).toBeTruthy();
|
|
243
|
-
expect(screen.queryByText(/File observed|文件已观测/)).toBeNull();
|
|
244
|
-
|
|
245
|
-
fireEvent.click(screen.getByRole("tab", { name: /Skills/ }));
|
|
246
|
-
expect(screen.getByText("2 skills")).toBeTruthy();
|
|
247
|
-
expect(screen.queryByRole("heading", { name: "Skills" })).toBeNull();
|
|
248
|
-
const skillPath = screen.getByText(
|
|
249
|
-
"/tmp/research/.agents/skills/research/SKILL.md",
|
|
250
|
-
);
|
|
251
|
-
expect(skillPath.className).toContain("truncate");
|
|
252
|
-
expect(skillPath.getAttribute("title")).toBe(
|
|
253
|
-
"/tmp/research/.agents/skills/research/SKILL.md",
|
|
254
|
-
);
|
|
255
|
-
const skillDescription = screen.getByText(
|
|
256
|
-
"Collects and organizes source material.",
|
|
257
|
-
);
|
|
258
|
-
expect(skillDescription.className).toContain("line-clamp-2");
|
|
259
|
-
expect(skillDescription.getAttribute("title")).toBe(
|
|
260
|
-
"Collects and organizes source material.",
|
|
261
|
-
);
|
|
262
|
-
expect(screen.queryByText(/Readable|可读取/)).toBeNull();
|
|
263
|
-
expect(screen.getByText(/Unavailable|不可用/)).toBeTruthy();
|
|
264
|
-
fireEvent.click(screen.getByRole("button", { name: /Research skill/ }));
|
|
265
|
-
expect(mocks.openFilePreview).toHaveBeenCalledWith({
|
|
266
|
-
path: "/tmp/research/.agents/skills/research/SKILL.md",
|
|
267
|
-
label: "Research skill",
|
|
268
|
-
viewMode: "preview",
|
|
269
|
-
previewViewer: "rendered",
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
fireEvent.click(
|
|
273
|
-
screen.getByRole("tab", { name: /Working rules|工作约定/ }),
|
|
274
|
-
);
|
|
275
|
-
expect(
|
|
276
|
-
screen.queryByRole("heading", { name: /Working rules|工作约定/ }),
|
|
277
|
-
).toBeNull();
|
|
278
|
-
expect(
|
|
279
|
-
screen.getByRole("heading", { name: /Vision and context|愿景与上下文/ }),
|
|
280
|
-
).toBeTruthy();
|
|
281
|
-
fireEvent.click(screen.getByRole("button", { name: /Project vision/ }));
|
|
282
|
-
expect(mocks.openFilePreview).toHaveBeenCalledWith({
|
|
283
|
-
path: "docs/VISION.md",
|
|
284
|
-
label: "Project vision",
|
|
285
|
-
viewMode: "preview",
|
|
286
|
-
previewViewer: "rendered",
|
|
287
|
-
});
|
|
288
|
-
expect(screen.getByText("PROJECT_CONFIG_PARSE_FAILED")).toBeTruthy();
|
|
108
|
+
fireEvent.click(screen.getByRole("button", { name: "Overview work" }));
|
|
109
|
+
expect(screen.getByTestId("work-drawer").textContent).toBe("work-overview");
|
|
289
110
|
});
|
|
290
111
|
|
|
291
|
-
it("
|
|
292
|
-
const { runs: _runs, ...legacySnapshot } = snapshot;
|
|
112
|
+
it("enables legacy observation only for preserved artifact, skill, and agreement surfaces", () => {
|
|
293
113
|
mocks.useProjectObservation.mockReturnValue({
|
|
294
|
-
data:
|
|
114
|
+
data: { requests: [] },
|
|
295
115
|
isLoading: false,
|
|
296
116
|
isError: false,
|
|
297
117
|
});
|
|
118
|
+
renderPage("/projects/project-1/artifacts");
|
|
298
119
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<Route path="/projects/:projectId/:tab" element={<ProjectsPage />} />
|
|
304
|
-
</Routes>
|
|
305
|
-
</MemoryRouter>
|
|
306
|
-
</QueryClientProvider>,
|
|
120
|
+
expect(mocks.useProjectObservation).toHaveBeenCalledWith(
|
|
121
|
+
"project-1",
|
|
122
|
+
"/tmp/research",
|
|
123
|
+
true,
|
|
307
124
|
);
|
|
308
|
-
|
|
309
|
-
expect(screen.
|
|
310
|
-
expect(
|
|
125
|
+
expect(screen.getByText("Artifact content")).toBeTruthy();
|
|
126
|
+
expect(screen.getByRole("tab", { name: /Skills/ })).toBeTruthy();
|
|
127
|
+
expect(
|
|
128
|
+
screen.getByRole("tab", { name: /Working rules|工作约定/ }),
|
|
129
|
+
).toBeTruthy();
|
|
311
130
|
});
|
|
312
131
|
|
|
313
|
-
it("asks the user to choose from the sidebar when
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<MemoryRouter initialEntries={["/projects"]}>
|
|
317
|
-
<Routes>
|
|
318
|
-
<Route path="/projects" element={<ProjectsPage />} />
|
|
319
|
-
</Routes>
|
|
320
|
-
</MemoryRouter>
|
|
321
|
-
</QueryClientProvider>,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
expect(mocks.useProjectObservation).toHaveBeenLastCalledWith(null, null);
|
|
132
|
+
it("asks the user to choose from the sidebar when no project route is selected", () => {
|
|
133
|
+
renderPage("/projects");
|
|
134
|
+
expect(mocks.useProjectObservation).toHaveBeenCalledWith(null, null, false);
|
|
325
135
|
expect(screen.getByText(/Choose a project|选择项目/)).toBeTruthy();
|
|
326
|
-
expect(screen.queryByRole("combobox")).toBeNull();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("keeps the observation setup entry out of an already configured project", () => {
|
|
330
|
-
mocks.useProjectObservation.mockReturnValue({
|
|
331
|
-
data: {
|
|
332
|
-
...snapshot,
|
|
333
|
-
sources: snapshot.sources.map((source) =>
|
|
334
|
-
source.id === "config" ? { ...source, status: "available" as const } : source,
|
|
335
|
-
),
|
|
336
|
-
},
|
|
337
|
-
isLoading: false,
|
|
338
|
-
isError: false,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
render(
|
|
342
|
-
<QueryClientProvider client={new QueryClient()}>
|
|
343
|
-
<MemoryRouter initialEntries={["/projects/project-research/overview"]}>
|
|
344
|
-
<Routes>
|
|
345
|
-
<Route path="/projects/:projectId/:tab" element={<ProjectsPage />} />
|
|
346
|
-
</Routes>
|
|
347
|
-
</MemoryRouter>
|
|
348
|
-
</QueryClientProvider>,
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
expect(
|
|
352
|
-
screen.queryByRole("button", {
|
|
353
|
-
name: /Let AI set up project observation|让 AI 帮我建立项目观察/,
|
|
354
|
-
}),
|
|
355
|
-
).toBeNull();
|
|
356
|
-
fireEvent.click(
|
|
357
|
-
screen.getByRole("button", {
|
|
358
|
-
name: /Start project work|开始项目工作/,
|
|
359
|
-
}),
|
|
360
|
-
);
|
|
361
|
-
expect(mocks.createSession).toHaveBeenCalledWith({
|
|
362
|
-
projectRoot: "/tmp/research",
|
|
363
|
-
prompt: expect.stringMatching(/Start working on this project|请开始处理这个项目/),
|
|
364
|
-
});
|
|
365
136
|
});
|
|
366
137
|
});
|
|
@@ -251,5 +251,20 @@
|
|
|
251
251
|
"serviceActionAuthorizationRisk": "Risk",
|
|
252
252
|
"serviceActionAuthorizationInput": "Input",
|
|
253
253
|
"serviceActionAuthorizationReject": "Reject",
|
|
254
|
-
"serviceActionAuthorizationAllow": "Allow"
|
|
254
|
+
"serviceActionAuthorizationAllow": "Allow",
|
|
255
|
+
"appPackagesDocumentAccessTitle": "Files and folders",
|
|
256
|
+
"appPackagesDocumentAccessUngranted": "Not granted",
|
|
257
|
+
"appPackagesDocumentAccessUnavailable": "Folder unavailable",
|
|
258
|
+
"appPackagesDocumentAccessRead": "Read only",
|
|
259
|
+
"appPackagesDocumentAccessReadWrite": "Read and write",
|
|
260
|
+
"appPackagesDocumentAccessMode": "Access mode",
|
|
261
|
+
"appPackagesDocumentAccessChoose": "Choose folder",
|
|
262
|
+
"appPackagesDocumentAccessReplace": "Replace",
|
|
263
|
+
"appPackagesDocumentAccessRevoke": "Revoke access",
|
|
264
|
+
"appPackagesDocumentAccessGrant": "Grant this folder",
|
|
265
|
+
"appPackagesDocumentAccessChooseTitle": "Choose a folder on the runtime host",
|
|
266
|
+
"appPackagesDocumentAccessChooseDescription": "Choose the folder this app may access.",
|
|
267
|
+
"appPackagesDocumentAccessPath": "Granted folder",
|
|
268
|
+
"appPackagesDocumentAccessWriteWarning": "Read and write access lets this app modify the original files in this folder. You can revoke it from the app card at any time.",
|
|
269
|
+
"appPackagesDocumentAccessReadHint": "Read-only access lets this app view the folder but not modify files through this mount."
|
|
255
270
|
}
|
|
@@ -86,5 +86,64 @@
|
|
|
86
86
|
"projectsStatus_cancelled": "Cancelled",
|
|
87
87
|
"projectsSource_available": "Available",
|
|
88
88
|
"projectsSource_empty": "No data",
|
|
89
|
-
"projectsSource_error": "Failed"
|
|
89
|
+
"projectsSource_error": "Failed",
|
|
90
|
+
"projectsWorkNewItem": "Add a work item",
|
|
91
|
+
"projectsWorkEmpty": "No work items yet. Add one here or ask AI from a project conversation.",
|
|
92
|
+
"projectsWorkShowDeleted": "Show deleted work items",
|
|
93
|
+
"projectsWorkTitle": "Title",
|
|
94
|
+
"projectsWorkDescription": "Description",
|
|
95
|
+
"projectsWorkAttention": "Attention",
|
|
96
|
+
"projectsWorkAttention_none": "No special attention",
|
|
97
|
+
"projectsWorkAttention_blocked": "Blocked",
|
|
98
|
+
"projectsWorkAttention_awaiting-user": "Waiting for you",
|
|
99
|
+
"projectsWorkVersion": "Version",
|
|
100
|
+
"projectsWorkItemSaved": "Work item saved",
|
|
101
|
+
"projectsWorkItemSaveFailed": "Failed to save work item",
|
|
102
|
+
"projectsWorkItemLoadFailed": "Failed to load work item details",
|
|
103
|
+
"projectsWorkItemCreateFailed": "Failed to create work item",
|
|
104
|
+
"projectsDeleteWorkItem": "Delete work item?",
|
|
105
|
+
"projectsDeleteWorkItemDescription": "The work item will remain available under Show deleted and can be restored.",
|
|
106
|
+
"projectsDeleted": "Deleted",
|
|
107
|
+
"projectsDelete": "Delete",
|
|
108
|
+
"projectsRestore": "Restore",
|
|
109
|
+
"projectsCreate": "Create",
|
|
110
|
+
"projectsAdd": "Add",
|
|
111
|
+
"projectsWorkArtifacts": "Linked artifacts",
|
|
112
|
+
"projectsWorkNoArtifacts": "No artifacts linked",
|
|
113
|
+
"projectsWorkArtifactPath": "Project-relative file path",
|
|
114
|
+
"projectsWorkArtifactLabel": "Optional label",
|
|
115
|
+
"projectsWorkLinkArtifact": "Link",
|
|
116
|
+
"projectsWorkUnlinkArtifact": "Unlink artifact",
|
|
117
|
+
"projectsWorkArtifactLinkFailed": "Failed to link artifact",
|
|
118
|
+
"projectsWorkActionFailed": "Could not update project work",
|
|
119
|
+
"projectsWorkStateSettings": "Work states",
|
|
120
|
+
"projectsWorkStateSettingsDescription": "Customize names and ordering while keeping each state's lifecycle category explicit.",
|
|
121
|
+
"projectsWorkStateName": "State name",
|
|
122
|
+
"projectsWorkStateCategory": "Lifecycle category",
|
|
123
|
+
"projectsWorkDefaultState": "Use for new work items",
|
|
124
|
+
"projectsWorkNewState": "New state name",
|
|
125
|
+
"projectsWorkStateMigration": "Move existing work items to",
|
|
126
|
+
"projectsWorkStateMigrationOptional": "Choose when this state is in use",
|
|
127
|
+
"projectsDeleteState": "Delete state",
|
|
128
|
+
"projectsDeleteStateDescription": "Existing work items must be moved to another state before this state can be deleted.",
|
|
129
|
+
"projectsMoveUp": "Move state up",
|
|
130
|
+
"projectsMoveDown": "Move state down",
|
|
131
|
+
"projectsWorkCategory_backlog": "Backlog",
|
|
132
|
+
"projectsWorkCategory_unstarted": "Not started",
|
|
133
|
+
"projectsWorkCategory_started": "In progress",
|
|
134
|
+
"projectsWorkCategory_completed": "Completed",
|
|
135
|
+
"projectsWorkCategory_canceled": "Canceled",
|
|
136
|
+
"projectsWorkSummary": "Work summary",
|
|
137
|
+
"projectsWorkTotal": "Total",
|
|
138
|
+
"projectsWorkActive": "Active",
|
|
139
|
+
"projectsWorkNeedsAttention": "Needs attention",
|
|
140
|
+
"projectsWorkCompleted": "Completed",
|
|
141
|
+
"projectsWorkRecentlyUpdated": "Recently updated",
|
|
142
|
+
"projectsWorkActivity_created": "Created",
|
|
143
|
+
"projectsWorkActivity_updated": "Updated",
|
|
144
|
+
"projectsWorkActivity_state-changed": "State changed",
|
|
145
|
+
"projectsWorkActivity_deleted": "Deleted",
|
|
146
|
+
"projectsWorkActivity_restored": "Restored",
|
|
147
|
+
"projectsWorkActivity_artifact-linked": "Artifact linked",
|
|
148
|
+
"projectsWorkActivity_artifact-unlinked": "Artifact unlinked"
|
|
90
149
|
}
|
|
@@ -251,5 +251,20 @@
|
|
|
251
251
|
"serviceActionAuthorizationRisk": "风险",
|
|
252
252
|
"serviceActionAuthorizationInput": "输入",
|
|
253
253
|
"serviceActionAuthorizationReject": "拒绝",
|
|
254
|
-
"serviceActionAuthorizationAllow": "允许"
|
|
254
|
+
"serviceActionAuthorizationAllow": "允许",
|
|
255
|
+
"appPackagesDocumentAccessTitle": "文件与文件夹",
|
|
256
|
+
"appPackagesDocumentAccessUngranted": "未授权",
|
|
257
|
+
"appPackagesDocumentAccessUnavailable": "目录不可用",
|
|
258
|
+
"appPackagesDocumentAccessRead": "只读",
|
|
259
|
+
"appPackagesDocumentAccessReadWrite": "读写",
|
|
260
|
+
"appPackagesDocumentAccessMode": "访问模式",
|
|
261
|
+
"appPackagesDocumentAccessChoose": "选择目录",
|
|
262
|
+
"appPackagesDocumentAccessReplace": "替换",
|
|
263
|
+
"appPackagesDocumentAccessRevoke": "撤销访问",
|
|
264
|
+
"appPackagesDocumentAccessGrant": "授权此目录",
|
|
265
|
+
"appPackagesDocumentAccessChooseTitle": "选择运行主机上的目录",
|
|
266
|
+
"appPackagesDocumentAccessChooseDescription": "选择此应用可以访问的目录。",
|
|
267
|
+
"appPackagesDocumentAccessPath": "授权目录",
|
|
268
|
+
"appPackagesDocumentAccessWriteWarning": "读写授权允许应用直接修改这个目录中的原始文件;你可以随时在应用卡片中撤销。",
|
|
269
|
+
"appPackagesDocumentAccessReadHint": "只读授权允许应用查看这个目录,但不能通过此挂载修改文件。"
|
|
255
270
|
}
|
|
@@ -86,5 +86,64 @@
|
|
|
86
86
|
"projectsStatus_cancelled": "已取消",
|
|
87
87
|
"projectsSource_available": "可用",
|
|
88
88
|
"projectsSource_empty": "暂无数据",
|
|
89
|
-
"projectsSource_error": "读取失败"
|
|
89
|
+
"projectsSource_error": "读取失败",
|
|
90
|
+
"projectsWorkNewItem": "添加工作项",
|
|
91
|
+
"projectsWorkEmpty": "还没有工作项。你可以在这里添加,也可以在项目会话中让 AI 创建。",
|
|
92
|
+
"projectsWorkShowDeleted": "显示已删除的工作项",
|
|
93
|
+
"projectsWorkTitle": "标题",
|
|
94
|
+
"projectsWorkDescription": "说明",
|
|
95
|
+
"projectsWorkAttention": "关注状态",
|
|
96
|
+
"projectsWorkAttention_none": "无需特别关注",
|
|
97
|
+
"projectsWorkAttention_blocked": "受阻",
|
|
98
|
+
"projectsWorkAttention_awaiting-user": "等待你处理",
|
|
99
|
+
"projectsWorkVersion": "版本",
|
|
100
|
+
"projectsWorkItemSaved": "工作项已保存",
|
|
101
|
+
"projectsWorkItemSaveFailed": "工作项保存失败",
|
|
102
|
+
"projectsWorkItemLoadFailed": "工作项详情读取失败",
|
|
103
|
+
"projectsWorkItemCreateFailed": "工作项创建失败",
|
|
104
|
+
"projectsDeleteWorkItem": "删除这个工作项?",
|
|
105
|
+
"projectsDeleteWorkItemDescription": "删除后仍可通过“显示已删除的工作项”找到并恢复。",
|
|
106
|
+
"projectsDeleted": "已删除",
|
|
107
|
+
"projectsDelete": "删除",
|
|
108
|
+
"projectsRestore": "恢复",
|
|
109
|
+
"projectsCreate": "创建",
|
|
110
|
+
"projectsAdd": "添加",
|
|
111
|
+
"projectsWorkArtifacts": "关联产物",
|
|
112
|
+
"projectsWorkNoArtifacts": "还没有关联产物",
|
|
113
|
+
"projectsWorkArtifactPath": "项目内的文件路径",
|
|
114
|
+
"projectsWorkArtifactLabel": "显示名称(可选)",
|
|
115
|
+
"projectsWorkLinkArtifact": "关联",
|
|
116
|
+
"projectsWorkUnlinkArtifact": "解除关联",
|
|
117
|
+
"projectsWorkArtifactLinkFailed": "产物关联失败",
|
|
118
|
+
"projectsWorkActionFailed": "无法更新项目工作项",
|
|
119
|
+
"projectsWorkStateSettings": "工作项状态",
|
|
120
|
+
"projectsWorkStateSettingsDescription": "你可以修改名称和顺序;每个状态仍需归入明确的生命周期类别。",
|
|
121
|
+
"projectsWorkStateName": "状态名称",
|
|
122
|
+
"projectsWorkStateCategory": "生命周期类别",
|
|
123
|
+
"projectsWorkDefaultState": "新工作项默认使用此状态",
|
|
124
|
+
"projectsWorkNewState": "新状态名称",
|
|
125
|
+
"projectsWorkStateMigration": "把已有工作项迁移到",
|
|
126
|
+
"projectsWorkStateMigrationOptional": "状态被使用时必须选择",
|
|
127
|
+
"projectsDeleteState": "删除状态",
|
|
128
|
+
"projectsDeleteStateDescription": "如果已有工作项使用这个状态,需要先把它们迁移到另一个状态。",
|
|
129
|
+
"projectsMoveUp": "上移状态",
|
|
130
|
+
"projectsMoveDown": "下移状态",
|
|
131
|
+
"projectsWorkCategory_backlog": "待整理",
|
|
132
|
+
"projectsWorkCategory_unstarted": "未开始",
|
|
133
|
+
"projectsWorkCategory_started": "推进中",
|
|
134
|
+
"projectsWorkCategory_completed": "已完成",
|
|
135
|
+
"projectsWorkCategory_canceled": "已取消",
|
|
136
|
+
"projectsWorkSummary": "工作项概况",
|
|
137
|
+
"projectsWorkTotal": "全部工作项",
|
|
138
|
+
"projectsWorkActive": "推进中",
|
|
139
|
+
"projectsWorkNeedsAttention": "需要关注",
|
|
140
|
+
"projectsWorkCompleted": "已完成",
|
|
141
|
+
"projectsWorkRecentlyUpdated": "最近更新",
|
|
142
|
+
"projectsWorkActivity_created": "创建了工作项",
|
|
143
|
+
"projectsWorkActivity_updated": "更新了工作项",
|
|
144
|
+
"projectsWorkActivity_state-changed": "切换了状态",
|
|
145
|
+
"projectsWorkActivity_deleted": "删除了工作项",
|
|
146
|
+
"projectsWorkActivity_restored": "恢复了工作项",
|
|
147
|
+
"projectsWorkActivity_artifact-linked": "关联了产物",
|
|
148
|
+
"projectsWorkActivity_artifact-unlinked": "解除了产物关联"
|
|
90
149
|
}
|