@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/assets/api-BqVIq0lF.js +13 -0
  3. package/dist/assets/{appearance-settings-page-CPoFZLw8.js → appearance-settings-page-CTWn1eAT.js} +1 -1
  4. package/dist/assets/{book-open-jcUt0rr9.js → book-open-o2x7OLiO.js} +1 -1
  5. package/dist/assets/{channels-list-page-anMJAkfK.js → channels-list-page-Bzn-pzyJ.js} +1 -1
  6. package/dist/assets/{config-split-page-AcuFnbIC.js → config-split-page-CnRbXXlh.js} +1 -1
  7. package/dist/assets/{confirm-dialog-B-atCfC5.js → confirm-dialog-B_Hbcra2.js} +1 -1
  8. package/dist/assets/{createLucideIcon-Blz0FzrV.js → createLucideIcon-rwwpqyks.js} +1 -1
  9. package/dist/assets/{desktop-capabilities-page-8lKF_Ob-.js → desktop-capabilities-page-C2ayYQTr.js} +1 -1
  10. package/dist/assets/{desktop-update-config-DABXDpbj.js → desktop-update-config-C13czpyL.js} +1 -1
  11. package/dist/assets/{dist-eH-_j8yU.js → dist-BsF3hfOi.js} +1 -1
  12. package/dist/assets/{dist-D8kcEpTf.js → dist-aNN3iQxV.js} +1 -1
  13. package/dist/assets/{doc-browser-DzXRehlW.js → doc-browser-D8dIOo9E.js} +1 -1
  14. package/dist/assets/doc-browser-DcuOOtpP.js +1 -0
  15. package/dist/assets/{doc-browser-context-BlbUcg2J.js → doc-browser-context-Dhsit2Ma.js} +1 -1
  16. package/dist/assets/{extensions-mquwAJ2Z.js → extensions-CD6xvKF_.js} +1 -1
  17. package/dist/assets/{form-actions-CaZDGEmB.js → form-actions-CC4Wz_lL.js} +1 -1
  18. package/dist/assets/index-BTsbdtv6.js +109 -0
  19. package/dist/assets/index-Bcfy2kTh.css +1 -0
  20. package/dist/assets/loader-circle-B6aFbB-8.js +1 -0
  21. package/dist/assets/mcp-marketplace-page-BtUAypA7.js +1 -0
  22. package/dist/assets/{mcp-marketplace-page-gFtgyueL.js → mcp-marketplace-page-r2MmD9xO.js} +1 -1
  23. package/dist/assets/{model-config-page-hFBpeRAO.js → model-config-page-DboxoT-I.js} +1 -1
  24. package/dist/assets/{navigation-link-Hb5Z0UxZ.js → navigation-link-C_6oj3zz.js} +1 -1
  25. package/dist/assets/plus-DRsq9Kvs.js +1 -0
  26. package/dist/assets/{privacy-settings-page-C18zo3ox.js → privacy-settings-page-C2sDBqhb.js} +1 -1
  27. package/dist/assets/projects-BcPcx-uD.js +1 -0
  28. package/dist/assets/{providers-config-page-YU0-AOXJ.js → providers-config-page-CmUFl9RF.js} +1 -1
  29. package/dist/assets/react-BjO9I4WZ.js +8 -0
  30. package/dist/assets/{refresh-cw-ZHKfE3yx.js → refresh-cw-DsT2kvXi.js} +1 -1
  31. package/dist/assets/remote-Ci6x91ES.js +1 -0
  32. package/dist/assets/{rotate-cw-CLhOXGwE.js → rotate-cw-BCAs6c3R.js} +1 -1
  33. package/dist/assets/{runtime-config-page-Dmy1A_0Y.js → runtime-config-page-cQKNeAjL.js} +1 -1
  34. package/dist/assets/{save-CWuuZs77.js → save-CadRMTMQ.js} +1 -1
  35. package/dist/assets/{search-DcmsKAn2.js → search-B6tB4fzB.js} +1 -1
  36. package/dist/assets/{search-config-page-CxBacx6W.js → search-config-page-C8lOIkxn.js} +1 -1
  37. package/dist/assets/{secrets-config-page-B4py8-KR.js → secrets-config-page-8clzAag2.js} +1 -1
  38. package/dist/assets/security-config-DPbM6IVT.js +1 -0
  39. package/dist/assets/{select-BEr0Iy_f.js → select-CO9S_C2C.js} +1 -1
  40. package/dist/assets/skeleton-IHILNhIW.js +1 -0
  41. package/dist/assets/{tag-chip-ByqDkn6g.js → tag-chip-BZKk0UkK.js} +1 -1
  42. package/dist/assets/x-BuvHIb1d.js +1 -0
  43. package/dist/index.html +21 -21
  44. package/package.json +6 -6
  45. package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +362 -241
  46. package/src/features/apps/components/app-document-access-section.tsx +187 -0
  47. package/src/features/apps/components/app-package-card.tsx +150 -275
  48. package/src/features/apps/hooks/use-app-packages.ts +91 -35
  49. package/src/features/chat/index.ts +5 -1
  50. package/src/features/projects/AGENTS.md +3 -2
  51. package/src/features/projects/components/project-overview.tsx +70 -139
  52. package/src/features/projects/components/work/project-work-item-drawer.test.tsx +65 -0
  53. package/src/features/projects/components/work/project-work-item-drawer.tsx +367 -0
  54. package/src/features/projects/components/work/project-work-items.test.tsx +98 -0
  55. package/src/features/projects/components/work/project-work-items.tsx +199 -0
  56. package/src/features/projects/components/work/project-work-state-settings.tsx +285 -0
  57. package/src/features/projects/hooks/use-project-observation.ts +56 -26
  58. package/src/features/projects/hooks/use-project-work.test.tsx +58 -0
  59. package/src/features/projects/hooks/use-project-work.ts +215 -0
  60. package/src/features/projects/pages/project-home-page.tsx +119 -138
  61. package/src/features/projects/pages/projects-page.test.tsx +79 -308
  62. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +16 -1
  63. package/src/shared/lib/i18n/locales/en-US/projects.json +60 -1
  64. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +16 -1
  65. package/src/shared/lib/i18n/locales/zh-CN/projects.json +60 -1
  66. package/dist/assets/api-CoqPRKmF.js +0 -13
  67. package/dist/assets/doc-browser-DJEMr7Zk.js +0 -1
  68. package/dist/assets/index-DyR5iR0O.css +0 -1
  69. package/dist/assets/index-XxXj8WDS.js +0 -109
  70. package/dist/assets/loader-circle-0qPOOYQ5.js +0 -1
  71. package/dist/assets/mcp-marketplace-page-uanamJ6L.js +0 -1
  72. package/dist/assets/plus-CYOvNvcV.js +0 -1
  73. package/dist/assets/projects-C8Gm9RS8.js +0 -1
  74. package/dist/assets/react-DF-kP7iN.js +0 -8
  75. package/dist/assets/remote-B_utkBra.js +0 -1
  76. package/dist/assets/security-config-CTP4IQWw.js +0 -1
  77. package/dist/assets/skeleton-B2NHluWf.js +0 -1
  78. package/dist/assets/x-CENkFyK5.js +0 -1
  79. package/src/features/projects/components/project-work-items.test.tsx +0 -78
  80. package/src/features/projects/components/project-work-items.tsx +0 -272
@@ -1,12 +1,10 @@
1
1
  import { useState } from "react";
2
2
  import { useNavigate, useParams } from "react-router-dom";
3
3
  import { useMutation, useQueryClient } from "@tanstack/react-query";
4
- import type {
5
- ObservedRequest,
6
- ProjectObservationSnapshot,
7
- } from "@nextclaw/client-sdk";
4
+ import type { ObservedRequest } from "@nextclaw/client-sdk";
8
5
  import { toast } from "sonner";
9
6
  import { useProjects } from "@/shared/hooks/use-projects";
7
+ import { useViewportLayout } from "@/app/hooks/use-viewport-layout";
10
8
  import { t } from "@/shared/lib/i18n";
11
9
  import {
12
10
  Tabs,
@@ -19,9 +17,12 @@ import { ProjectArtifacts } from "@/features/projects/components/project-artifac
19
17
  import { ProjectOverview } from "@/features/projects/components/project-overview";
20
18
  import { ProjectRequests } from "@/features/projects/components/project-requests";
21
19
  import { ProjectSkills } from "@/features/projects/components/project-skills";
22
- import { ProjectWorkItems } from "@/features/projects/components/project-work-items";
23
- import { ChatConversationWorkspaceSection } from "@/features/chat/components/conversation/chat-conversation-workspace-section";
24
- import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
20
+ import { ProjectWorkItemDrawer } from "@/features/projects/components/work/project-work-item-drawer";
21
+ import { ProjectWorkItems } from "@/features/projects/components/work/project-work-items";
22
+ import {
23
+ ChatConversationWorkspaceSection,
24
+ usePresenter,
25
+ } from "@/features/chat";
25
26
  import {
26
27
  projectObservationQueryKey,
27
28
  useProjectObservation,
@@ -44,42 +45,41 @@ const PROJECT_TABS: ProjectHomeTab[] = [
44
45
  "agreement",
45
46
  ];
46
47
 
47
- function shouldOfferObservationSetup(
48
- sources: ProjectObservationSnapshot["sources"],
49
- ): boolean {
50
- const config = sources.find((source) => source.id === "config");
51
- return !config || config.status !== "available";
52
- }
53
-
54
- function hasObservedProjectSessions(
55
- sources: ProjectObservationSnapshot["sources"],
56
- ): boolean {
57
- return (sources.find((source) => source.id === "sessions")?.itemCount ?? 0) > 0;
58
- }
59
-
60
48
  export function ProjectsPage() {
61
- const { projectId, tab: tabParam } = useParams<{ projectId?: string; tab?: string }>();
49
+ const { projectId, tab: tabParam } = useParams<{
50
+ projectId?: string;
51
+ tab?: string;
52
+ }>();
62
53
  const navigate = useNavigate();
63
- const [pendingRequestId, setPendingRequestId] = useState<string | null>(null);
64
- const queryClient = useQueryClient();
65
54
  const presenter = usePresenter();
55
+ const queryClient = useQueryClient();
56
+ const { isMobile } = useViewportLayout();
66
57
  const projects = useProjects();
58
+ const [selectedWorkItemId, setSelectedWorkItemId] = useState<string | null>(
59
+ null,
60
+ );
61
+ const [pendingRequestId, setPendingRequestId] = useState<string | null>(null);
67
62
  const registered = projects.data?.projects ?? [];
68
- const selectedProject = registered.find((project) => project.id === projectId) ?? null;
69
- const tab: ProjectHomeTab | null = isProjectHomeTab(tabParam) ? tabParam : null;
63
+ const selectedProject =
64
+ registered.find((project) => project.id === projectId) ?? null;
65
+ const tab: ProjectHomeTab | null = isProjectHomeTab(tabParam)
66
+ ? tabParam
67
+ : null;
68
+ const needsObservation =
69
+ tab === "artifacts" || tab === "skills" || tab === "agreement";
70
70
  const observation = useProjectObservation(
71
71
  selectedProject?.id ?? null,
72
72
  selectedProject?.rootPath ?? null,
73
+ needsObservation,
73
74
  );
74
75
  const response = useMutation({
75
76
  mutationFn: sendProjectRequestResponse,
76
77
  onMutate: (input) => setPendingRequestId(input.requestId),
77
78
  onSuccess: async () => {
78
- if (selectedProject) {
79
+ if (selectedProject)
79
80
  await queryClient.invalidateQueries({
80
81
  queryKey: projectObservationQueryKey(selectedProject.id),
81
82
  });
82
- }
83
83
  toast.success(t("projectsResponseSent"));
84
84
  },
85
85
  onError: (error) =>
@@ -88,7 +88,6 @@ export function ProjectsPage() {
88
88
  ),
89
89
  onSettled: () => setPendingRequestId(null),
90
90
  });
91
-
92
91
  const reply = (
93
92
  request: ObservedRequest,
94
93
  decision: ProjectRequestDecision,
@@ -102,46 +101,31 @@ export function ProjectsPage() {
102
101
  });
103
102
  };
104
103
 
105
- if (projects.isLoading) {
104
+ if (projects.isLoading)
106
105
  return (
107
106
  <main className="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
108
107
  {t("projectsLoading")}
109
108
  </main>
110
109
  );
111
- }
112
- if (projects.isError) {
110
+ if (projects.isError)
113
111
  return (
114
112
  <main className="p-6 text-sm text-destructive">
115
113
  {t("projectsLoadFailed")}: {projects.error.message}
116
114
  </main>
117
115
  );
118
- }
119
- if (registered.length === 0) {
116
+ if (!registered.length)
120
117
  return (
121
118
  <main className="p-6 text-sm text-muted-foreground">
122
119
  {t("projectsEmpty")}
123
120
  </main>
124
121
  );
125
- }
126
- if (!selectedProject || !tab) {
122
+ if (!selectedProject || !tab)
127
123
  return (
128
124
  <main className="p-6 text-sm text-muted-foreground">
129
125
  {t("projectsChoose")}
130
126
  </main>
131
127
  );
132
- }
133
128
 
134
- const snapshot = observation.data;
135
- const startObservationSetup = () =>
136
- presenter.chatSessionListManager.createSession({
137
- projectRoot: selectedProject.rootPath,
138
- prompt: t("projectsObservationSetupPrompt"),
139
- });
140
- const startProjectWork = () =>
141
- presenter.chatSessionListManager.createSession({
142
- projectRoot: selectedProject.rootPath,
143
- prompt: t("projectsStartProjectWorkPrompt"),
144
- });
145
129
  const openProjectFile = (path: string, label: string) =>
146
130
  presenter.chatThreadManager.openFilePreview({
147
131
  path,
@@ -149,105 +133,102 @@ export function ProjectsPage() {
149
133
  viewMode: "preview",
150
134
  previewViewer: "rendered",
151
135
  });
136
+ const snapshot = observation.data;
152
137
  return (
153
138
  <>
154
- <main className="h-full min-w-0 flex-1 overflow-y-auto p-4 md:p-6">
155
- <div className="mx-auto max-w-6xl space-y-4">
156
- <header className="flex flex-wrap items-center gap-3">
157
- <h1 className="text-xl font-semibold">{t("projectsTitle")}</h1>
158
- </header>
159
-
160
- {observation.isLoading ? (
161
- <div className="rounded-xl border border-border/60 p-5 text-sm text-muted-foreground">
162
- {t("projectsLoading")}
163
- </div>
164
- ) : null}
165
- {observation.isError ? (
166
- <div className="rounded-xl border border-destructive/40 bg-destructive/5 p-5 text-sm text-destructive">
167
- {t("projectsLoadFailed")}: {observation.error.message}
168
- </div>
169
- ) : null}
170
- {snapshot ? (
171
- <>
172
- <section className="rounded-2xl border border-border/60 bg-card p-4">
173
- <h2 className="text-lg font-semibold">{snapshot.project.name}</h2>
174
- {snapshot.project.summary ? (
175
- <p className="mt-1 text-sm text-muted-foreground">
176
- {snapshot.project.summary}
177
- </p>
178
- ) : null}
179
- <p className="mt-2 break-all text-xs text-muted-foreground">
180
- {snapshot.project.rootPath}
181
- </p>
182
- </section>
139
+ <main className="h-full min-w-0 flex-1 overflow-y-auto p-3 sm:p-4 md:p-6">
140
+ <div className="mx-auto max-w-6xl space-y-4">
141
+ <header className="rounded-2xl border border-border/60 bg-card p-3 sm:p-4">
142
+ <h1 className="text-lg font-semibold">{selectedProject.name}</h1>
143
+ <p className="mt-1 break-all text-xs text-muted-foreground">
144
+ {selectedProject.rootPath}
145
+ </p>
146
+ </header>
147
+ {snapshot ? (
183
148
  <ProjectRequests
184
149
  requests={getOpenProjectRequests(snapshot)}
185
150
  pendingRequestId={pendingRequestId}
186
151
  onReply={reply}
187
152
  />
188
- <Tabs
189
- value={tab}
190
- onValueChange={(value) =>
191
- navigate(`/projects/${encodeURIComponent(selectedProject.id)}/${value}`)
192
- }
193
- >
194
- <div className="min-w-0 overflow-x-auto pb-1">
195
- <TabsList className="w-max">
196
- {PROJECT_TABS.map((key) => (
197
- <TabsTrigger key={key} value={key}>
198
- {t(`projects${key[0].toUpperCase()}${key.slice(1)}`)}
199
- </TabsTrigger>
200
- ))}
201
- </TabsList>
153
+ ) : null}
154
+ <Tabs
155
+ value={tab}
156
+ onValueChange={(value) =>
157
+ navigate(
158
+ `/projects/${encodeURIComponent(selectedProject.id)}/${value}`,
159
+ )
160
+ }
161
+ >
162
+ <div className="min-w-0 overflow-x-auto pb-1">
163
+ <TabsList className="w-max">
164
+ {PROJECT_TABS.map((key) => (
165
+ <TabsTrigger key={key} value={key}>
166
+ {t(`projects${key[0].toUpperCase()}${key.slice(1)}`)}
167
+ </TabsTrigger>
168
+ ))}
169
+ </TabsList>
170
+ </div>
171
+ <TabsContent value="overview" className="mt-4">
172
+ <ProjectOverview
173
+ projectId={selectedProject.id}
174
+ onOpenWorkItem={setSelectedWorkItemId}
175
+ />
176
+ </TabsContent>
177
+ <TabsContent value="work" className="mt-4">
178
+ <ProjectWorkItems
179
+ projectId={selectedProject.id}
180
+ onOpenWorkItem={setSelectedWorkItemId}
181
+ />
182
+ </TabsContent>
183
+ {needsObservation && observation.isLoading ? (
184
+ <div className="mt-4 rounded-xl border border-border/60 p-5 text-sm text-muted-foreground">
185
+ {t("projectsLoading")}
202
186
  </div>
203
- <TabsContent value="overview" className="mt-4">
204
- <ProjectOverview
205
- hasProjectSessions={hasObservedProjectSessions(snapshot.sources)}
206
- snapshot={snapshot}
207
- shouldOfferObservationSetup={shouldOfferObservationSetup(snapshot.sources)}
208
- onOpenFile={openProjectFile}
209
- onStartObservationSetup={startObservationSetup}
210
- onStartProjectWork={startProjectWork}
211
- />
212
- </TabsContent>
213
- <TabsContent value="work" className="mt-4">
214
- <ProjectWorkItems
215
- hasProjectSessions={hasObservedProjectSessions(snapshot.sources)}
216
- snapshot={snapshot}
217
- shouldOfferObservationSetup={shouldOfferObservationSetup(snapshot.sources)}
218
- onStartObservationSetup={startObservationSetup}
219
- onStartProjectWork={startProjectWork}
220
- />
221
- </TabsContent>
222
- <TabsContent value="artifacts" className="mt-4">
223
- <ProjectArtifacts snapshot={snapshot} onOpenFile={openProjectFile} />
224
- </TabsContent>
225
- <TabsContent value="skills" className="mt-4">
226
- <ProjectSkills
227
- snapshot={snapshot}
228
- onOpen={(skill) =>
229
- presenter.chatThreadManager.openFilePreview({
230
- path: skill.path,
231
- label: skill.name,
232
- viewMode: "preview",
233
- previewViewer: "rendered",
234
- })
235
- }
236
- />
237
- </TabsContent>
238
- <TabsContent value="agreement" className="mt-4">
239
- <ProjectAgreement snapshot={snapshot} onOpenFile={openProjectFile} />
240
- </TabsContent>
241
- </Tabs>
242
- </>
243
- ) : null}
244
- </div>
245
- </main>
246
- <ChatConversationWorkspaceSection
247
- layoutMode="desktop"
248
- sessionKey={null}
249
- projectRoot={selectedProject.rootPath}
250
- />
187
+ ) : null}
188
+ {needsObservation && observation.isError ? (
189
+ <div className="mt-4 rounded-xl border border-destructive/40 p-5 text-sm text-destructive">
190
+ {t("projectsLoadFailed")}: {observation.error.message}
191
+ </div>
192
+ ) : null}
193
+ {snapshot ? (
194
+ <>
195
+ <TabsContent value="artifacts" className="mt-4">
196
+ <ProjectArtifacts
197
+ snapshot={snapshot}
198
+ onOpenFile={openProjectFile}
199
+ />
200
+ </TabsContent>
201
+ <TabsContent value="skills" className="mt-4">
202
+ <ProjectSkills
203
+ snapshot={snapshot}
204
+ onOpen={(skill) => openProjectFile(skill.path, skill.name)}
205
+ />
206
+ </TabsContent>
207
+ <TabsContent value="agreement" className="mt-4">
208
+ <ProjectAgreement
209
+ snapshot={snapshot}
210
+ onOpenFile={openProjectFile}
211
+ />
212
+ </TabsContent>
213
+ </>
214
+ ) : null}
215
+ </Tabs>
216
+ </div>
217
+ </main>
218
+ <ProjectWorkItemDrawer
219
+ projectId={selectedProject.id}
220
+ projectRoot={selectedProject.rootPath}
221
+ workItemId={selectedWorkItemId}
222
+ onOpenChange={(open) => {
223
+ if (!open) setSelectedWorkItemId(null);
224
+ }}
225
+ onOpenArtifact={openProjectFile}
226
+ />
227
+ <ChatConversationWorkspaceSection
228
+ layoutMode={isMobile ? "mobile" : "desktop"}
229
+ sessionKey={null}
230
+ projectRoot={selectedProject.rootPath}
231
+ />
251
232
  </>
252
233
  );
253
234
  }